|
Just sharing my implementation for anyone who is still looking for an alternative solution. Takes just couple nanoseconds to find DST transition dates:
function FindTransitionDates(year) {
if (!year)
year = new Date().getFullYear();
var days = [
new Date(Date.UTC(year, 0, 1)),
new Date(Date.UTC(year, 6, 1)),
new Date(new Date(Date.UTC(year + 1, 0, 1)).getTime() - 1),
];
var offsets = [days[0].getTimezoneOffset(), days[1].getTimezoneOffset()];
if (offsets[0] == offsets[1])
return null;
var isNorth = offsets[0] > offsets[1];
var transitionDates = [];
for (var i of [0, 1]) {
var min = days[i].getTime();
var max = days[i + 1].getTime();
var startOffset = days[i].getTimezoneOffset();
while (min <= max) {
var mid = min + Math.floor((max - min) / 2);
var offset = new Date(mid).getTimezoneOffset();
if (offset == startOffset)
min = mid + 1;
else
max = mid - 1;
}
transitionDates.push(new Date(min));
}
return isNorth
? transitionDates
: transitionDates.reverse();
}
How to Use:
var dates = FindTransitionDates(2022);
function ShowTime(t) {
var p = new Date(t.getTime() - 1);
document.write(p.toString() + "</br>");
document.write(t.toString() + "</br>");
}
if (dates != null) {
ShowTime(dates[0]);
ShowTime(dates[1]);
}
modified 5-Oct-22 13:39pm.
|
|
|
|
|
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(fullYear-1, 11, 21, 12);
findSwitch = function (a, b)
{
var m; if (b-a <= p) return { from: date(a), to: date(b) };
m = a + Math.floor((b-a)/(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 (b-a > p)
{
m = a + Math.floor((b-a)/(2*p))*p;
if (hasSwitch(m, b)) { a = m; } else { b = m; }
}
return {from: date(a), to: date(b)};
};
Well, after some micro-optimization 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(year-1,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(b-a>1){m=a+(b-a>>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(y-1,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(b-a>1){m=a+(b-a>>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 13-Apr-14 8:41am.
|
|
|
|
|
My intent was to post a usable solution that folks could build upon. Optimized JavaScript code (single letter variable names, minifying, etc.) is often hard to decipher, which is why I did not take it that far, but I'm glad my base code and concept inspired you to write something production worthy and share it with the community. Excellent work!
|
|
|
|
|
I reworked the code above in an attempt to:
- account for both hemispheres (I would appreciate if someone could double check in the northern hemisphere).
- minimise the number of iterations (all timezones and daylight savings zones seem to be within 15 minute blocks of one hour with the exception of mid last century, so 16 iterations should suffice).
- return the first and last millisecond of daylight savings.
- add a little more readability to variable names.
function getDST(fullYear) {
'use strict';
var blockMs = 900000,
dst = {},
findSwitch,
jun = new Date(Date.UTC(fullYear, 5, 21, 12)),
dec = new Date(Date.UTC(fullYear, 11, 21, 12)),
isTimezoneOffsetDifference = function (firstMs, secondMs) {
return (new Date(secondMs)).getTimezoneOffset() !== (new Date(firstMs)).getTimezoneOffset();
},
junToDecDif = jun.getTimezoneOffset() - dec.getTimezoneOffset();
if (junToDecDif === 0) {
return null;
}
findSwitch = function (firstMs, secondMs) {
var midMs;
while (secondMs - firstMs > blockMs) {
midMs = firstMs + Math.floor((secondMs - firstMs) / (2 * blockMs)) * blockMs;
if (isTimezoneOffsetDifference(firstMs, midMs)) {
secondMs = midMs;
} else {
firstMs = midMs;
}
}
return isTimezoneOffsetDifference(firstMs, firstMs - 1) ? firstMs : secondMs;
};
if (junToDecDif > 0) {
dst.start = new Date(findSwitch(jun.getTime(), dec.getTime()));
jun.setFullYear(fullYear + 1);
dst.finish = new Date(findSwitch(dec.getTime(), jun.getTime()) - 1);
} else {
dst.finish = new Date(findSwitch(jun.getTime(), dec.getTime()) - 1);
jun.setFullYear(fullYear - 1);
dst.start = new Date(findSwitch(dec.getTime(), jun.getTime()));
}
return dst;
}
modified 21-Apr-15 6:31am.
|
|
|
|
|
Maybe I'm missing something, but DST started on March 14th in 2010 and ended November 7th. I don't see either of those dates in the sample snapshot. Also, does this code take into consideration the recent change to when DST starts/ends? Thank you.
-------
PRISMAY
|
|
|
|
|
|
Sorry Matt
I meant to say asp label or hidden field.
|
|
|
|
|
Hey Matt
Hopefully the Help got your attention. I need your help. Your script is fantastic and I would like to incorporate it into my website. But I cannot for the life of me get it to work in an asp.net 3.5 environment VS 2008. Nothing seem to work in allow me to use a asp label of hidden field instead of document.write so that I can control where I place the information on the page and also to be able to send it back to the server for future use. I tried using document.getElementById("HiddenFieldClientTime") and document.getElementById("Label1") and
var hdnControl=document.getElementById("HiddenFieldClientTime.ClientID") and var hdnControl=document.getElementById("Label1.ClientID").
Can you help. My objective is to post your code on the home page and when the user logs in and a post back occurs I can then store the timezone information in a session or DB.
Thanks
|
|
|
|
|
Using ASP.NET controls is your issue. At runtime, the names of your controls will be changed: http://www.asp.net/master-pages/tutorials/control-id-naming-in-content-pages-cs[^]
I recommend using AJAX to send the value back to server-side. I prefer to use jQuery to do this on the client-side, and on the server-side you can have a web service or even a page method in your web page that you call to pass the timezone value.
Hope this helps.
-Matt
|
|
|
|
|
Interesting idea, however it only works in IE the result line if FF reads:
"Last minute before DST change occurs in 110: 03/30/110 00:59:00 and 10/26/110 01:59:00"
Note the year values in the message. Also, the doctype in the example is not valid, don't know if it was like that in your original HTML or it is a formatting issue.
|
|
|
|
|
Thank-you for catching the issue. Firefox and Google Chrome appear to return the year 1900 less than the actual year; i.e. 2010 - 1900 = 110, which is the year that appears in your example. I will make a two-line update to the code to address this behavior.
After the line:
var year = new Date().getYear();
The code that needs added is:
if (year < 1000)
year += 1900;
I set my time zone to Brasilia and tested after adding the two-line conditional above. Here are the results:
Firefox
Current date/time: Fri Feb 19 2010 02:02:56 GMT-0200
Last minute before DST change occurs in 2010: 10/16/2010 23:59:00 and 02/20/2010 23:59:00
Google Chrome
Current date/time: Fri Feb 19 2010 02:02:50 GMT-0200 (E. South America Daylight Time)
Last minute before DST change occurs in 2010: 10/16/2010 23:59:00 and 02/20/2010 23:59:00
Thanks again for identifying the issue.
-Matt
|
|
|
|
|
getYear() was deprecated last millennium; getFullYear() is the correct function to use.
Explorer returns incorrect values for getYear(); Firefox follows the official spec.
|
|
|
|
|
I did know that getYear() was deprecated. However, the code still works in IE, Firefox, Chrome, etc. given the code only uses getYear() based on the conditional check for only the browsers that need it.
|
|
|
|
|
I almost forgot to tell you that
<!DOCTYPE html> is the doctype for HTML 5. Even if a browser does not support HTML 5 yet, the doctype forces the browser into standards mode. You can replace the doctype if you wish, but it is not necessary to do so.
-Matt
|
|
|
|
|
Fair enough - you live and learn. Cheers for the note
|
|
|
|
|
Interesting ! Could you add the proper explanation on your code snippets, deeply discuss about how it works....
|
|
|
|
|