JavaScript 中的日期和时间操作相对复杂且具有一些特殊的行为,处理日期和时间时常常会遇到很多挑战。下面就来深入理解日期和时间操作,并提供一些日期/时间操作的最佳实践!
本文大纲:
- 标准化时间
- 日期/时间操作
- 日期/时间操作最佳实践
- 现代日期/时间处理 API:Temporal
- 日期/时间操作库
标准化时间
标准化时间是指使用一套公认的标准来表示和衡量时间的方法。这种标准化使得不同地区和系统之间能够统一地解读和比较时间。目前最常用的标准化时间系统是协调世界时(Coordinated Universal Time,简称UTC)。UTC 是基于原子钟的国际标准时间,被广泛应用于全球各个领域,包括科学、航空、计算机网络等。
在 Web 应用中,只要知道用户所在的时区,就可以随时转换、展示时间。如果知道用户当地的时间和时区,就可以将其转换为 UTC。计算机中的时间采用 ISO 日期格式,它是 ISO-8601 扩展格式的简化版本,如下所示:
日期/时间操作
下面先来看看如何使用 JavaScript 进行日期/时间操作。
Date对象
Date 对象基于 Unix Time Stamp,即自 1970 年 1 月 1 日(UTC)起经过的毫秒数。其语法如下:
javascript
复制代码
new Date(); new Date(value); new Date(dateString); new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]])
;
注意, 创建一个新Date对象的唯一方法是通过 new
操作符,例如:let now = new Date();
若将它作为常规函数调用(即不加 new
操作符),将返回一个字符串,而非 Date
对象。
获取当前时间
javascript
复制代码
const currentDate = newDate();
如果不向 Date 构造函数传递任何内容,则返回的日期对象就是当前的日期和时间。然后,就可以将其格式化为仅提取日期部分,如下所示:
javascript
复制代码
const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // '4-7-2023'
需要注意,月份是从 0 开始的,一月就是 0,依此类推。
获取当前时间戳
可以创建一个新的 Date 对象并使用 getTime()
方法来获取当前时间戳:
javascript
复制代码
const currentDate = newDate();
const timestamp = currentDate.getTime();
在 JavaScript 中,时间戳是自 1970 年 1 月 1 日以来经过的毫秒数。如果不需要支持Date.now()直接获取时间戳,而无需创建新的 Date 对象。
解析日期
可以通过不同的方式将字符串转换为 JavaScript 日期对象。Date 对象的构造函数接受多种日期格式:
javascript
复制代码
const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");
需要注意,这里字符串不需要包含星期几,因为 JS 可以确定任何日期是星期几。
我们还可以传入年、月、日、小时、分钟和秒作为单独的参数:
javascript
复制代码
const date = newDate(2016, 6, 27, 13, 30, 0);
当然,也可以使用 ISO 日期格式:
javascript
复制代码
const date = newDate("2016-07-27T07:45:00Z");
但是,如果不明确提供时区,就会有问题:
javascript
复制代码
const date1 = newDate("25 July 2016");
const date2 = newDate("July 25, 2016");
date1 === date2; // false
这两个都会展示当地时间 2016 年 7 月 25 日 00:00:00,但是两者是不相等的。
如果使用 ISO 格式,即使只提供日期而不提供时间和时区,它也会自动接受时区为 UTC。
javascript
复制代码
newDate("25 July 2016").getTime() !== newDate("2016-07-25").getTime()
newDate("2016-07-25").getTime() === newDate("2016-07-25T00:00:00Z").getTime()
设置日期格式
现代 JavaScript 在标准命名空间中内置了一些方便的国际化函数Intl
,使日期格式化变得简单。
为此,我们需要两个对象:Date
和 Intl.DateTimeFormat
,并使用输出首选项进行初始化。假设想使用美国 (M/D/YYYY) 格式,则如下所示:
javascript
复制代码
const firstValentineOfTheDecade = new Date(2020, 1, 14); const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020
如果想要荷兰 (D/M/YYYY) 格式,只需将不同的区域性代码传递给 DateTimeFormat
构造函数即可:
javascript
复制代码
const nlBEFormatter = newIntl.DateTimeFormat('nl-BE');
console.log(nlBEFormatter.format(firstValentineOfTheDecade));
// 14/2/2020
或者美国格式的较长形式,并拼写出月份名称:
const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020
更改日期格式
我们知道了如何解析日期并对其进行格式化,将日期从一种格式更改为另一种格式只需将两者结合起来即可。
例如,如果日期格式为 Jul 21, 2013,并且想要将格式更改为 21-07-2013,可以这样实现:
const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"
本地化日期
上面讨论的日期格式化方法应该适用于大多数应用,但如果想本地化日期的格式,建议使用 Date
对象的toLocaleDateString()
方法:
const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', }); console.log(today); // '4 Jul 2023'
如果想显示日期的数字版本,建议使用以下解决方案:
const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });
这里输出了 7/26/2016. 如果想确保月份和日期是两位数,只需更改选项:
const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });
这样就会输出 07/26/2016。
还可以使用其他一些相关函数来本地化时间和日期的显示方式:
计算相对日期和时间
下面是在 JavaScript 日期中添加 20 天的示例(即计算出已知日期后 20 天的日期):
const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();
原始的日期对象现在表示7月20日之后20天的日期,newDate包含的是表示该日期的本地化字符串。newDate
的值是2016/8/9 15:00:00
。
要计算相对时间戳,并得到更精确的差异,可以使用Date.getTime()
和Date.setTime()
来处理表示自某个特定时刻(即1970年1月1日)以来的毫秒数的整数。例如,如果想知道距离现在17小时后的时间:
javascript
复制代码
const msSinceEpoch = (newDate()).getTime();
const seventeenHoursLater = newDate(msSinceEpoch + 17 * 60 * 60 * 1000);
比较时间
在比较时间时,首先需要创建日期对象,<、>、<= 和 >= 都可以工作。 因此,比较 2014 年 7 月 19 日和 2014 年 7 月 18 日就很简单:
const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log(date1); } else { console.log(date2); }
检查相等性比较棘手,因为表示同一日期的两个日期对象仍然是两个不同的日期对象并且不相等。 比较日期字符串不是一个好主意,因为例如“July 20, 2014”和“20 July 2014”表示相同的日期,但具有不同的字符串表示形式。 下面的代码片段说明了第一点:
javascript
复制代码
const date1 = newDate("June 10, 2003");
const date2 = newDate(date1);
const equalOrNot = date1 == date2 ? "相等" : "不等";
console.log(equalOrNot);
这里将输出“不等”,这种特殊情况可以通过比较日期的时间戳来解决,如下所示:
javascript
复制代码
date1.getTime() == date2.getTime()
这个例子不太符合实际的应用,因为通常不会从另一个日期对象创建日期对象。下面来看一个更实际的例子。比较用户输入的生日是否与从后端获得的幸运日期相同。
const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }
两者都代表相同的日期,但不幸的是用户将无法获得这百万美元。问题在于:JavaScript 会假定时区是浏览器提供的时区,除非另有明确指定。
这意味着,new Date ("12/20/1989")
将创建一个日期 1989-12-20T00:00:00+5:45
或 1989-12-19T18:15:00Z
,而这与时间戳为 1989-12-20T00:00:00Z 是不同的。
不能只更改现有日期对象的时区,因此现在的目标是创建一个新的日期对象,但使用 UTC 而不是本地时区。
在创建日期对象时将忽略用户的时区并使用 UTC。有两种方法可以做到这一点:
- 根据用户输入日期创建 ISO 格式的日期字符串,并使用它创建 Date 对象。 使用有效的 ISO 日期格式创建 Date 对象,同时明确指定使用的是 UTC 而不是本地时区。
javascript
复制代码
const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true
如果不指定时间,这也适用,因为默认为午夜(即 00:00:00Z):
javascript
复制代码
const userEnteredDate = newDate("1989-12-20");
const dateFromAPI = newDate("1989-12-20T00:00:00Z");
const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true
注意,如果向日期构造函数传递了正确的 ISO 日期格式 YYYY-MM-DD 的字符串,则它会自动采用 UTC。
- JavaScript 提供了一个简洁的 Date.UTC() 函数,可以使用它来获取日期的 UTC 时间戳。从日期中提取组件并将它们传递给函数。
javascript
复制代码
const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true
日期差异
下面来讨论两个用例:
- 计算两个日期之间的天数
将两个日期转换为 UTC 时间戳,找出以毫秒为单位的差异并找到等效的天数。
const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // 使用 Math.round 代替 Math.floor 来考虑某些 DST 情况 // 每天的毫秒数 = 24 小时/天 * 60 分钟/小时 * 60 秒/分钟 * 1000 毫秒/秒 const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);
- 根据出生日期计算用户的年龄
const birthDateFromAPI = "12/10/1989";
JavaScript日期时间操作完整指南!(下)https://developer.aliyun.com/article/1411543