預約時間

預約時間

學習練習

簡介

JavaScript 有一個內建的物件 Date,用來儲存日期與時間,並提供管理它們的方法。

Caution

它是以 Java 的 java.util.Date 類別為基礎,該類別在 2010 年代初被取代,但為了向後相容,JavaScript 的 Date 至今仍保留著。

由於日期本身就很難處理,加上時區處理既糟糕又常常付之闕如,因此市面上存在許多函式庫,例如 moment.js、day.js、date-fns 和 luxon。 這些在 Exercism 上都無法使用。

在你自己的專案中,不要使用 moment.js 這類已棄用或無人維護的套件,請改用 luxon 等更現代的做法,或是尚未普及的 Temporal。 本練習的重點是 Date,它在 JavaScript 走到盡頭之前都會是重要的存在。

建立

Date 物件是 Date 類別的實例。 它可以在不傳任何引數給建構函式的情況下建立。 這樣會得到一個代表目前日期與時間的 Date 物件:

const now = new Date();
// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time)
// Shows current day, date and time (in your time zone).

Unix 時間戳記(數字)

如果傳入的是數字,它會被解讀為 timestamp。 時間戳記是一個整數,代表自 1970 年 1 月 1 日 UTC+0 以來所經過的毫秒數。

const epoch = new Date(0);
// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time)

const another = new Date(1749508766627);
// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time)

你可能會以為 new Date(0) 會產生「最早」的日期物件,但 JavaScript 會把日期轉換成你的本地時區,這表示只有身處 GMT / UTC+0 時區附近的人,才會真正得到 Unix 紀元 的值。

ISO 8601 時間戳記(字串)

你可以傳入一個代表日期的字串值給 Date 建構函式。 在所有實作之間唯一一致的格式,是國際公認且標準化的所謂 ISO 8601 時間戳記字串 的簡化版本。

一個位於 UTC 的時刻看起來像這樣:

YYYY-MM-DDTHH:mm:ss.mssZ
YYYYMMDDTHHmmss.mssZ

其中各部分的替換如下:

鍵 說明 預設值
YYYY 西元年,以 4 位數表示
MM 月份,以 2 位數表示 01
DD 日期,以 2 位數表示 01
T 字面上的字母 T,用來分隔日期與時間
HH 24 小時制的小時,以 2 位數表示 00
mm 分鐘,以 2 位數表示 00
ss 秒,以 2 位數表示 00
mss 毫秒,以 3 位數表示 000
Z 字面上的字母 Z,或位移 +/-HH:mm

字面上的字母 Z 代表 UTC(沒有時區,也沒有日光節約時間)。

由於大多數組成部分都有預設值,因此省略結尾的部分是有效的:

YYYY-MM-DD

預設時間為 00:00:00.000。

如果時間戳記的結尾不是 Z,也不是以 +HH:mm 或 -HH:mm(代表時區位移)結尾,那麼基於歷史因素,下列規則適用:

當缺少時區位移時,只含日期的格式會解讀為 UTC 時間,而含日期與時間的格式則會解讀為本地時間。 解讀成 UTC 時間是源自一個歷史上的規格錯誤,它與 ISO 8601 並不一致,但因為網頁相容性的緣故已無法更動。 請參閱 損壞的剖析器:一個網頁現實問題。

Caution

其他 Date.parse 接受的格式可能可行,也可能不行。 在 JavaScript 中處理日期時,從 string 轉換成 Date 時_永遠_要使用 ISO 8601 時間戳記。

只含日期的格式是允許的,但並非所有 ISO 8601 格式都受到支援。 請參閱 MDN 上的簡化版本說明頁面。

Date 物件

既有的日期物件也可以當作建構函式的引數傳入。 這會複製既有的 Date 物件,並帶有相同的日期與時間。

const t1 = new Date();
const t2 = new Date(t1);
// Values of t1 and t2 will be the same.

分別提供日期與時間的各個組成值

傳入三個數字可以建立一個代表日期的物件。 傳入 6 個數字則可以建立一個代表日期與時間的物件。

const date1 = new Date(95, 11, 17);
// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC.

const date2 = new Date(2013, 12, 5, 13, 24, 0);
// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC.

第二個值是 month,從代表一月的 0 開始,一直到代表十二月的 11。

Date.parse()

你可能會看到有人提到或引用一個日期剖析函式 Date.parse。 由於這個函式只有少數不變的規則,其餘的實作並未被規定(因此也不標準),所以不應該使用它。

存取 Date 的組成部分

日期物件上有各種方法可以回傳日期的組成部分:

getFullYear(); // Get the year (4 digits)
getMonth(); // Get the month, from 0 to 11.
getDate(); // Get the day of month, from 1 to 31.
getHours(); // Get the hour in a 24 clock, from 0 to 23
getMinutes(); // Get the minutes, from 0 to 59
getSeconds(); // Get the seconds, from 0 to 59
getMilliseconds(); // Get the milliseconds, from 0 and 999
getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday).

每一個都有對應的 set 版本(例如 setFullYear)可以更新值。 任何超出範圍的值都會進位到下一個組成部分:

const date = new Date('2025-02-28T12:42:00Z');
// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time)

date.setDate(29);
// there was no February 29th in 2025.

date.getDate();
// => 1

date.toString();
// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time)

所有方法都有忽略本地時區的 UTC 版本。

從日期轉換

日期物件有一個方法 getTime(),會回傳以毫秒為單位的 UNIX 時間戳記,也就是自 1970 年 1 月 1 日 UTC 午夜起算的毫秒數。 此外,還有 toISOString() 方法可以將日期物件轉換成 ISO 8601 時間戳記字串。

比較日期

大於(>)與大於等於(>=),以及小於(<)與小於等於(<=),都可以直接用在兩個日期之間,或日期與數字之間。 之所以可行,是因為 JavaScript 會嘗試把日期強制轉型成原始型別。

Advanced

在比較兩個日期,或比較日期與數字時,JavaScript 會呼叫 [Symbol.toPrimitive]("number"),而它內部會呼叫 date.valueOf()。 後者等同於呼叫 date.getTime()。

如果你不想依賴這種行為,請先用 getTime() 轉換成數字。

日期不能使用相等運算子(== 和 ===)比較,但 .getTime() 的結果可以。

說明

在這道練習中,你會動手實作幾個管理預約的函式。 系統會把所有資料都存成 ISO 8601 格式的字串,但人們並不是這樣使用行事曆的。 因此需要各種函式來在不同格式之間轉換。

1. 建立一筆預約

建立一筆從現在起 n 天後、時間為當下時刻的預約。 這個函式接收 n 天,並回傳從現在起 n 天後的預約時間。

createAppointment(4, now);
// Given now is Sun Oct 05 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)
// => Sun Oct 09 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)

如果沒有用到第二個參數 now,就應該改用目前時間。

2. 將日期轉換成時間戳記

許多工具只支援國際標準化的 ISO 8601 格式。 請撰寫函式getAppointmentTimestamp,接收一個日期並回傳該格式的字串。

const appointment = new Date(Date.UTC(2010, 6, 16, 12, 42, 0, 0));

getAppointmentTimestamp(appointment);
// => '2010-07-16T12:42:00.000Z'

3. 取得預約的詳細資料

時間戳記不好閱讀,而一個能取得預約詳細資料的函式正好可以幫上忙。 函式getAppointmentDetails接收 ISO 8601 格式的時間戳記,並回傳年、月、日、時、分。

getAppointmentDetails('2022-04-24T08:15:00.000');
// => { year: 2022, month: 3, date: 24, hour: 8, minute: 15 }

4. 用指定的選項更新預約

這個函式的第一個引數是預約時間,第二個引數則是裝有一些選項的物件。 你必須依照物件中的選項更新預約,並回傳新的預約日期。 選項物件裡可能包含多個選項。

updateAppointment('2022-02-09T09:20:00.000', { month: 6 });
// => { year: 2022, month: 6, date: 9, hour: 10, minute: 20 }

5. 取得兩筆預約之間可用的時間

這個函式會接收兩筆預約(時間戳記)作為引數。 你必須回傳這兩個時間相差的秒數。

由於半秒幾乎沒有意義,請在回傳前先將數字四捨五入。

timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000');
// => 515400

6. 檢查預約目前是否有效

最後,當預約建立後,系統需要檢查它是否有效。 換句話說,預約必須落在未來,而不是過去。

請撰寫函式isValid,它接收兩個引數:一個是預約的時間戳記(字串),另一個是以時間戳記表示的目前時間(字串);在給定目前時間的情況下,如果該預約位在未來,就回傳true。

isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000');
// => true
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
JavaScript Exercism

準備好開始 預約時間 了嗎?

註冊 Exercism,透過 37 個概念159 個練習 和真人引導來學習並精通 JavaScript,全部免費。