216

This is a bit of my JS code for which this is needed:

var secDiff = Math.abs(Math.round((utc_date-this.premiere_date)/1000));
this.years = this.calculateUnit(secDiff,(86400*365));
this.days = this.calculateUnit(secDiff-(this.years*(86400*365)),86400);
this.hours = this.calculateUnit((secDiff-(this.years*(86400*365))-(this.days*86400)),3600);
this.minutes = this.calculateUnit((secDiff-(this.years*(86400*365))-(this.days*86400)-(this.hours*3600)),60);
this.seconds = this.calculateUnit((secDiff-(this.years*(86400*365))-(this.days*86400)-(this.hours*3600)-(this.minutes*60)),1);

I want to get the datetime in "ago", but if the DST is in use then the dates are off by 1 hour. I don't know how to check if the DST is in effect or not.

How can I know when the daylight saving starts and ends?

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Jo Smo
  • 6,923
  • 9
  • 47
  • 67

16 Answers16

392

This code uses the fact that getTimezoneOffset returns a greater value during Standard Time versus Daylight Saving Time (DST). Thus it determines the expected output during Standard Time, and it compares whether the output of the given date the same (Standard) or less (DST).

Note that getTimezoneOffset returns positive numbers of minutes for zones west of UTC, which are usually stated as negative hours (since they're "behind" UTC). For example, Los Angeles is UTC–8h Standard, UTC-7h DST. getTimezoneOffset returns 480 (positive 480 minutes) in December (winter, Standard Time), rather than -480. It returns negative numbers for the Eastern Hemisphere (such -600 for Sydney in winter, despite this being "ahead" (UTC+10h).

Date.prototype.stdTimezoneOffset = function () {
    var jan = new Date(this.getFullYear(), 0, 1);
    var jul = new Date(this.getFullYear(), 6, 1);
    return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
}

Date.prototype.isDstObserved = function () {
    return this.getTimezoneOffset() < this.stdTimezoneOffset();
}

var today = new Date();
if (today.isDstObserved()) { 
    alert ("Daylight saving time!");
}
2540625
  • 11,022
  • 8
  • 52
  • 58
Sheldon Griffin
  • 4,405
  • 1
  • 14
  • 5
  • 35
    I can verify that this works internationally. There are currently no time zones that use any form of DST where both Jan 1st and July 1st are either both in or both out of the DST period. Also, in all time zones in the TZDB ([with one trivial exception](http://en.wikipedia.org/wiki/Antarctica/Casey)) the larger of the two offsets is the DST offset. Since JavaScript's `getTimezoneOffset` returns the inverse value, then `Math.max` is indeed returning the *standard* offset. The code is correct. – Matt Johnson-Pint Apr 08 '13 at 21:39
  • 9
    However, if any time zone ever changes its definition such that both Jan 1st and Jul 1st are either both in DST, or both *not* in DST (and DST still applies), then this code would not work in that zone. – Matt Johnson-Pint Apr 08 '13 at 21:40
  • 11
    This does not work in general, e.g. there are countries that haven't observed DST in certain years and also some countries revert DST during ramadan. Next to that, the ECMAScript definition for Date is broken and also the handling of the TZ environment variable is broken in some implementations. All of this combined makes this method unreliable. You're better off using a library that doesn't use Date e.g. timezonecomplete – rogierschouten May 11 '15 at 21:19
  • ECMAScript definition for Date is broken? – dandavis May 11 '15 at 21:41
  • 5
    This code doesn't work in countries that doesn't observe DST, like for instance South Africa or Iceland; meaning if you use it to compare with other time zones in those countries, it won't show the correct times over there. Suggest using UTC all the way, and manually check if the time now is within a certain DST range. Then it's just a matter of changing the normal time UTC offset by +1 to get DST. – Kebman Jun 27 '15 at 18:28
  • @Kebman—it works within the limitations of the ECMAScript Date object. If the offset in Jan and Jul are different, then the larger offset is the "standard" offset (ECMScript offsets are opposite to ISO offsets). If a place doesn't observe daylight saving, the offsets will be the same and the *dst* method will always return false. However, as @rogierschouten says, the Date object doesn't allow for changes to daylight saving in different years as places adopt or abandon it or change the dates it applies. There is no logic to such movements, so a database is required for a general solution. – RobG Sep 17 '15 at 22:46
  • Be careful. For the last 2 years or so Russia keeps changing its DST switch policy. Currently most of Russian regions have same offset for winter and summer. I had problems with that having clients both inside and outside Russia – Ksenia Mar 29 '16 at 12:30
  • But we can replace getfullyear() with the year we care about. – Joshua Oct 26 '16 at 18:24
  • 1
    How can this be correct? Germany for example entered DST on the 2016-10-30 while the USA entered one week later on the 2016-11-06. Bad information like this one is what causes stuff like this to happen: http://www.macworld.co.uk/news/apple/apple-iphone-bug-causes-europe-wake-late-updated-3246750/ – Daniel F Dec 23 '16 at 02:37
  • 3
    This code is seriously flawed, does not work in all situations and should never be used. For example, the America/New_York time zone has an offset that is normally 240 and in daylight savings the offset is 300. The way this code works, is that it gets the bigger number of the January and July offsets. That returns 300. Then the `dst` function compares the current offset to the larger of the jan and jul offsets. So in November, the offset will be 300, and the larger of jan and jul is 300. 300 is not less than 300. It fails even though November is in DST. – Alan Wells Nov 07 '17 at 17:45
  • Is it neccessary to create 2 dates on the year or only one more with 6 month difference is good to go? – Morteza Tourani Dec 06 '17 at 21:55
  • @SandyGood: DST is an hour added during summer month. Standard Time is "normal" (closer to solar time). – 2540625 Mar 12 '18 at 03:32
  • It's worth noting the confusion of `getTimezoneOffset()` returning positive numbers for negative offsets (west of UTC) and vice versa. – 2540625 Mar 12 '18 at 03:34
  • 1
    All the comments insisting this cannot work are simply fear mongering. This code works. You can test it easily by opening your system clock and changing your time and/or location to whatever you want. The commenters who said this doesn't work in South Africa or Iceland or NYC in November are wrong, I tested all of those. The commenter worried about Ramadan is basically wrong, but I don't have space to explain why. As for the edge case of calculating DST for a future date in a country with new DST rules—well, I'm willing to bet some of us have a use case that can tolerate that imprecision. – Matt Korostoff Jul 25 '19 at 22:59
  • 2
    @AlanWells Regarding your comment "DST is NOT normal" (true), "DST is in the winter" (false). "So, 'normal' is in the summer, when the DST is not artificially changing the time" (false). DST is actually summer time. Winter is "normal". See https://time.is/New_York where it's "Currently Eastern Standard Time (EST), UTC -5" and "Daylight saving time (Eastern Daylight Time (EDT), UTC -4) starts March 8, 2020". – Peter Hansen Jan 18 '20 at 17:29
  • 7
    For anyone using AWS Lambda or Node with UTC as the local timezone, this will not work. getTimezoneOffset() will return 0 in both january and july. – Connor Apr 16 '21 at 18:05
  • This will not work internationally. If country doesn't use day savings, offset in July and offset in January will be the same. I had a task to show functionality only in working hours CET time. This doesn't work properly if user come from city that doesn't use day saving (Minsk in my case) – Vicky Jun 17 '21 at 11:07
  • The above code works, but a future use case where it would fail would be if a bunch of fine individuals (for some negative values of fine) try to make DST permanent, as is being proposed in the US. At that point, all time would be DST including on January 1st. – BamaPookie Jan 17 '23 at 20:43
57

This answer is quite similar to the accepted answer, but doesn't override the Date prototype, and only uses one function call to check if Daylight Savings Time is in effect, rather than two.


The idea is that, since no country observes DST that lasts for seven months[1], in an area that observes DST the offset from UTC time in January will be different to the one in July.

While Daylight Savings Time moves clocks forwards, JavaScript always returns a greater value during Standard Time. Therefore, getting the minimum offset between January and July will get the timezone offset during DST.

We then check if the dates timezone is equal to that minimum value. If it is, then we are in DST; otherwise we are not.

The following function uses this algorithm. It takes a date object, d, and returns true if daylight savings time is in effect for that date, and false if it is not:

function isDST(d) {
    let jan = new Date(d.getFullYear(), 0, 1).getTimezoneOffset();
    let jul = new Date(d.getFullYear(), 6, 1).getTimezoneOffset();
    return Math.max(jan, jul) !== d.getTimezoneOffset();    
}
Andrew
  • 132
  • 1
  • 9
Toastrackenigma
  • 7,604
  • 4
  • 45
  • 55
  • 9
    This works, but if there is no DST in the current TimeZone then it will also result true, which isn't correct. If you switch it to `Math.max(...) != d.get...()`, it will return true iff DST is observed in the given timezone AND the date is currently in DST. If DST is not observed or the date matches the Standard offset, it will return false. – GreySage Mar 29 '19 at 23:08
  • 3
    It seems that the answer has been edited to accommodate the fix from the comment above. @Toastrackenigma can you confirm and add a note that this is the case? – 3dGrabber Dec 15 '21 at 10:01
  • 4
    Yes, this was a great spot by @GreySage, and I edited the answer to incorporate it very soon after they commented. – Toastrackenigma Dec 15 '21 at 11:25
  • Currently this doesn't work to detect if daylight savings time is in effect **in a specific timezone** (only the one the client computer is on). I'd love for this to be expanded to include specifying a timezone. – jbyrd Mar 22 '23 at 17:06
29

Create two dates: one in June, one in January. Compare their getTimezoneOffset() values.

  • if January offset > June offset, client is in northern hemisphere
  • if January offset < June offset, client is in southern hemisphere
  • if no difference, client timezone does not observe DST

Now check getTimezoneOffset() of the current date.

  • if equal to June, northern hemisphere, then current time zone is DST (+1 hour)
  • if equal to January, southern hemisphere, then current time zone is DST (+1 hour)
Bob Kaufman
  • 12,864
  • 16
  • 78
  • 107
Jon Nylander
  • 8,743
  • 5
  • 34
  • 45
  • 1
    Why do you need the hemispheres? would it not be enough to say that if getTimezoneOffset() for the current date equals to the smaller of the two getTimezoneOffset() then its DST? [ and the offset is the difference between the two ?] – epeleg Nov 06 '14 at 08:53
  • You don't need the hemispheres as the accepted answer clearly demonstrates :) – Jon Nylander Nov 06 '14 at 11:32
  • 1
    This won't work. The best thing to do is to make sure you use UTC times and manually set the offset for the region you want it for. Then manually find the start and finish for the DST for the same region (if any). Then you want to check if the time for that region is inside the DST range or not, and then update the offset correspondingly with +1. This makes it possible to compare countries what observe DST and those that do not. – Kebman Jun 27 '15 at 18:34
  • 1
    The question is how to determine whether DST is in effect at the moment in the timezone of the client machine Kebman, not how to display dates, web clients already handles that for you. – Jon Nylander Jul 25 '15 at 15:07
  • You shoud check between January and July (or February and August, March and September, etc.) because they are 6 month apart. – kpull1 Apr 04 '19 at 06:39
14

I was faced with this same problem today but since our daylight saving starts and stops at differing times from the USA (at least from my understanding), I used a slightly different route..

var arr = [];
for (var i = 0; i < 365; i++) {
 var d = new Date();
 d.setDate(i);
 newoffset = d.getTimezoneOffset();
 arr.push(newoffset);
}
DST = Math.min.apply(null, arr);
nonDST = Math.max.apply(null, arr);

Then you simply compare the current timezone offset with DST and nonDST to see which one matches.

Aaron Cole
  • 333
  • 2
  • 11
  • This is how we do it as well. That is, figure out the times of the year the DST changes in your target time zone, and compute offsets for the current day and the most recent change date. They will either differ by an hour or be equal (assuming the time zone in question is an hour offset). – Heather Nov 13 '12 at 17:55
  • There is no need to create 365 values, a binary search approach that stops as soon as a change in offset is determined should be very much more efficient, even where daylight saving is not observed. All these approaches assume that places observe daylight saving every year, which is not necessarily true. Places adopt and abandon daylight saving from time to time (though ECMAScript assumes the current rules, whatever they area, applied always). – RobG Oct 16 '14 at 02:09
  • 3
    Rob - how can you do this via a binary search if you don't know where to search (i.e. is the place you are looking for is above or below you r test point?) – epeleg Nov 06 '14 at 08:46
11

The getTimezoneOffset() method in JavaScript, in a browser, returns the number of minutes offset from the 00:00 time zone. For example, America/New_York time zone in Daylight Savings (DST) returns the number 300. 300 minutes is 5 hours difference from zero. 300 minutes divided by 60 minutes is 5 hours. Every time zone is compared to the zero time zone, +00:00 / Etc/GMT / Greenwich time.

MDN Web Docs

The next thing that you must know, is that the offset has the opposite sign of the actual time zone.

Information about time zones is maintained by the Internet Assigned Numbers Authority (iana)

iana time zones

A nicely formatted table of Time Zones is supplied by joda.org

joda-time Time Zones

+00:00 or Etc/GMT is Greenwich time

All time zones are offset from +00:00 / "Etc/GMT" / Greenwich time

Daylight Savings Time is always an earlier time than the "regular" time in the summer. You set your clocks back in the fall season. ("Fall Back" slogan to remember what to do)

So, America/New_York time in Daylight Savings (winter) is one hour before the regular time. So, for example, what was normally 5 p.m. in the afternoon in New York city in the summer, is now 4 p.m. America/New_York time in Daylight Savings. The name "America/New_York" time is a "Long Format" time zone name. The east coast of the U.S typically calls their time zone Eastern Standard Time (EST)

If you want to compare today's time zone offset to the time zone offset of some other date, you need to know that mathematical sign (+/- "Positive / Negative") of the time zone offset is the opposite of the time zone.

Look at the time zone table at joda.org and find the time zone for "America/New_York" It will have a negative sign in front of the Standard Offset.

The earth rotates counter-clockwise on its axis. A person watch the sunrise in Greenwich sees the sunrise 5 hours before someone in New York City will see the sunrise. And someone on the West Coast of the U.S. will see the sunrise after someone on the East Coast of the U.S. sees the sunrise.

There's a reason why you need to know all of this. So that you'll be able to logically determine whether some JavaScript code is getting the DST status correctly or not, without needing to test every time zone at different times of the year.

Imagine that it's November in New York City, and the clocks have been set back an hour. In the summer in New York City, the offset is 240 minutes or 4 hours.

You can test this by creating a date that is in July and then getting the offset.

var July_Date = new Date(2017, 6, 1);
var july_Timezone_OffSet = July_Date.getTimezoneOffset();

console.log('july_Timezone_OffSet: ' + july_Timezone_OffSet)

What will print to the browser's developer tools console log?

Answer is: 240

So, now you can create a date in January and see what your browser returns for a time zone offset for the winter season.

var Jan_Date = new Date(2017, 0, 1);//Month is zero indexed - Jan is zero
var jan_Timezone_OffSet = Jan_Date.getTimezoneOffset();

console.log('jan_Timezone_OffSet: ' + jan_Timezone_OffSet)

Answer is: 300

Obviously 300 is bigger than 240. So, what does this mean? Should you write code that tests for the winter offset being bigger than the summer offset? Or the summer offset less than the winter offset? If there is a difference between the summer and winter time zone offsets, then you can assume that DST is being used for this time zone. But that doesn't tell you if today is using DST for the browsers time zone. So, you'll need to get the time zone offset for today.

var today = new Date();
var todaysTimeZone = today.getTimezoneOffset();

console.log('todaysTimeZone : ' + todaysTimeZone)

Answer is: ? - Depends on the time of year

If today's time zone offset and the summer time zone offset is the same, AND the summer and winter time zone offsets are different, then by logical deduction, today must be NOT be in DST.

Can you omit comparing the summer and winter time zone offsets, (To know if DST is used for this time zone) and just compare today's time zone offset to the summer TZ offset, and always get the correct answer?

today's TZ Offset !== Summer TZ Offset

Well, is today in the winter or summer? If you knew that then you could apply the following logic:

if ( it_is_winter && ( todays_TZ_Offset !== summer_TZ_Offset) {
  var are_We_In_DST = true;
}

But the problem is, that you don't know if today's date is in winter or summer. Every time zone can have its own rules for when DST starts and stops. You'd need to keep track of every time zone's rules for every time zone in the world. So, if there is a better and easier way then you might as well do it the better and easier way.

What we are left with, is that you need to know if this time zone uses DST, and then compare today's time zone offset with the summer time zone offset. That will always give you a reliable answer.

The final logic is:

if ( DST_Is_Used_In_This_Time_Zone && ( todays_TZ_Offset !== summer_TZ_Offset) {
  var are_We_In_DST = true;
}

Function to determine if the time zone in the browser uses DST:

function is_DST_Used_In_This_TimeZone() {
  var Jan_Date, jan_Timezone_OffSet, July_Date, july_Timezone_OffSet 
      offsetsNotEqual, thisYear, today;

  today = new Date();//Create a date object that is now
  thisYear = today.getFullYear();//Get the year as a number

  Jan_Date = new Date(thisYear, 0, 1);//Month is zero indexed - Jan is zero
  jan_Timezone_OffSet = Jan_Date.getTimezoneOffset();

  console.log('jan_Timezone_OffSet: ' + jan_Timezone_OffSet)

  July_Date = new Date(thisYear, 6, 1);
  july_Timezone_OffSet = July_Date.getTimezoneOffset();

  console.log('july_Timezone_OffSet: ' + july_Timezone_OffSet)

  offsetsNotEqual = july_Timezone_OffSet !== jan_Timezone_OffSet;//True if not equal

  console.log('offsetsNotEqual: ' + offsetsNotEqual);

  return offsetsNotEqual;//If the offsets are not equal for summer and
       //winter then the only possible reason is that DST is used for
       //this time zone
}
danronmoon
  • 3,814
  • 5
  • 34
  • 56
Alan Wells
  • 30,746
  • 15
  • 104
  • 152
  • 2
    [According to dateandtime.com](https://www.timeanddate.com/worldclock/usa/new-york) DST started March 10th in 2019, and therefore is in the summer, not winter, and New York's DST offset is -4, not -5. – jk7 Apr 10 '19 at 22:54
  • If an improvement or fix needs to be made to the answer please make an edit, and it will be reviewed. – Alan Wells Apr 11 '19 at 01:31
9

Based on Matt Johanson's comment on the solution provided by Sheldon Griffin I created the following code:

    Date.prototype.stdTimezoneOffset = function() {
        var fy=this.getFullYear();
        if (!Date.prototype.stdTimezoneOffset.cache.hasOwnProperty(fy)) {

            var maxOffset = new Date(fy, 0, 1).getTimezoneOffset();
            var monthsTestOrder=[6,7,5,8,4,9,3,10,2,11,1];

            for(var mi=0;mi<12;mi++) {
                var offset=new Date(fy, monthsTestOrder[mi], 1).getTimezoneOffset();
                if (offset!=maxOffset) { 
                    maxOffset=Math.max(maxOffset,offset);
                    break;
                }
            }
            Date.prototype.stdTimezoneOffset.cache[fy]=maxOffset;
        }
        return Date.prototype.stdTimezoneOffset.cache[fy];
    };

    Date.prototype.stdTimezoneOffset.cache={};

    Date.prototype.isDST = function() {
        return this.getTimezoneOffset() < this.stdTimezoneOffset(); 
    };

It tries to get the best of all worlds taking into account all the comments and previously suggested answers and specifically it:

1) Caches the result for per year stdTimezoneOffset so that you don't need to recalculate it when testing multiple dates in the same year.

2) It does not assume that DST (if it exists at all) is necessarily in July, and will work even if it will at some point and some place be any month. However Performance-wise it will work faster if indeed July (or near by months) are indeed DST.

3) Worse case it will compare the getTimezoneOffset of the first of each month. [and do that Once per tested year].

The assumption it does still makes is that the if there is DST period is larger then a single month.

If someone wants to remove that assumption he can change loop into something more like whats in the solutin provided by Aaron Cole - but I would still jump half a year ahead and break out of the loop when two different offsets are found]

epeleg
  • 10,347
  • 17
  • 101
  • 151
7

Future-Proof Solution That Works In All Time Zones

  1. Let x be the expected number of milliseconds into the year of interest without factoring in daylight savings.
  2. Let y be the number of milliseconds since the Epoch from the start of the year of the date of interest.
  3. Let z be the number of milliseconds since the Epoch of the full date and time of interest
  4. Let t be the subtraction of both x and y from z: z - y - x. This yields the offset due to DST.
  5. If t is zero, then DST is not in effect. If t is not zero, then DST is in effect.

"use strict";
function dstOffsetAtDate(dateInput) {
    var fullYear = dateInput.getFullYear()|0;
    // "Leap Years are any year that can be exactly divided by 4 (2012, 2016, etc)
    //   except if it can be exactly divided by 100, then it isn't (2100,2200,etc)
    //    except if it can be exactly divided by 400, then it is (2000, 2400)"
    // (https://www.mathsisfun.com/leap-years.html).
    var isLeapYear = ((fullYear & 3) | (fullYear/100 & 3)) === 0 ? 1 : 0;
    // (fullYear & 3) = (fullYear % 4), but faster
    //Alternative:var isLeapYear=(new Date(currentYear,1,29,12)).getDate()===29?1:0
    var fullMonth = dateInput.getMonth()|0;
    return (
        // 1. We know what the time since the Epoch really is
        (+dateInput) // same as the dateInput.getTime() method
        // 2. We know what the time since the Epoch at the start of the year is
        - (+new Date(fullYear, 0)) // day defaults to 1 if not explicitly zeroed
        // 3. Now, subtract what we would expect the time to be if daylight savings
        //      did not exist. This yields the time-offset due to daylight savings.
        - ((
            ((
                // Calculate the day of the year in the Gregorian calendar
                // The code below works based upon the facts of signed right shifts
                //    • (x) >> n: shifts n and fills in the n highest bits with 0s 
                //    • (-x) >> n: shifts n and fills in the n highest bits with 1s
                // (This assumes that x is a positive integer)
                -1 + // first day in the year is day 1
                (31 & ((-fullMonth) >> 4)) + // January // (-11)>>4 = -1
                ((28 + isLeapYear) & ((1-fullMonth) >> 4)) + // February
                (31 & ((2-fullMonth) >> 4)) + // March
                (30 & ((3-fullMonth) >> 4)) + // April
                (31 & ((4-fullMonth) >> 4)) + // May
                (30 & ((5-fullMonth) >> 4)) + // June
                (31 & ((6-fullMonth) >> 4)) + // July
                (31 & ((7-fullMonth) >> 4)) + // August
                (30 & ((8-fullMonth) >> 4)) + // September
                (31 & ((9-fullMonth) >> 4)) + // October
                (30 & ((10-fullMonth) >> 4)) + // November
                // There are no months past December: the year rolls into the next.
                // Thus, fullMonth is 0-based, so it will never be 12 in Javascript
                
                (dateInput.getDate()|0) // get day of the month
                
            )&0xffff) * 24 * 60 // 24 hours in a day, 60 minutes in an hour
            + (dateInput.getHours()&0xff) * 60 // 60 minutes in an hour
            + (dateInput.getMinutes()&0xff)
        )|0) * 60 * 1000 // 60 seconds in a minute * 1000 milliseconds in a second
        - (dateInput.getSeconds()&0xff) * 1000 // 1000 milliseconds in a second
        - dateInput.getMilliseconds()
    );
}

// Demonstration:
var date = new Date(2100, 0, 1)
for (var i=0; i<12; i=i+1|0, date.setMonth(date.getMonth()+1|0))
    console.log(date.getMonth()+":\t"+dstOffsetAtDate(date)/60/60/1000+"h\t"+date);
date = new Date(1900, 0, 1);
for (var i=0; i<12; i=i+1|0, date.setMonth(date.getMonth()+1|0))
    console.log(date.getMonth()+":\t"+dstOffsetAtDate(date)/60/60/1000+"h\t"+date);

// Performance Benchmark:
console.time("Speed of processing 16384 dates");
for (var i=0,month=date.getMonth()|0; i<16384; i=i+1|0)
    date.setMonth(month=month+1+(dstOffsetAtDate(date)|0)|0);
console.timeEnd("Speed of processing 16384 dates");

I believe that the above code snippet is superior to all other answers posted here for many reasons.

  • This answer works in all time zones, even Antarctica/Casey.
  • Daylight savings is very much subject to change. It might be that 20 years from now, some country might have 3 DST periods instead of the normal 2. This code handles that case by returning the DST offset in milliseconds, not just whether DST is in effect or not in effect.
  • The size of the months of the year and the way that Leap Years work fits perfectly into keeping our time on track with the sun. Heck, it works so perfectly that all we ever do is just adjust mere seconds here and there. Our current system of leap years has been in effect since February 24th, 1582, and will likely stay in effect for the foreseeable future.
  • This code works in timezones that do not use DST.
  • This code works in historic times before when DST was implemented (such as the 1900s).
  • This code is maximally integer-optimized and should give you no problem if called in a tight loop. After running the code snippet above, scroll down to the bottom of the output to see the performance benchmark. My computer is able to process 16384 dates in 29ms on FireFox.

However, if you are not preparing for over 2 DST periods, then the below code can be used to determine whether DST is in effect as a boolean.

function isDaylightSavingsInEffect(dateInput) {
    // To satisfy the original question
    return dstOffsetAtDate(dateInput) !== 0;
}
Jack G
  • 4,553
  • 2
  • 41
  • 50
  • This is by far the most comprehensive answer. This works perfectly in most cases. Unfortunately, it does not work for dates with a year less than 100. Any ideas as to why this is? Otherwise, this answer is perfect. – Jason Feb 08 '22 at 11:57
  • @Jason This appears to be a Y2K problem, where 2-digit years have 1900 added to them. `new Date(50,0)` yields `"Sun Jan 01 1950 00:00:00 GMT-0500 (Eastern Standard Time)"` for me. Try `dstOffsetAtDate(new Date("0050-06-01 12:00:00"))`. – Jack G Feb 11 '22 at 18:07
  • Thanks for the reply @Jack G. The dates were already set to the correct year by creating the date first `const date = new Date(50, 0, 1); date.setFullYear(50);`. That is the only way to set the date to the correct year. For now I simply hack it by prefixing `fullYear < 100 ? false : ...`. JavaScript dates are so weird. Thanks again. – Jason Feb 11 '22 at 20:21
  • 1
    unfortunately, this only works in the browser, i mean with local time. there's no way to give it another timezone than the one you're currently on, and see the dst offset for that. – ioan Mar 04 '22 at 22:42
4

The moment.js library provides an .isDst() method on its time objects.

moment#isDST checks if the current moment is in daylight saving time.

moment([2011, 2, 12]).isDST(); // false, March 12 2011 is not DST
moment([2011, 2, 14]).isDST(); // true, March 14 2011 is DST
Daniel F
  • 13,684
  • 11
  • 87
  • 116
  • I tried var moment = require('moment'); this.logger.info(moment([2011, 2, 12]).isDST()); this.logger.info(moment([2011, 2, 14]).isDST()); both are false – Logan_B Apr 05 '19 at 18:26
  • DST change dates vary among **countries**, even among states in the same country (i.e. the state of Arizona). In the USA it was on the 2011-03-13, while in Germany it was on the 2011-03-31. So the result will differ depending on which timezone moment.js is configured to work in. – Daniel F Apr 10 '19 at 23:17
  • 1
    It even varies within of the state of Arizona https://www.timeanddate.com/time/us/arizona-no-dst.html – Daniel F Apr 10 '19 at 23:25
  • I do have only offset! How can I check whether my date falls on DST or not? I tried this `moment.utc().utcOffset('-05:00').isDST()` not working! – HenonoaH Mar 17 '21 at 13:49
  • 1
    @HenonoaH This is not possible because it depends on the country, in some countries even by states. You need to know the timezone name of the location where you want to determine the DST-status. For example "Europe/Berlin" or "CEST" (while CEST already implies no DST because of "Central European Summer Time" or "ET" which exists as "EST" and "EDT" https://en.wikipedia.org/wiki/Eastern_Time_Zone). – Daniel F Mar 17 '21 at 23:33
  • In 2021, the moment.js project says to use https://moment.github.io/luxon/ or something else that is smaller https://momentjs.com/docs/#/-project-status/: "Modern web browsers (and Node.js) expose internationalization and time zone support via the Intl object, codified as ECMA-402. Libraries like Luxon (and others) take advantage of this, reducing or removing the need to ship your own data files." – buzz3791 May 28 '21 at 15:35
2

I've found that using the Moment.js library with some of the concepts described here (comparing Jan to June) works very well.

This simple function will return whether the timezone that the user is in observes Daylight Saving Time:

function HasDST() {
    return moment([2017, 1, 1]).isDST() != moment([2017, 6, 1]).isDST();
}

A simple way to check that this works (on Windows) is to change your timezone to a non DST zone, for example Arizona will return false, whereas EST or PST will return true.

enter image description here

Nico Westerdale
  • 2,147
  • 1
  • 24
  • 31
2

Use Moment.js (https://momentjs.com/)

moment().isDST(); will give you if Day light savings is observed.

Also it has helper function to calculate relative time for you. You don't need to do manual calculations e.g moment("20200105", "YYYYMMDD").fromNow();

Santhosh S
  • 782
  • 5
  • 17
  • 1
    Have you noticed that the "Use Moment.js answer" was [already given in 2017](https://stackoverflow.com/questions/11887934/how-to-check-if-dst-daylight-saving-time-is-in-effect-and-if-so-the-offset/46547439#46547439)? – Dan Dascalescu Jan 18 '21 at 12:08
1

Your're close but a little off. You never need to calculate your own time as it is a result of your own clock. It can detect if you are using daylight saving time in your location but not for a remote location produced by the offset:

newDateWithOffset = new Date(utc + (3600000*(offset)));

This will still be wrong and off an hour if they are in DST. You need for a remote time account if they are currently inside their DST or not and adjust accordingly. try calculating this and change your clock to - lets say 2/1/2015 and reset the clock back an hour as if outside DST. Then calculate for an offset for a place that should still be 2 hours behind. It will show an hour ahead of the two hour window. You would still need to account for the hour and adjust. I did it for NY and Denver and always go the incorrect (hour ahead) in Denver.

d-_-b
  • 21,536
  • 40
  • 150
  • 256
1

Update: After trying to use these functions in a custom datetime picker, I noticed that switching from March to April toggled the timezone as expected, since my zone toggles DST in March. Unexpectedly, it was toggling to the next time zone over instead of switching between Standard and Daylight in the same time zone.

Turns out that's because my original functions were always creating new Date() for the current time or the arbitrary fixed time in the past. Comparing that to the the relative times from March and April meant it would logically detect the DST toggle as switching time zones instead.

The solution was to pass the relative times into the utility functions, so all my comparisons were for the relative time instead of now or the arbitrary fixed time. Lost some of the compactness, but now the logic works as needed.

Updates to workflow:

  • t parameter defaults to new Date()
    • For fixed time, pass in an existing Date
    • For current time, pass in null or nothing
  • std() updated to use t.setMonth(v); to change the month for fixed times
    • .getTimezoneOffset() cannot chain to .setMonth(), so we need to swap from one-line notation to use closures ({}), terminators (;), and return
  • console.log() example loops through each month (0 to 11)
    • The fixed date object needs to be cloned using the same timestamp (let ts = +t;)
    • The + before the Date type casts it to a number with the Unix timestamp
    • Date() also accepts Unix timestamps to create fixed times
    • If we don't clone it, each call would pass around the same Date object with the months set to 6, which defeats the purpose
    • Ok, we're not actually cloning, just creating a new object using the same settings; same difference ;)

let ns = {
  std: (t = new Date()) => Math.max(...[0, 6].map(v => {
    t.setMonth(v);
    return t.getTimezoneOffset();
  })),
  is_dst: (t = new Date()) => t.getTimezoneOffset() < ns.std(t),
  utc: (t, std = 0) => {
    t = t || new Date();
    let z = std ? ns.std(t) : t.getTimezoneOffset(),
      zm = z % 60;
    return 'UTC' + (z > 0 ? '-' : '+') + (z / 60) + (zm ? ':' + zm : '');
  }
};

//current time only
console.log(ns.std(), ns.is_dst(), ns.utc(), ns.utc(null, 1));

//iterate each month
let t = new Date(2021,0,1);
for (let i = 0; i < 12; i++) {
  t.setMonth(i);
  let ts = +t;
  console.log(t.toDateString().split(" ")[1], ns.std(new Date(ts)), ns.is_dst(new Date(ts)), ns.utc(new Date(ts)), ns.utc(new Date(ts), 1));
}

Expanding on the compact and cryptic solution from @nkitku to turn it into a set of reusable functions.

Workflow:

  • All functions are scoped in a namespace ns so they don't conflict with other functions in the code that may have the same name
    • Namespacing also allows for compact function notation; std: ()=>Math.max(), is equivalent to function std(){ return Math.max(); }
  • std() returns the timezone offset in Standard Time
  • [0, 6] sets up a comparison of a month without DST and a month with DST
    • 0 for January, since Date.setMonth() is zero-indexed
    • 6 for July
    • Apparently, Standard Time is not in January for everyone, so we have to check both January and July
  • ...[] converts the Array of months to a Set so we can apply the map() function
    • Raw arrays cannot run map()
    • map() runs a set of variables on the same function and returns an array of results
  • Create a new Date object with year, month, day
    • The year (95 in the example) is arbitrary since the year isn't important for this calculation
    • The month plugs in our values [0, 6] as a variable v
    • The day (1 in the example) is also arbitrary
    • Logically we could have created a new Date(), then .setMonth(v), but using the arbitrary numbers is more compact and faster
  • Now that we have the dates, getTimezoneOffset() returns the offsets for each month and pushes them to the results array
  • Math.max() finds the largest value from the results, which will be the Standard Time offset
  • is_dst() checks if it is currently Daylight Savings Time
    • new Date().getTimezoneOffset() gets the current offset, with or without DST
    • ns.std() gets the offset in Standard Time
    • If the current offset is lower, then it's DST
  • utc() returns a string in UTC notation
    • The std parameter defaults to off
    • z = std ? ns.std() : new Date().getTimezoneOffset() sets the time to DST or standard based on the flag
    • zm = z % 60 captures minutes since some zones use 30 minutes for example
    • (z > 0 ? '-' : '+') assigns the correct sign per UTC notation; positive offset values are shown as negative offsets in the notation
    • (z / 60) captures the hours in single-digit format per the notation, so no need to .toString().padStart(2,'0)` for double-digit format
    • (zm ? ':' + zm : '') appends minutes if they exist for the timezone

Since this version is meant to be compact, you could save even more space by stripping out extraneous whitespace. Though that's really a job for a minifier.

std:()=>Math.max(...[0,6].map(v=>new Date(95,v,1).getTimezoneOffset())),

const ns = {
  std: () => Math.max(...[0, 6].map(v => new Date(95, v, 1).getTimezoneOffset())),
  is_dst: () => new Date().getTimezoneOffset() < ns.std(),
  utc: (std = 0) => {
    let z = std ? ns.std() : new Date().getTimezoneOffset(),
      zm = z % 60;
    return 'UTC' + (z > 0 ? '-' : '+') + (z / 60) + (zm ? ':' + zm : '');
  }
};

console.log(ns.std(), ns.is_dst(), ns.utc(), ns.utc(1));
OXiGEN
  • 2,041
  • 25
  • 19
1

With https://date-fns.org/v2.22.1/docs/Time-Zones can be solved with one line

new Date().getUTCHours() + getTimezoneOffset('Europe/Amsterdam') / 1000 / 60 / 60;

Vicky
  • 164
  • 1
  • 3
1

Assuming the DST is changed on 27th March, the following can be used for detection of DST:

//utcT1: UTC time in [ms] before DST (DST has no impact on UTC!)
const utcT1 = (new Date(Date.UTC(2023, 2, 26))).getTime();
//locT1: local time in [ms] before setting DST
const locT1 = (new Date(2023, 2, 26)).getTime();
//tz1: time zone before DST
const tz1 = (locT1 - utcT1)/1000/60;
//expected output -60 [min] for e.g. Berlin

//utcT2: UTC time in [ms] after DST
const utcT2 = (new Date(Date.UTC(2023, 2, 27))).getTime();
//locT2: local time in [ms] after DST
const locT2 = (new Date(2023, 2, 27)).getTime();
//tz2: time zone after DST
const tz2 = (locT2 - utcT2)/1000/60;
//expected output -120 [min] for e.g. Berlin

//I.e. the DST for the local time zone is 60 [min]

//Similar result can be obtain by using the getTimezoneOffset
console.log((new Date(2023, 2, 26)).getTimezoneOffset());//-60
console.log((new Date(2023, 2, 27)).getTimezoneOffset());//-120
Sapol
  • 11
  • 2
0

I recently needed to create a date string with UTC and DST, and based on Sheldon's answer I put this together:

Date.prototype.getTimezone = function(showDST) {
    var jan = new Date(this.getFullYear(), 0, 1);
    var jul = new Date(this.getFullYear(), 6, 1);

    var utcOffset = new Date().getTimezoneOffset() / 60 * -1;
    var dstOffset = (jan.getTimezoneOffset() - jul.getTimezoneOffset()) / 60;

    var utc = "UTC" + utcOffset.getSign() + (utcOffset * 100).preFixed(1000);
    var dst = "DST" + dstOffset.getSign() + (dstOffset * 100).preFixed(1000);

    if (showDST) {
        return utc + " (" + dst + ")";
    }

    return utc;
}
Number.prototype.preFixed = function (preCeiling) {
    var num = parseInt(this, 10);
    if (preCeiling && num < preCeiling) {
        num = Math.abs(num);
        var numLength   = num.toString().length;
        var preCeilingLength = preCeiling.toString().length;
        var preOffset   = preCeilingLength - numLength;
        for (var i = 0; i < preOffset; i++) {
            num = "0" + num;
        }
    }
    return num;
}
Number.prototype.getSign = function () {
    var num  = parseInt(this, 10);
    var sign = "+";
    if (num < 0) {
        sign = "-";
    }
    return sign;
}

document.body.innerHTML += new Date().getTimezone() + "<br>";
document.body.innerHTML += new Date().getTimezone(true);
<p>Output for Turkey (UTC+0200) and currently in DST: &nbsp; UTC+0300 (DST+0100)</p>
<hr>
akinuri
  • 10,690
  • 10
  • 65
  • 102
0

Is there an issue using the Date.toString().indexOf('Daylight Time') > -1

"" + new Date()

Sat Jan 01 100050 00:00:00 GMT-0500 (Eastern Standard Time)

"" + new Date(...)

Sun May 01 100033 00:00:00 GMT-0400 (Eastern Daylight Time)

This seems compatible with all browsers.

Y C
  • 73
  • 7
  • 5
    Yes, it doesn't work all around the world. In summer in Europe, you get `"Thu Jul 02 2020 14:07:01 GMT+0200 (Central European Summer Time)"` – Tadej Krevh Jul 02 '20 at 12:07