轨道
/
JavaScript
JavaScript
/
练习
/
预约时间
预约时间

预约时间

学习练习

简介

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 不一致,但出于 Web 兼容性又无法更改。 参见 Broken Parser:一个 Web 现实问题。

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,全部免费。