JavaScript Date:計算不同時區的時間

JavaScript Date:計算不同時區的時間

利用 JavaScript 的 Date object 計算不同時區的時間。

我的此刻

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

在瀏覽器的 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 然後再加上 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)
};

說明如下:

或是回傳不同的格式。

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。

一些疑難雜症

GTM vs UTC

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

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

getYear vs getFullYear vs getUTCFullYear

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 函式庫有以下這些…

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

Temporal

我們或許可以考慮 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

總結

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


javascript date Date object is mutable UTC GTM javascript