Click here to Skip to main content
14,237,394 members

JavaScript Code to Determine When DayLight Savings Time (DST) Occurs

Rate this:
3.88 (11 votes)
Please Sign up or sign in to vote.
3.88 (11 votes)
19 Feb 2010CPOL
JavaScript code to determine when DayLight Savings Time (DST) occurs

DSTCalculator/DSTCalculator.png

Introduction

Sometimes, you need to be able to determine when Daylight Savings Time (DST) will occur in a given year. DST varies around the world for timezones where DST is observed. The code herein can automatically determine the date/time a minute before a DST change occurs in the user's timezone. The JavaScript Date object is used as part of calculating the date/time just before DST adjustments occur. I needed a solution with this behavior to properly adjust UTC date/time for previous and future dates intended for display to users. Feel free to adapt the code for your own uses.

Using the Code

The DST determination is split between two functions. The DisplayDstSwitchDates() function locates the initial date/time for each month before a timezone change occurs. This method internally calls the FindDstSwitchDate() method, which in turn determines the exact minute before a timezone change occurs.

To elaborate more on the code, let's first take a look at the DisplayDstSwithDates() function. This function is very simple. Its only purpose is to get the date/time from 12 consecutive months and compare the timezone offset month to month. When the timezone changes, the month prior to the current date/time checked is captured. Typically, there are two times the timezone will change if the code is running on a browser running in a geographical location where DST is observed. The for loop completes once the month counter (variable i) reaches 12. With the current year and the two month numbers captured, the FindDstSwitchDate() function is called.

The FindDstSwitchDate() function may appear busy, but it is actually very simple. The first part of the function starts checking the timezone offset day by day, beginning with the month and year supplied as function parameters. Once the timezone offset changes, the day prior to the timezone offset change is captured. The rest of the function counts minute by minute, beginning with the day captured, until the timezone offset changes. The minute prior to the actual date/time that the timezone offset changes is captured. Using the captured day and number of minutes, the function assembles a string to represent the date/time that is one minute prior to when a timezone offset change occurs. This function is called twice for locations where DST is observed. One date represents a minute before when you turn the clock back an hour, and the other date represents a minute before when you move the clock ahead by an hour.

If you would like to learn more about DST and see the applicable DST rules for countries around the world, please have a look here: time and date.com DST information.

<!DOCTYPE html>
<html>
    <head>
    <title>DST Calculator</title>
    <script type="text/javascript">

    function DisplayDstSwitchDates()
    {
        var year = new Date().getYear();
        if (year < 1000)
            year += 1900;

        var firstSwitch = 0;
        var secondSwitch = 0;
        var lastOffset = 99;

        // Loop through every month of the current year
        for (i = 0; i < 12; i++)
        {
            // Fetch the timezone value for the month
            var newDate = new Date(Date.UTC(year, i, 0, 0, 0, 0, 0));
            var tz = -1 * newDate.getTimezoneOffset() / 60;

            // Capture when a timzezone change occurs
            if (tz > lastOffset)
                firstSwitch = i-1;
            else if (tz < lastOffset)
                secondSwitch = i-1;

            lastOffset = tz;
        }

        // Go figure out date/time occurrences a minute before
        // a DST adjustment occurs
        var secondDstDate = FindDstSwitchDate(year, secondSwitch);
        var firstDstDate = FindDstSwitchDate(year, firstSwitch);

        if (firstDstDate == null && secondDstDate == null)
            return 'Daylight Savings is not observed in your timezone.';
        else
            return 'Last minute before DST change occurs in ' +
               year + ': ' + firstDstDate + ' and ' + secondDstDate;
    }

    function FindDstSwitchDate(year, month)
    {
        // Set the starting date
        var baseDate = new Date(Date.UTC(year, month, 0, 0, 0, 0, 0));
        var changeDay = 0;
        var changeMinute = -1;
        var baseOffset = -1 * baseDate.getTimezoneOffset() / 60;
        var dstDate;

        // Loop to find the exact day a timezone adjust occurs
        for (day = 0; day < 50; day++)
        {
            var tmpDate = new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
            var tmpOffset = -1 * tmpDate.getTimezoneOffset() / 60;

            // Check if the timezone changed from one day to the next
            if (tmpOffset != baseOffset)
            {
                var minutes = 0;
                changeDay = day;

                // Back-up one day and grap the offset
                tmpDate = new Date(Date.UTC(year, month, day-1, 0, 0, 0, 0));
                tmpOffset = -1 * tmpDate.getTimezoneOffset() / 60;

                // Count the minutes until a timezone change occurs
                while (changeMinute == -1)
                {
                    tmpDate = new Date(Date.UTC(year, month, day-1, 0, minutes, 0, 0));
                    tmpOffset = -1 * tmpDate.getTimezoneOffset() / 60;

                    // Determine the exact minute a timezone change
                    // occurs
                    if (tmpOffset != baseOffset)
                    {
                        // Back-up a minute to get the date/time just
                        // before a timezone change occurs
                        tmpOffset = new Date(Date.UTC(year, month,
                                             day-1, 0, minutes-1, 0, 0));
                        changeMinute = minutes;
                        break;
                    }
                    else
                        minutes++;
                }

                // Add a month (for display) since JavaScript counts
                // months from 0 to 11
                dstDate = tmpOffset.getMonth() + 1;

                // Pad the month as needed
                if (dstDate < 10) dstDate = "0" + dstDate;

                // Add the day and year
                dstDate += '/' + tmpOffset.getDate() + '/' + year + ' ';
 
                // Capture the time stamp
                tmpDate = new Date(Date.UTC(year, month,
                                   day-1, 0, minutes-1, 0, 0));
                dstDate += tmpDate.toTimeString().split(' ')[0];
                return dstDate;
            }
        }
    }

    </script>
    </head>
    <body>
        <script type="text/javascript">
            document.write("Current date/time: " + new Date() + "<br />");
            document.write(DisplayDstSwitchDates());
        </script>
    </body>
</html>

Points of Interest

Please note that capturing the date/time one minute before a timezone change occurs is purposeful in that if you try to capture the date/time one minute later, you'll find that JavaScript applies the DST time adjustment (moves the hour ahead or backward). I am open to constructive criticism, suggestions, and requests, so please provide feedback.

History

  • v1.0: 02-14-2010: Initial post
  • v1.1: 02-14-2010: Added additional explanations for the code
  • v1.2: 02-18-2010: Added a conditional to add 1900 to the year to accommodate Firefox, Chrome, and perhaps other browsers

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matt Esterak
Engineer Intel Corporation
United States United States
I am an Automation Engineer specializing in application and web development/support.

Comments and Discussions

 
SuggestionExecution seems too expensive Pin
pcdonp5-Apr-14 22:38
memberpcdonp5-Apr-14 22:38 
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, // desired precision in ms, we want the actual second :)
          spring, fall, lastWinter, findSwitch, // declared for later use
          date = function (ms) { return new Date(ms) }, // a Date from ms
          getTzo = function (ms) { return date(ms).getTimezoneOffset() },
          hasSwitch = function (a, b) { return getTzo(a) !== getTzo(b) },
          summer = Date.UTC(fullYear, 5, 21, 12),  // June 21st, 12am
          winter = Date.UTC(fullYear, 11, 21, 12), // December 21st, 12am
          observed = hasSwitch(summer, winter);

      if (!observed) return 'Daylight Savings is not observed in your timezone.';

      lastWinter = Date.UTC(fullYear-1, 11, 21, 12); // December 21st 12am (year before)

      findSwitch = function (a, b) // finds the UTC offset change in time span a..b
      {
         var m; if (b-a <= p) return { from: date(a), to: date(b) }; // => return object
         m = a + Math.floor((b-a)/(2*p))*p; // get the middle date, rounded to precision
         return hasSwitch(m, b) ? findSwitch(m, b) : findSwitch(a, m); // next iteration
      };

      spring = findSwitch(lastWinter, summer); // find the 1st DST transition
      fall = findSwitch(summer, winter); // find the 2nd DST transition

      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) // finds the UTC offset change in time span a..b
{
  var m; while (b-a > p) // while the configured precision is not yet reached
  {
    m = a + Math.floor((b-a)/(2*p))*p; // get the middle date, rounded to precision
    if (hasSwitch(m, b)) { a = m; } else { b = m; } // set new time span to examine
  }
  return {from: date(a), to: date(b)}; // => return the resulting transition object
};


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 Cool | :cool: .

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 Smile | :) .

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.

GeneralRe: Execution seems too expensive Pin
Matt Esterak18-Jun-14 20:24
memberMatt Esterak18-Jun-14 20:24 
GeneralRe: Execution seems too expensive Pin
Mcshaz20-Apr-15 23:06
memberMcshaz20-Apr-15 23:06 
GeneralSnashot Has Incorrect Dates Pin
PRISMAY21-Dec-10 3:10
memberPRISMAY21-Dec-10 3:10 
GeneralRe: Snashot Has Incorrect Dates Pin
Matt Esterak21-May-11 22:29
memberMatt Esterak21-May-11 22:29 
QuestionCorrections to question Pin
Robert Adamo15-Oct-10 8:30
memberRobert Adamo15-Oct-10 8:30 
QuestionHelp!!!! Pin
Robert Adamo15-Oct-10 8:27
memberRobert Adamo15-Oct-10 8:27 
AnswerRe: Help!!!! Pin
Matt Esterak21-May-11 22:18
memberMatt Esterak21-May-11 22:18 
GeneralIssues Pin
SmirkinGherkin16-Feb-10 6:09
memberSmirkinGherkin16-Feb-10 6:09 
GeneralRe: Issues Pin
Matt Esterak18-Feb-10 17:15
memberMatt Esterak18-Feb-10 17:15 
GeneralRe: getYear() Pin
Francis Uy18-Mar-10 0:49
memberFrancis Uy18-Mar-10 0:49 
GeneralRe: getYear() Pin
Matt Esterak19-Mar-10 9:26
memberMatt Esterak19-Mar-10 9:26 
GeneralRe: Issues Pin
Matt Esterak18-Feb-10 18:28
memberMatt Esterak18-Feb-10 18:28 
GeneralRe: Issues Pin
SmirkinGherkin18-Feb-10 21:55
memberSmirkinGherkin18-Feb-10 21:55 
GeneralMy vote of 1 Pin
Md. Marufuzzaman14-Feb-10 5:28
mentorMd. Marufuzzaman14-Feb-10 5:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 14 Feb 2010

Tagged as

Stats

78.6K views
11 bookmarked