关于javascript:如何检查DST(夏令时)是否有效以及它是否是偏移量?

How to check if the DST (Daylight Saving Time) is in effect and if it is what's the offset?

这是我需要的一些JS代码:

1
2
3
4
5
6
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);

我想在之前获得日期时间,但是如果正在使用DST,则日期将关闭1小时。 我不知道如何检查DST是否正在使用中。

我怎么知道夏令时的开始和结束时间?


此代码使用getTimezoneOffset在标准时间与夏令时(DST)期间返回更大值的事实。因此,它确定标准时间内的预期输出,并比较给定日期的输出是否相同(标准)或更低(DST)。

请注意,getTimezoneOffset返回UTC以西区域的正分钟数,通常表示为负小时(因为它们"落后于"UTC)。例如,洛杉矶是UTC-8h标准,UTC-7h DST。 getTimezoneOffset在12月(冬季,标准时间)返回480(正480分钟),而不是-480。它返回东半球的负数(冬季的悉尼-600,尽管这是"领先"(UTC + 10h)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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!");
}


创建两个日期:一个在六月,一个在一月。比较它们的getTimezoneOffset()值。

  • 如果1月抵消> 6月抵消,客户在北半球
  • 如果1月抵消<6月抵消,客户在南半球
  • 如果没有差异,客户端时区不会观察到DST

现在检查当前日期的getTimezoneOffset()。

  • 如果等于六月,北半球,则当前时区为夏令时(+1小时)
  • 如果等于1月,南半球,则当前时区为夏令时(+1小时)


我今天遇到了同样的问题,但由于我们的夏令时开始和停止在与美国不同的时间(至少从我的理解),我使用了一个稍微不同的路线..

1
2
3
4
5
6
7
8
9
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);

然后,您只需将当前时区偏移与DST和nonDST进行比较,以查看哪一个匹配。


这个答案与接受的答案非常相似,但不会覆盖Date原型,只使用一个函数调用来检查夏令时是否有效,而不是两个。

这个想法是,因为没有国家观察持续7个月的DST [1],在观察夏令时的区域中,1月份与UTC时间的偏差将与7月份的偏差不同。

虽然夏令时向前移动时钟,但JavaScript在标准时间内总是返回更大的值。因此,在1月到7月之间获得最小偏移将在DST期间获得时区偏移。

然后我们检查日期时区是否等于该最小值。如果是,那么我们在DST;否则我们不是。

以下函数使用此算法。它需要一个日期对象d,如果夏令时对该日期有效,则返回true,如果不是,则返回false

1
2
3
4
5
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();
}


基于Matt Johanson对Sheldon Griffin提供的解决方案的评论,我创建了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    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();
    };

它试图在考虑所有评论和以前建议的答案的情况下,充分利用所有世界,特别是:

1)缓存每年stdTimezoneOffset的结果,以便在同一年测试多个日期时不需要重新计算它。

2)它不假设DST(如果它存在的话)必然在7月,
并且即使它会在某个时刻和某个地方出现在任何月份也能工作。
然而,如果确实是7月(或接近几个月)确实是DST,那么在性能方面它将更快地工作。

3)更糟糕的情况是它会比较每月第一天的getTimezoneOffset。 [并且每个测试年份执行一次]。

它仍然做出的假设是,如果有DST期间比一个月大。

如果有人想要删除这个假设,他可以将循环变成更像是Aaron Cole提供的解决方案中的东西 - 但我仍然会提前半年跳出来并在发现两个不同的偏移时突破循环]


moment.js库在其时间对象上提供.isDst()方法。

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

1
2
moment([2011, 2, 12]).isDST(); // false, March 12 2011 is not DST
moment([2011, 2, 14]).isDST(); // true, March 14 2011 is DST


JavaScript中的getTimezoneOffset()方法在浏览器中返回从00:00时区偏移的分钟数。例如,夏令时(DST)中的America / New_York时区返回数字300. 300分钟与零相差5小时。 300分钟除以60分钟是5小时。每个时区都与零时区进行比较,+ 00:00 / Etc / GMT /格林威治时间。

MDN Web Docs

接下来你必须知道的是,偏移量与实际时区的符号相反。

有关时区的信息由互联网号码分配机构(iana)维护

伊亚纳时区

时区的格式很好的表格由joda.org提供

joda-time时区

+00:00或Etc / GMT是格林威治时间

所有时区都偏离+00:00 /"Etc / GMT"/格林威治时间

夏令时总是比夏天的"常规"时间更早的时间。你在秋季重新设置时钟。 ("后退"口号要记住该怎么做)

因此,美国/纽约时间夏令时(冬季)是常规时间前一小时。所以,例如,通常是下午5点。夏天下午在纽约市,现在下午4点。美国/纽约时间夏令时。名称"America / New_York"时间是"长格式"时区名称。美国东海岸通常将其时区称为东部标准时间(EST)

如果要将今天的时区偏移与某个其他日期的时区偏移进行比较,则需要知道时区偏移的数学符号(+/-"正/负")与时区相反。

查看joda.org上的时区表,找到"America / New_York"的时区。标准偏移前面会有一个负号。

地球在它的轴上逆时针旋转。一个人在格林威治观看日出,在纽约市有人看到日出前5小时看日出。在美国东海岸有人看到日出之后,美国西海岸的某个人将看到日出。

这就是为什么你需要了解所有这一切的原因。这样您就能够在逻辑上确定某些JavaScript代码是否正确获取DST状态,而无需在一年中的不同时间测试每个时区。

想象一下,它是11月在纽约市,时钟已经缩短了一个小时。在纽约市的夏季,抵消时间为240分钟或4小时。

您可以通过创建7月份的日期然后获取偏移量来测试此项。

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

console.log('july_Timezone_OffSet: ' + july_Timezone_OffSet)

什么将打印到浏览器的开发人员工具控制台日志?

答案是:240

因此,现在您可以在1月创建一个日期,并查看您的浏览器返回的冬季时区偏移量。

1
2
3
4
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)

答案是:300

显然300大于240.那么,这意味着什么?您是否应该编写测试冬季偏移量大于夏季偏移量的代码?或者夏季偏移小于冬季抵消?如果夏季和冬季时区偏移之间存在差异,则可以假设DST正用于此时区。但是,这并没有告诉您今天是否在浏览器时区使用DST。所以,你需要获得今天的时区偏移量。

1
2
3
4
var today = new Date();
var todaysTimeZone = today.getTimezoneOffset();

console.log('todaysTimeZone : ' + todaysTimeZone)

答案是:? - 取决于一年中的时间

如果今天的时区偏移和夏季时区偏移是相同的,并且夏季和冬季时区偏移是不同的,那么通过逻辑演绎,今天必须不是夏令时。

你能否省略比较夏季和冬季时区偏移,(要知道DST是否用于此时区),只是将今天的时区偏移与夏季TZ偏移进行比较,并始终得到正确的答案?

1
today's TZ Offset !== Summer TZ Offset

嗯,今天是冬天还是夏天?如果你知道那么你可以应用以下逻辑:

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

但问题是,你不知道今天的日期是冬天还是夏天。 DST开始和停止时,每个时区都有自己的规则。您需要跟踪世界上每个时区的每个时区规则。所以,如果有更好更简单的方法,那么你可以做得更好更容易。

我们剩下的是,您需要知道此时区是否使用DST,然后将今天的时区偏移与夏令时区偏移进行比较。这总能给你一个可靠的答案。

最后的逻辑是:

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

用于确定浏览器中的时区是否使用DST的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}

好。


适用于所有时区的面向未来的解决方案

  • x是感兴趣年份的预期毫秒数,而不考虑夏令时。
  • y是自感兴趣日期年份开始以来的纪元以来的毫秒数。
  • z是自完整日期和兴趣时间的纪元以来的毫秒数
  • t是从zz - y - x减去xy。这会产生由DST引起的偏移。
  • 如果t为零,则DST无效。如果t不为零,则DST有效。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    (function(){"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, 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)
                    (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");
    })();

    我相信上面的代码片段优于此处发布的所有其他答案,原因有很多。

    • 这个答案适用于所有时区,甚至是南极洲/凯西。
    • 夏令时很容易改变。可能是从现在开始的20年,一些国家可能有3个DST周期而不是正常2.这个代码通过返回DST偏移(以毫秒为单位)来处理这种情况,而不仅仅是DST是否生效。
    • 一年中的几个月的大小和闰年的工作方式非常适合我们的时间与太阳保持一致。哎呀,它的工作非常完美,我们所做的只是在这里和那里调整仅仅几秒钟。我们目前的闰年系统自1582年2月24日起生效,并且可能在可预见的未来保持有效。
    • 此代码适用于不使用DST的时区。
    • 此代码适用于DST实施之前的历史时期(例如20世纪)。
    • 此代码是最大程度上整数优化的,如果在紧密循环中调用,则应该没有问题。运行上面的代码片段后,向下滚动到输出的底部以查看性能基准。我的电脑能够在Chrome上约97毫秒处理16384个日期。

    但是,如果您没有准备超过2个DST周期,则可以使用以下代码来确定DST是否作为布尔值生效。

    1
    2
    3
    4
    function isDaylightSavingsInEffect(dateInput) {
        // To satisfy the original question
        return dstOffsetAtDate(dateInput) !== 0;
    }

    我发现使用Moment.js库和这里描述的一些概念(比较1月到6月)非常有效。

    这个简单的函数将返回用户所在的时区是否遵守夏令时:

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

    检查此功能(在Windows上)的一种简单方法是将您的时区更改为非DST区域,例如Arizona将返回false,而EST或PST将返回true。

    enter image description here


    你很近但有点偏。您永远不需要计算自己的时间,因为它是您自己的时钟的结果。它可以检测您是否在您所在的位置使用夏令时,但不能检测偏移产生的远程位置:

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

    如果他们在夏令时,这仍然是错误的并且在一小时之内。如果他们当前在DST内部,您需要一个远程时间账户并进行相应调整。尝试计算这个并将时钟更改为 - 比如说2015年2月1日并将时钟重新设置为一小时,就像在夏令时之外一样。然后计算一个应该仍然落后2小时的地方的偏移量。它将显示两小时窗口前一小时。您仍然需要考虑小时并进行调整。我是在纽约和丹佛做过的,总是在丹佛做错(提前一小时)。


    我最近需要使用UTC和DST创建一个日期字符串,并根据Sheldon的回答我把它放在一起:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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() +"";
    document.body.innerHTML += new Date().getTimezone(true);
    1
    2
    3
    <p>
    Output for Turkey (UTC+0200) and currently in DST: &nbsp; UTC+0300 (DST+0100)
    </p>