網站搜尋

javascript - 如何檢查夏令時間(Daylight Saving Time)是否生效,如果生效,偏移量是多少?


這是我一些需要用到此功能的 JS 代碼:

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);

我想以「之前」的形式取得日期時間,但如果使用夏令時間,日期就會差一小時。我不知道如何檢查夏令時間是否生效。

我怎麼知道夏令時間何時開始和結束?

答案#1

這段程式碼利用了 getTimezoneOffset 在標準時間與日光節約時間(DST)期間返回的值不同的事實;在標準時間時返回的值會比較大。因此它可以判斷標準時間期間的預期輸出,並比較給定日期的輸出是相同(標準時間)還是較小(日光節約時間)。

請注意,getTimezoneOffset 對於位於 UTC 西方 的時區會返回 正數 分鐘,而這些時區通常會以 負數 小時表示(因為它們「落後」於 UTC)。例如,洛杉磯在標準時間為 UTC-8h,夏令時為 UTC-7h。在十二月(冬季,標準時間),getTimezoneOffset 返回 480(正 480 分鐘),而不是 -480。對於東半球則返回 負數(例如雪梨冬季為 -600,儘管它「領先」於 UTC(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!");
}

答案#2

這個答案與公認答案相當相似,但不會覆蓋日期原型,且只使用一個函式呼叫來檢查夏令時間是否生效,而非兩次。


這個概念是,由於沒有任何國家會實行長達七個月的夏令時間[1],在實行夏令時間的地區,一月份的協調世界時偏移量將與七月份的不同。

當夏令時間將時鐘撥快時,JavaScript 在標準時間內總是返回較大的值。因此,獲取一月到七月之間的最小偏移量將得到夏令時間期間的時區偏移量。

然後我們檢查日期的時區是否等於該最小值。如果是,那麼我們處於夏令時間;否則不是。

以下函數使用此算法。它接受一個日期對象 d,如果該日期正在使用夏令時間則返回 true,如果沒有則返回 false:

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();    
}

答案#3

建立兩個日期:一個在六月,一個在一月。比較它們的 getTimezoneOffset() 值。

  • 如果一月的偏移量 > 六月的偏移量,客戶在北半球
  • 如果一月的偏移量 < 六月的偏移量,客戶位於南半球
  • 如果沒有差異,客戶端時區不遵守夏令時間

現在檢查當前日期的 getTimezoneOffset()。

  • 如果是六月份,北半球,則當前時區為夏令時間(+1 小時)
  • 如果等於一月,位於南半球,則當前時區為夏令時間(+1小時)

答案#4

我今天也面臨了同樣的問題,但因為我們的夏令時間開始和結束時間與美國不同(至少依我所理解),所以我用了稍微不同的方式……

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);

然後你只需比較當前時區偏移與夏令時間和非夏令時間的偏移,看看哪一個匹配。

答案#5

適用於所有時區的未來保護解決方案

  1. 設 x 為不考慮夏令時間的情況下,該年中預期的毫秒數。
  2. 令 y 為自該日期所在年份年初起經過的毫秒數(自 Epoch 起計)。
  3. 令 z 為自所關心完整日期和時間的紀元以來的毫秒數
  4. 設 t 為從 z 中減去 x 和 y:z - y - x。這會產生因夏令時間(DST)造成的偏移量。
  5. 若 t 為零,則 DST 不生效。如果 t 不是零,則 DST 生效。

"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");

我相信上述程式碼片段在很多方面都優於這裡發布的所有其他答案。

  • 這個答案適用於所有時區,甚至南極/凱西也是如此。
  • 日光節約時間非常容易改變。也許二十年後,某些國家可能會有三個日光節約時間期間,而不是正常的兩個。這段程式碼處理了這種情況,它返回的是以毫秒為單位的日光節約時間偏移量,而不僅僅是日光節約時間是否生效。
  • 一年中月份的大小以及閏年的運作方式,完美契合了我們與太陽時間的同步。說真的,它運作得如此完美,以至於我們所做的只是偶爾微調幾秒鐘。我們現行的閏年制度自1582年2月24日開始實施,且很可能在可預見的未來持續實施。
  • 這段程式碼適用於不使用夏令時間的時區。
  • 此法典適用於DST實施前的歷史時期(如20世紀初)。
  • 這段程式碼是最大整數優化的,如果在緊湊迴圈中呼叫,應該不會有問題。執行上述程式碼片段後,往下捲到輸出底部可以看到效能基準。我的電腦在 FireFox 上能在 29 毫秒內處理 16384 日期。

然而,如果你沒有準備超過 2 個夏令時期間,那麼下面的程式碼可以用來判斷夏令時是否生效,並以布林值表示。

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

答案#6

在瀏覽器中的 JavaScript 中,getTimezoneOffset() 方法會返回與 00:00 時區的分鐘偏移量。例如,美國/紐約 時區在夏令時間(DST)期間返回數字 300。300 分鐘相當於與零時區的 5 小時差異。300 分鐘除以 60 分鐘等於 5 小時。每個時區都是與零時區 +00:00/Etc/GMT/格林威治時間進行比較的。

MDN 網頁文件

你必須知道的下一件事是,偏移量的符號與實際時區的符號相反。

有關時區的資訊由網際網路指派數字權威(IANA)維護

IANA 時區

joda.org 提供了一個格式良好的時區表

joda-time 時區

+00:00 或 Etc/GMT 是格林威治時間

所有時區均與 +00:00/「等/格林威治標準時間」/ 格林威治時間相差

夏令時間在夏季總是比「正常」時間早。你在秋季將時鐘調回去。(「時鐘回退」口號用來記住該怎麼做)

所以,美國/紐約時間在夏令時間(冬季)比標準時間早一個小時。例如,原本在夏天紐約市下午 5 點的時間,現在在夏令時間的美國/紐約時間是下午 4 點。名稱「美國/紐約」時間是「長格式」的時區名稱。美國東岸通常稱他們的時區為東部標準時間(EST)。

如果你想比較今天的時區偏移量與其他日期的時區偏移量,你需要知道時區偏移量的數學符號(正/負)與時區是相反的。

查看 joda.org 上的時區表並找到 “America/New_York” 的時區,它的標準偏移將有一個負號在前面。

地球以逆時針方向繞軸旋轉。 在格林威治觀看日出的人,會比紐約市的人早五個小時看到日出。 而美國西海岸的人會在美國東海岸的人看到日出之後才看到日出。

你需要了解這些是有原因的。這樣你就能夠邏輯地判斷某些 JavaScript 代碼是否正確獲取了夏令時間狀態,而不需要每次在一年中的不同時間測試每個時區。

想像現在是紐約市的十一月,時鐘被撥回一小時。 在紐約市的夏季,偏移時間為240分鐘或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

所以,現在你可以在一月創建一個日期,看看你的瀏覽器對冬季的時區偏移返回什麼。

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)。但這並不告訴你今天是否在瀏覽器的時區中使用夏令時間。因此,你需要獲取今天的時區偏移。

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

console.log('todaysTimeZone : ' + todaysTimeZone)

答案是:? - 取決於一年中的時間

如果今天的時區偏移與夏令時的時區偏移相同,且夏令時和冬令時的時區偏移不同,則依邏輯推論,今天一定不在夏令時。

你能不能省略比較夏季和冬季時區的偏移(以判斷這個時區是否使用夏令時),只比較今天的時區偏移和夏季的 TZ 偏移,這樣答案總是正確?

today's TZ Offset !== Summer TZ Offset

那麼,今天是冬天還是夏天呢?如果你知道的話,那麼你就可以應用以下的邏輯:

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

但問題是,你不知道今天的日期是冬天還是夏天。每個時區對夏令時間開始和結束都有自己規則。你需要追蹤世界上每個時區的規則。因此,如果有更好更簡單的方法,那你不如就用那個更好更簡單的方法。

我們剩下需要做的,就是你需要知道這個時區是否使用夏令時間,然後將今天的時區偏移與夏季的時區偏移進行比較。這將總是給你一個可靠的答案。

最終邏輯是:

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

函數用於判斷瀏覽器中的時區是否使用夏令時間(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
}