剛開始處理時間,常常會迷失在到底要傳什麼格式給後端以及到底拿回來的資料又是什麼時間格式?最怕的就是有時差問題沒處理到,讓使用者看到格林威治標準時間了,這篇會整理常見的時間格式類型、Js Date() api、以及第三方時間套件,就讓我們一起進入時光屋吧!

在我目前的開發團隊是以 iso8601 YYYY-MM-DDTHH:MM:SSZ 做為前後端溝通的時間格式,除了一些 list api 可能會帶有時間區間 sorter query-string ,就可能用 ?start_at=YYYY-mm-dd&end_at=YYYY-mm-dd 類似這樣的字串格式,這種情況就要跟後端確認一下接收的格式以及前端要做的處理唷!

什麼是 ISO 8601?

國際標準 ISO 8601,是國際標準化組織的日期和時間的表示方法,全名為《資料元件及交換格式-資訊交換-日期及時間表示法》—— wikiISO 8601 包括了詳細的日期和時間,有良好的跨平台兼容性,常用在 JSON 等數據交換格式中。

生成的時間是按照 UTC(協調世界時) 表示的,並「不是你的當地的時間」。無論你在哪個時區,使用這個方法生成的時間都會是相同的 UTC 時間,我們當地的時間會是「台北,UTC + 8:00」。ISO 8601 有很多不同格式,但最常見的是 YYYY-MM-DDTHH:MM:SSZ。

Time

冷知識 1:UTC vs GTM

我第一次看到 UTC 以為他就是小時候課本裡的「格林威治時間(GTM)」,但其實 UTC 是用原子鐘的概念,比 GTM 用太陽位置來判斷還要精準,因為地球的自轉速度並不平均, GTM 少了的時間誤差會以「閏秒」的方式加入 UTC 當中,所以對於需要非常精準的儀器而言,大多時間 UTC 做為時間計算依據。

JS 常用的 Date API

在 JavaScript 中,Date 是一種原生的 API ,常用於創建日期和時間,可以處理些實用的情境,以下是一些常見的 Date 方法(API):

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
// 取得當下的「當地時間」 date 物件
Date.now();
=> Thu Jun 01 2023 23:11:09 GMT+0800 (台北標準時間)

// 轉換特定時間點成字串
const event = new Date('04 Febuary 1994 14:48 UTC');

// date 字串,「當地時間」
event.toString();
=>'Fri Feb 04 1994 22:48:00 GMT+0800 (台北標準時間)'

// ISO 字串,「UTC 時間」
event.toISOString();
=>'1994-02-04T14:48:00.000Z'

// UTC 字串,「UTC 時間」
event.toUTCString();
=>'Fri, 04 Feb 1994 14:48:00 GMT'

// 依照第一個參數轉為當地時間,時間以當地的時間格式
event.toLocaleString('en-GB', { timeZone: 'UTC' })
=> '04/02/1994, 14:48:00'

event.toLocaleString('ko-KR', { timeZone: 'UTC' })
=> '1994. 2. 4. 오후 2:48:00'

event.toLocaleString('zh-TW', { timeZone: 'UTC' })
=> '1994/2/4 下午2:48:00'

冷知識 2:toUTCString( ) 為什麼顯示 GMT?!

JavaScript 的 Date 物件最早在 ECMAScript1 (ES1) 標準中定義,當時 toUTCString 方法的結果是以 GMT 顯示。雖然後來的標準已經改為 UTC 來表示協調世界時,但是為了向下兼容,toUTCString 仍然使用 GMT 來表示時間。

timestamp 那串神秘數字又是什麼?

timestamp 中文就是直翻為「時間戳記」,是指從 UTC 1970年1月1日0時0分0秒 起算到此時此刻的總秒數,是每一秒都在變化的。

通常你會看到一串有點長的整數,timestamp 可以透過 date( ) 的 api 去取得,如下方的範例,043 是毫秒,代表此時此刻距離 UTC 1970年1月1日0時0分0秒 已經過了 1685634071 秒。也是因為這樣的特性,timestamp 跟 UTC 一樣是沒有「時區」的概念,不論你的時區為何,同一時間點的 timestamp 永遠是相同的。

1
2
3
4
5
6
7
8
const currentTime = new Date(); 
// 透過 getTime() 取得 timestamp
currentTime.getTime();
=> 1685634071043

// 也可以再透過 new Date(timestamp) 的方式轉為好閱讀的時間格式
new Date(1685634071043).toLocaleString();
=> '2023/6/1 下午11:41:11'

實用的第三方套件:moment.js、Luxon、day.js

在我的專案中,我們的前端與後端專案都是採用 Moment.js 這一個套件,加上前端網頁使用 antd 套件,antd 在 4.X.X 版本的 date picker (時間選擇器)之前都是搭配 Moment.js 這一個套件,所以在前後端資料轉換上比較不會有認知的差異。雖然 Moment.js 很肥,但他確實也提供了許多超實用的 api ,讓前端在做時間的計算或是顯示時間時更加方便,以下舉幾個常用的情境為例:

1
2
3
4
5
6
7
8
9
10
11
// 在當前時間的基礎上再加 7 天
let newMomentObj = moment().add(7, 'days');

// 格式化當前時間為 'YYYY-MM-DD HH:mm:ss' 的格式
let formattedDate = moment().format('YYYY-MM-DD HH:mm:ss');

// 計算 '2023-06-01' 與 '2023-01-01' 之間的天數差距
let diff = moment('2023-06-01').diff(moment('2023-01-01'), 'days');

// 驗證特定時間是否介於 '2023-01-01' 與 '2023-09-01'之間
moment('2023-06-01').isBetween('2023-01-01', '2023-09-01');

But ! moment.js 不再更新了!那還有什麼套件可以用呢?

moment.js 官方推薦了同一個團隊做的 Luxon ,而 antd design 5.X.X 版本後使用的 day.js ,day.js 是一個相對 Luxon 更輕量的套件,詳細的使用率、更新狀況以及 package size ,大家可以再去官網以及 npm 上查看唷~ 常用的方法其實都與 moment.js 大同小異,這邊就不多說明哩!

Time

Time

以上就是今日的精神時光屋,這篇分享的時間觀念是很基礎與常見的,但其實時間這個主題,講起來可以有很多~很多~細節,舉例來說,最近我在寫類似週報的需求時才發現,以「週次」為單位並在前端顯示頭尾日期(星期一與星期日的日期),週次到底從星期一開始還是日開始,以及跨年份時的最後一週與第一週到底是哪一天開始等等,在不同的時間格式計算出來的結果完全不一樣,這種很細的坑真的要遇到需求後才會知道,但也不需要一次就搞懂全部,見招拆招囉!