添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

首先來定義「此刻」是什麼。

在瀏覽器的 console 輸入 new Date() 後,會得到「Tue Oct 18 2022 17:20:49 GMT+0800 (Taipei Standard Time)」這樣的結果,這個就是我所在的時區、目前的時間,就是「我的此刻」。

new Date()
Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)

「我的此刻」是 2022/10/18 的 12PM,比格林威治標準時間快 8 小時。

也就是說,「此刻」和所在地區有關 (時區列表可參考這裡,台灣在 UTC+8 這一塊)。

別人的此刻

既然「此刻」和我所在的地區有關,那麼,我可以推算不同時區,現在的時間是什麼時候嗎?

當然可以,只要知道對方的時區就可以了。

像是,小貓在 UTC+0 (冰島),小豬在 UTC-12 (貝克島),小狗在 UTC+12 (紐西蘭),那他們此刻的時間就是

  • 小貓 UTC+0 (冰島):相較我的此刻來說,小貓比我早 8 小時,也就是 2022/10/18 的 4AM。
  • 小豬 UTC-12 (貝克島):相較我的此刻來說,小豬比我早 12 小時,也就是 2022/10/17 的 4PM。
  • 小狗 UTC+12 (紐西蘭):相較我的此刻來說,小狗比我晚 12 小時,也就是 2022/10/18 的 4PM。
  • 怎麼算別人的時間

    先講結論「先從自己所在的時區,推到 UTC+0 然後再加上 timezone」即可。

    像是剛剛我們先把我的時間推導出小貓的時間 (UTC+0),然後再去推測小豬 (UTC-12) 和 小狗 (UTC+12) 的時間。

    實作為程式碼的推導過程是以下這樣的…

    首先,從自己的時間,推算出 UTC+0 的時間。為了便於說明,這裡都是帶入指定的時間「Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)」。

    const event = new Date('Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)');
    const utcTime = event.toUTCString();
    console.log(utcTime);
    

    得到以下結果,目前 UTC+0 的時間是 2022/10/18 4AM。

    'Tue, 18 Oct 2022 04:00:00 GMT';
    

    再來,結合時區,像是計算 UTC-12 (貝克島) 的時間。

    setUTCHours 帶入目前 UTC+0 的時間,並加上或減去時區,來推算指定時區的時間,得到人看不懂的 timestamp,因此要用 toUTCString 做轉換成人能看懂的字串。

    const event = new Date('Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)');
    event.setUTCHours(event.getUTCHours() - 12);
    const calculatedTime = event.toUTCString();
    console.log(calculatedTime);
    

    得到以下結果,目前 UTC-12 的時間是 2022/10/17 4PM。

    'Mon, 17 Oct 2022 16:00:00 GMT';
    

    以上會需要這樣計算…是因為無法直接將利用 date time string 帶入時區,來取得該時區的時間 😂

    我的幾天前

    我的前幾天,要怎麼算呢?

    實作 addDays 方法,使用 local 時間加上指定的天數,可傳入要延後 (+) 或提前 (-) 的天數。

    Date.prototype.addDays = function (offset) {
      this.setDate(this.getDate() + offset);
      return this;
    

    當我輸入 -7 天時,就可以得到 7 天前的時間,也就是 2022/10/11 12PM。

    var today = new Date('Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)');
    today; // Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)
    today.addDays(-7); // Tue Oct 11 2022 12:00:00 GMT+0800 (Taipei Standard Time)
    

    別人的幾天前

    如果我想知道,我的前幾天,是別人的什麼時候呢?

    實作 getDateByTimezone 函式。

    const getDateByTimezone = ({ givenDate = null, offset = 0, timezone = 0 }) => {
      const event = givenDate ? new Date(givenDate) : new Date();
      event.addDays(offset); // (1)
      event.setUTCHours(event.getUTCHours() + timezone); // (2)
      const year = event.getUTCFullYear(); // (3)
      const month = event.getUTCMonth() + 1; // (4)
      const date = event.getUTCDate();
      return `${year}-${month}-${date}`; // (5)
    

    說明如下:

  • (1) 使用 local 時間加上指定的天數,例如:在台北的我,現在是 10/18,想要推算前一天,就帶入 offset 為 -1
  • (2) 利用 getUTCHours 取得 UTC+0 時間後加上 timezone,例如:在台北的我,就是帶入 timezone 為 +8,再利用 setUTCHours 重新設定為目前的時間。
  • (3) 根據 (2) 的結果,利用 getUTCFullYeargetUTCMonthgetUTCDate 取得該時區現在時間的年、月、日,也可以得到等。
  • (4) 由於月份計算是從 0 開始,也就是 0 ~ 11 的範圍,因此若想得到我們平日在日曆看到的數字,就必須加一,得到 1 ~ 12 的範圍。
  • (5) 客製化要回傳的時間格式 yyyy-mm-dd,或是利用 toUTCStringtoLocaleString 也是可以的。
  • 或是回傳不同的格式。

    event.toUTCString().replace(/ GMT$/, ''); // 'Wed, 19 Oct 2022 16:00:00'
    event.toLocaleString('en-CA'); // '2022-10-20, 12:00:00 a.m.'
    

    利用前面實作的 getDateByTimezone 函式,來推算我的前一天,是其他人的什麼時刻?來計算小貓、小豬、小狗的前一天時間。

    小貓 UTC+0 (冰島) 的前一天是 2022/10/17 04:00 UTC。

    getDateByTimezone({
      givenDate: 'Tue Oct 18 2022 12:00:00 GMT+0800',
      offset: -1,
      timezone: 0,
    

    從 getDateByTimezone 得到 2022-10-17。

    小豬 UTC-12 (貝克島) 的前一天是 2022/10/16 16:00 UTC-12。

    getDateByTimezone({
      givenDate: 'Tue Oct 18 2022 12:00:00 GMT+0800',
      offset: -1,
      timezone: -12,
    

    從 getDateByTimezone 得到 2022-10-16。

    小狗 UTC+12 (紐西蘭) 的前一天是 2022/10/17 16:00 UTC+12。

    getDateByTimezone({
      givenDate: 'Tue Oct 18 2022 12:00:00 GMT+0800',
      offset: -1,
      timezone: 12,
    

    從 getDateByTimezone 得到 2022-10-17。

    總結以下結果,若「此刻」的我是 2022/10/18 12:00 UTC+8,則我的前一天是 2022/10/17 12:00 UTC+8。

  • 小貓 UTC+0 (冰島) 的前一天是 2022/10/17 04:00 UTC,從 getDateByTimezone 得到結果 2022-10-17。
  • 小豬 UTC-12 (貝克島) 的前一天是 2022/10/16 16:00 UTC-12,從 getDateByTimezone 得到結果 2022-10-16。
  • 小狗 UTC+12 (紐西蘭) 的前一天是 2022/10/17 16:00 UTC+12,從 getDateByTimezone 得到結果 2022-10-17。
  • 一些疑難雜症

    GTM vs UTC

    基本上 GTM 和 UTC 是相同的。那到底差異點是什麼呢?

    GTM 是「格林威治標準時間」,由格林威治天文台觀測太陽仰角來計算目前時間;而 UTC 是「世界協調時間」,由更精密的太陽日計算方式而得。相較 GTM,UTC 的準確度更高,只是說你的時間是一秒鐘幾十萬上下,那才會感覺到差異摟!

    getYear vs getFullYear vs getUTCFullYear

  • 使用 getYear 會拿到減去 1900 後的數字,而且是 local time,不過由於[千禧危機](https://zh.wikipedia.org/zh-tw/2000%E5%B9%B4%E9%97%AE%E9%A2%98{:target=”_blank”},因此已被廢棄。例如:new Date('Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)').getYear() 會得到 122,建議使用 getFullYear,例如:new Date('Tue Oct 18 2022 12:00:00 GMT+0800 (Taipei Standard Time)').getFullYear() 來得到 2022。
  • getUTCFullYear 是指取得 UTC+0 的年份,範例如下
  • 2022/12/31 23:00 UTC+11,在時區為 UTC+0 的年份需要減去 11 個小時,是同一天的 12:00,因此得到年份是 2022。
  • 2022/12/31 23:00 UTC-11,在時區為 UTC+0 的年份需要加上 11 個小時,是後一天的 10:00,因此得到年份是 2023。
  • const date1 = new Date('December 31, 2022, 23:00:00 GMT+11:00');
    const date2 = new Date('December 31, 2022, 23:00:00 GMT-11:00');
    console.log(date1.getUTCFullYear()); // 2022
    console.log(date2.getUTCFullYear()); // 2023
    

    Timestamp

    timestamp 是表示在 1970/01/01 00:00:00 UTC+0 後經過的毫秒數 (ms),也就是時間的絕對值,沒有時區轉換的問題,因此我們可以經由 timestamp 來換算不同時區的時間。

    例如:Date.now() 得到 1667878276459。

    由 timestamp 取得 local time (UTC+8)。

    new Date(1667878276459)
    'Tue Nov 08 2022 11:31:16 GMT+0800 (Taipei Standard Time)'
    

    由 timestamp 取得 UTC+0 的時間。

    new Date(1667878276459).toUTCString()
    'Tue, 08 Nov 2022 03:31:16 GMT'
    

    若已知日期、時間和時區,要怎麼取得 timestamp 呢?通常會想要轉成 timestamp,就是要利用它能溝通不同時區的時間的特性。

    利用 Date.parse 將已知日期、時間和時區轉換成 timestamp。

    Date.parse('2022-11-08 GMT+0800')
    

    得到 timestamp 如下。

    1667836800000
    

    取得 UTC+0 的時間字串。

    const date = new Date(1667836800000);
    getDateByTimezone({
      givenDate: date,
      offset: 0,
      timezone: 0,
    

    offset 填 0 表示當天。

    "2022-11-7"
    

    取得 UTC+8 的時間字串。

    const date = new Date(1667836800000);
    getDateByTimezone({
      givenDate: date,
      offset: 0,
      timezone: 8,
    
    "2022-11-8"
    

    以上證明 timestamp 真的可以溝通不同時區的時間。

    Date.now() vs Date().getTime

    Date.now()Date().getTime 同樣是得到回傳 1970/01/01 00:00:00 UTC+0 後經過的毫秒數 (ms)。差異在於,在使用方面,Date.now() 是 Date 物件的 static method,無法經由 instance 來叫用;而 Date().getTime 會先呼叫 Date constructor 的 method 來初始化實體。(instance),再取得 timestamp。也因為叫用方法的差異,在計算效能時,Date.now() 會比 Date().getTime 來得快 (點此測試)。

    Date & Time 函式庫…或是?

    常見的 Date & Time 函式庫有以下這些…

  • Moment.js
  • date-fns
  • DayJS
  • 使用函式庫的優點是簡單便利,但缺點就是要考量的東西頗多,像是一但不維護了怎麼辦?文件是否友善?擴充性如何?和其他 library 的 dependency 如何?多國語系的支援程度?package 大小?生態系?等等一堆問題。

    Temporal

    (2025/02/05 更新) 滿久以前 Temporal 就已經進入 stage 3,最近有消息指出再過一陣子就可以使用 Temporal,因為現在進入瀏覽器的實驗版本的階段了 - JavaScript Temporal is coming

    嘗試用 Firefix Nightly 做個簡單的 demo。

    取得 UTC+0 目前的時間。

    Temporal.Now.instant().toString() // current time in UTC+0 
    

    取得 local time。

    Temporal.Now.zonedDateTimeISO().toString() // local time
    

    local time 加上 7 天之後的時間。

    Temporal.Now.zonedDateTimeISO().add({ days: 7 }).toString() // local time after 7 days
    

    寫起來是蠻簡潔有力的,換成原生的 API 吧,不再需要肥大的 library,也不用手刻落落長的程式碼摟。

    我們或許可以考慮 Temporal,Temporal 不是 library 而是標準的提案,這個提案目前在 Stage 3,這樣就能有更好的原生 JavaScript API 以供使用,這裡有介紹文polyfills 可以裝來玩玩看。

    如下範例所示,(1) 取得 UTC+0 的時間;(2) 我所在的時區是 Asia/Taipei UTC+8;(3) 目前台北的時間 (4) 台北時間 7 天後的日期。

    const instant = Temporal.Now.instant(); // (1) current time in UTC+0
    console.log('current time in UTC+0: ', instant);
    const timezone = Temporal.Now.timeZone();
    console.log('local timezone: ', timezone); // (2) local timezone
    const localDateTime = Temporal.Now.zonedDateTimeISO();
    console.log('local time: ', localDateTime.toString()); // (3) local time
    const nextSevenDays = localDateTime.add({ days: 7 }).toString();
    console.log('next 7 days in local time: ', nextSevenDays); // (4) next 7 days in UTC+8
    

    範例程式碼 & Demo

    為了避免時間計算錯誤,有幾個小技巧:

  • 一律由前端在瀏覽器取得 local time,這樣才能確切知道目前使用者所在時區的時間。
  • 如果覺得人生太複雜,一律將 local time 轉到 UTC+0 再做後續處理。
  • 關於跨時區計算的步驟:(1) 取得 local time;(2) 轉換至 UTC+0;(3) 依照目標的時區加/減相對 UTC+0 所需要的小時,例如 UTC+8 表示加 8 小時,UTC-11 表示減 11 小時。
  • 細節 1:注意傳入時間的格式,格式不同會得到不同時區的時間或是不合法的格式,如下例所示:
  • (O) new Date('December 31, 2022, 23:00:00 GMT+11:00') 會得到指定 timezone 的時間,在這裡是 UTC+11。
  • (O) new Date(2022, 12, 21, 23) 會得到 local time。
  • (X) new Date('2022--12--21') 並非合法的格式,因此會得到錯誤訊息「Invalid Date」。
  • 合法的格式可參考這裡,並且截圖如下。

  • 細節 2:只有 UTC 字樣的方法 (例如:getUTCFullYear、getUTCMonth 和 getUTCDate) 才能取得 UTC+0 的時間,其他都是取得 local time。
  • 撰寫單元測試時,記得多測幾個時區,例如:UTC+12、UTC+0、UTC-12,這樣就能保證對所有時區的計算是正確的,不是剛好猜中 😂
  •