Hello,
Before creating such a function myself, I searched and found this inspiring article.
However, stepping through every month to find the switch months, then stepping through two month's days and finally through two day's minutes seems quite expensive in terms of performance.
Instead, I've been thinking of using some optimized iteration routine (sort of binary search) as follows:
Starting with the two dates A = midsummer 12:00 UTC (06/21) and B = midwinter 12:00 UTC (12/21):
1. Compare the local UTC offsets to see if there is a difference. If not, Daylight Savings is not observed and we are done. Else a DST switch exists between A and B and we proceed with step 2:
2. Find the exact middle date between A and B and check whether its UTC offset differs from A's offset or B's offset. Repeat this step using the new time span where the difference is found, until it is small enough, e.g. one minute or even just one second => finished.
3. To find the DST switch in spring, repeat steps 1 and 2 using midwinter of previous year as A and midsummer as B.
Since we always divide the examined time span by 2, that interval decreases fast, so that it takes only few iteration steps for each year.
Edit:
Curious about what my approach would lead to, I decided to code it and came up with the following:
<!DOCTYPE html>
<html>
<head>
<title>DST Calculator using binary search (1)</title>
<script type="text/javascript">
function DisplayDSTtransitions (fullYear) {
'use strict';
var p = 1000,
spring, fall, lastWinter, findSwitch,
date = function (ms) { return new Date(ms) },
getTzo = function (ms) { return date(ms).getTimezoneOffset() },
hasSwitch = function (a, b) { return getTzo(a) !== getTzo(b) },
summer = Date.UTC(fullYear, 5, 21, 12),
winter = Date.UTC(fullYear, 11, 21, 12),
observed = hasSwitch(summer, winter);
if (!observed) return 'Daylight Savings is not observed in your timezone.';
lastWinter = Date.UTC(fullYear1, 11, 21, 12);
findSwitch = function (a, b)
{
var m; if (ba <= p) return { from: date(a), to: date(b) };
m = a + Math.floor((ba)/(2*p))*p;
return hasSwitch(m, b) ? findSwitch(m, b) : findSwitch(a, m);
};
spring = findSwitch(lastWinter, summer);
fall = findSwitch(summer, winter);
return 'DST switch occurs<br>from: ' + spring.from + ' to: ' + spring.to +
'<br>and<br>from: ' + fall.from + ' to: ' + fall.to;
}
</script>
</head>
<body>
<script type="text/javascript">
document.write("Current date/time: " + new Date() + "<br />");
document.write(DisplayDSTtransitions(new Date().getFullYear()));
</script>
</body>
</html>
Function findSwitch(a, b) is called recursively until the examined time span a..b is less or equal to the configured precision p in milliseconds. It finally returns an object with two properties "from" and "to", which are the corresponding Date objects immediately before the DST transition (from) and after the DST transition (to).
As expected, this solution needs less iterations and surprisingly litte code. For a precision of 60000 ms (one minute) it takes only 18 iterations to determine a single DST switch, in total 36 iterations for both spring and fall transitons. For a precision of 1000ms (the actual transition second), it takes only 6 iterations more per transition, i.e. 48 iterations for both of them.
For those who dont't like recursion, function findSwitch can also be written using a classical while loop:
findSwitch = function (a, b)
{
var m; while (ba > p)
{
m = a + Math.floor((ba)/(2*p))*p;
if (hasSwitch(m, b)) { a = m; } else { b = m; }
}
return {from: date(a), to: date(b)};
};
Well, after some microoptimization and a bit of compression my code looks like this:
<!DOCTYPE html>
<html>
<head>
<title>DST Calculator using binary search (2)</title>
<script type="text/javascript">
function getDstSwitches (year) {
var p=1000, tzo=function(n){return new Date(n*p).getTimezoneOffset();},
f = function (a, b) {return tzo(a) !== tzo(b);}, search,
dec = new Date(year1,11,21,12)/p, jun = new Date(year,5,21,12)/p;
if (!f(dec, jun)){return null;}
search = function (a,b) {
var m;while(ba>1){m=a+(ba>>1);f(m,b)?a=m:b=m;}return{from:a*p,to:b*p};
};
return { first: search(dec,jun), second: search(jun,new Date(year,11,21,12)/p) };
}
</script>
</head>
<body>
<script type="text/javascript">
var now = new Date(), dstSwitches = getDstSwitches(now.getFullYear());
document.write('Current date/time: ' + now + '<br />');
document.write(
!dstSwitches ? 'Daylight Savings is not observed in your timezone.' : 'DST switches: ' +
'<br>1) ' + new Date(dstSwitches.first.from) + ' to ' + new Date(dstSwitches.first.to) +
'<br>2) ' + new Date(dstSwitches.second.from) + ' to ' + new Date(dstSwitches.second.to)
);
</script>
</body>
</html>
The binary search algorithm is implemented without recursion in function "search". Now the main function getDstSwitches(year) finds both DST transitions in about 1ms on my machine .
The final main function after even more compression is:
function getDstSwitches(y){
var p=1e3,z=function(n){return(new Date(n*p)).getTimezoneOffset()},s,
f=function(a,b){return z(a)!==z(b)},d=new Date(y1,11,21,12)/p,j=new Date(y,5,21,12)/p;
if(!f(d,j)){return null}s=function(a,b){var m;while(ba>1){m=a+(ba>>1);f(m,b)?a=m:b=m}
return{from:a*p,to:b*p}};return{first:s(d,j),second:s(j,new Date(y,11,21,12)/p)}
}
This is really little code and lightning fast. I'm definitely going to implement this one in my web application .
Note that this code is supposed to work for any time zone, provided that the browser knows the correct timezone rules. But please leave a comment here, if you encounter a time zone or browser where the function gives wrong results.
Regards, Don P
modified 13Apr14 8:41am.
