Date 对象和日期时间字符串
JavaScript 用 Date 表示一个时间点:从 Unix 纪元(UTC 1970 年 1 月 1 日 00:00:00)起的毫秒数,忽略闰秒。一天按 86,400,000 毫秒计。规范里可表示的范围大约是纪元前后各 1 亿天(约 ±8.64×10¹⁵ 毫秒),超出则得到 Invalid Date。
时间相关的一些概念
GMT 时间
GMT 即「格林威治标准时间」(Greenwich Mean Time)。它以通过英国伦敦郊区皇家格林威治天文台的经线为本初子午线。由于地球自转不均匀,GMT 有误差,日常计时已改用 UTC。
UTC 时间
UTC 是现行主要的世界时间标准,由平均太阳时(历史上以 GMT 为准)、地轴运动修正,以及以秒为单位的国际原子时综合得到,比 GMT 更稳。它与地球自转之差须保持在 0.9 秒以内;若超出,由设在巴黎的国际地球自转服务(IERS)发布闰秒。日常使用里可以把 UTC 与 GMT 看成同一时刻,仅在讨论闰秒时才需要区分。
时区的概念
时区是地球上使用同一套时间定义的区域。以前人们靠太阳时角定地方时,经度不同则钟点不同。19 世纪后期铁路时刻表推动了统一时区;1884 年国际子午线会议之后,按经度划分时区逐渐普及。
经典模型把全球分成二十四个时区,每个时区有自己的本地时间。现行民用时区还有半时区、45 分钟偏移等,不只二十四档。
本地时间 = 标准时间 + 时区差(东为正,西为负)
时区表示法
如果时间以协调世界时(UTC)表示,则在时间后面直接加上「Z」(不加空格)。「Z」是 UTC 中 0 时区的标志。
// 09:30 UTC
09:30Z
0930Z
// 14:45:15 UTC
14:45:15Z
144515Z
UTC 时间也被叫做祖鲁时间,因为在北约音标字母中用 「Zulu」表示「Z」。
UTC 偏移量
UTC 偏移量代表某个具体时间与 UTC 之间的时区差,写成 ±[hh]:[mm]、±[hh][mm]、±[hh]。
// 北京时间的时区
+08:00
+0800
UTC+8
一个时区可以对应多个偏移量;多个时区也可以在一年中的某些时段共用同一个偏移量。
比如 America/Chicago、America/Denver、America/Belize 在一年中不同时间都会用到 -06:00 这个偏移。
Date 对象
JavaScript 没有日期这种原始类型,用 Date 对象及其方法处理日期和时间。Date 提供大量读取、设置和格式化的方法。
构造函数用法
创建一个真正的 Date 实例,要用 new Date(...)。当成普通函数调用时,返回的是字符串,不是 Date 对象。
Date 构造函数常见有四种基本形式;另外也可以传入另一个 Date 实例,拷贝同一时刻。
没有参数
如果没有提供参数,那么新创建的 Date 对象表示实例化时刻的日期和时间。
new Date() // Tue May 24 2022 22:24:58 GMT+0800 (中国标准时间)
Unix 时间戳
Unix 时间戳(Unix Time Stamp)是一个整数值,表示自 1970 年 1 月 1 日 00:00:00 UTC(the Unix epoch)以来的毫秒数,忽略了闰秒。
请注意: 很多系统、语言的「Unix 时间戳」只精确到秒,JavaScript 的 Date 用毫秒。秒级时间戳要先乘 1000。
注意: 传给构造函数当时间戳用时必须是 Number。数字字符串会走日期字符串解析,不会当成毫秒时间戳,常见结果是 Invalid Date。
new Date(1653402623348) // Tue May 24 2022 22:30:23 GMT+0800 (中国标准时间)
new Date('1653402623348') // Invalid Date
日期时间字符串
字符串参数会交给 Date.parse()。ECMAScript 只保证简化的 ISO 8601(Date Time String Format,如 YYYY-MM-DDTHH:mm:ss.sssZ)。RFC 2822 以及其他写法是否能解析,完全看引擎。
注意: 跨引擎不要依赖 new Date(字符串) 或 Date.parse 去猜非标准格式。需要稳定解析时自己拆字段,或用带时区的 ISO 字符串。
new Date('1970-01-02') // Fri Jan 02 1970 08:00:00 GMT+0800 (中国标准时间)
new Date('1970-1-02') // Fri Jan 02 1970 00:00:00 GMT+0800 (中国标准时间)
new Date('1970-01-02').toUTCString() // 'Fri, 02 Jan 1970 00:00:00 GMT'
new Date('1970-1-02').toUTCString() // 'Thu, 01 Jan 1970 16:00:00 GMT'
分别提供日期与时间的每一个成员
该传参形式至少需要提供年份与月份两个参数。如果只传一个参数,会被解析为时间戳。
new Date(2022) // Thu Jan 01 1970 08:00:02 GMT+0800 (中国标准时间)
new Date(2022, 0) // Sat Jan 01 2022 00:00:00 GMT+0800 (中国标准时间)
该传参形式的每一个成员都来自下列参数,有些参数可以省略,默认将使用最小可能值(日期为 1,其他为 0):
year: 表示年份的整数值。0 到 99 会被映射至 1900 年至 1999 年,其它值代表实际年份。
month: 表示月份的整数值,从 0-11(1 月-12 月)。
day(可选): 表示一个月中的第几天的整数值,从 1 开始,默认值为 1。
hours(可选): 表示一天中的小时数的整数值(24 小时制),默认值为 0。
minutes(可选): 表示一个小时内的分钟部分的整数值,默认值为 0。
seconds(可选): 表示一分钟内的秒部分的整数值,默认值为 0。
milliseconds(可选): 表示一秒内的毫秒部分的整数值,默认值为 0。
注意: 当 Date 构造函数传入多个参数时,如果数值大于合理范围(如月份为 13 或者分钟数为 70),相邻的数值会被调整。
new Date(2021, 13, 1) // Tue Feb 01 2022 00:00:00 GMT+0800 (中国标准时间)
new Date(2022, 1, 1) // Tue Feb 01 2022 00:00:00 GMT+0800 (中国标准时间)
new Date(2021, 13, 1, 8, 70) // Tue Feb 01 2022 09:10:00 GMT+0800 (中国标准时间)
new Date(2022, 13, 1, 9, 10) // Wed Feb 01 2023 09:10:00 GMT+0800 (中国标准时间)
注意: 当 Date 构造函数传入多个参数时,这些分量表示的是当地时间。
new Date(2019, 4, 30).getTime() // 1559145600000
new Date(2019, 4, 30) // Thu May 30 2019 00:00:00 GMT+0800 (中国标准时间)
new Date(Date.UTC(2019, 4, 30)).getTime() // 1559174400000
new Date(Date.UTC(2019, 4, 30)) // Thu May 30 2019 08:00:00 GMT+0800 (中国标准时间)
注意: Date 对象为跨平台提供了统一的行为。时间属性可以在不同的系统中表示相同的时刻;若使用本地时间方法,则反映当地的时间。
注意: Date 对象有处理 UTC 的方法,也有处理当地时间的方法。UTC 是现行世界时标准;代码里的 GMT 标签多半是历史叫法。当地时间是执行 JavaScript 的环境所设置的时区(浏览器是操作系统时区)。
常规函数调用
Date 可以当作普通函数来使用。
不管是否带有参数,返回的都是调用函数时刻的时间和日期的字符串:
Date() // 'Tue May 24 2022 22:59:56 GMT+0800 (中国标准时间)'
Date(2020, 1, 1) // 'Tue May 24 2022 23:01:41 GMT+0800 (中国标准时间)'
属性
prototype:允许给Date实例共享的方法往原型上加。length:7,对应构造函数最多可接受的参数个数(年、月、日、时、分、秒、毫秒)。
方法
Date.now()
Date.now() 返回自 1970 年 1 月 1 日 00:00:00 UTC 至今所经过的毫秒数。
注意: 为降低定时攻击和指纹追踪,部分浏览器会按隐私设置把 Date.now() 的精度取整。
Date.now() // 1653404883751
Date.parse()
Date.parse() 把日期字符串解析成从 1970 年 1 月 1 日 00:00:00 UTC 起的毫秒数。
规范只要求能解析 Date Time String Format(简化 ISO 8601)。RFC 2822 和其他格式是否支持、解析成 UTC 还是本地时间,都是实现定义。无法识别时返回 NaN。
Date.parse('2020-05-31 15:30:00')
跨引擎不要依赖 Date.parse 去解析非标准字符串,自己拆字段更稳妥。ES5 之前各引擎差异更大,现行引擎对 ISO 带 Z 的形式已经比较一致。
Date.UTC()
Date.UTC() 接受的参数同 Date 构造函数一样(month 从 0 起),按 UTC 解释这些分量,返回从 1970 年 1 月 1 日 00:00:00 UTC 到该时刻的毫秒数。
Date.UTC(year, month, day, hours, minutes, seconds, milliseconds)
Date.UTC(2022, 1, 1) // 1643673600000
实例属性
constructor:返回实例构造函数的引用,即Date构造函数。
实例方法
valueOf()
valueOf 方法返回实例对象距离 1970 年 1 月 1 日 00:00:00 UTC 对应的毫秒数,返回值与 getTime() 方法相同。
get 类方法(共 19 个)
| 方法 | 描述 |
|---|---|
| getYear()(已废弃) | 请使用 getFullYear() 方法代替。 |
| getTime() | 返回该时刻相对纪元的毫秒数。 |
| getTimezoneOffset() | 返回 UTC 减去本地时间的分钟差(东八区标准时间为 -480)。DST 期间会变。 |
new Date().getTimezoneOffset() // -480
// -480 表示 UTC 比当前时间少 480 分钟,即当前时区比 UTC 早 8 个小时。
根据本地时间,返回实例对象年、月、日、时、分、秒、毫秒、周的值(共 8 个):
| 方法 | 描述 |
|---|---|
| getFullYear() | 返回四位数字年份 |
| getMonth() | 返回月份(0-11) |
| getDate() | 返回一个月中的某一天(1-31) |
| getHours() | 返回小时(0-23) |
| getMinutes() | 返回分钟(0-59) |
| getSeconds() | 返回秒数(0-59) |
| getMilliseconds() | 返回毫秒(0-999) |
| getDay() | 返回一周中的某一天(0-6)。 0 代表星期日, 1 代表星期一,2 代表星期二, 依次类推。 |
根据世界时,返回实例对象年、月、日、时、分、秒、毫秒、周的值(共 8 个):
| 方法 | 描述 |
|---|---|
| getUTCFullYear() | 根据世界时,返回四位数的年份 |
| getUTCMonth() | 根据世界时,返回月份(0-11) |
| getUTCDate() | 根据世界时,返回月中的一天(1-31) |
| getUTCHours() | 根据世界时,返回小时(0-23) |
| getUTCMinutes() | 根据世界时,返回分钟(0-59) |
| getUTCSeconds() | 根据世界时,返回秒钟(0-59) |
| getUTCMilliseconds() | 根据世界时,返回毫秒(0-999) |
| getUTCDay() | 根据世界时,返回一周中的某一天(0-6)。 0 代表星期日, 1 代表星期一,2 代表星期二, 依次类推。 |
set 类方法(共 16 个)
| 方法 | 描述 |
|---|---|
| setTime() | 以毫秒设置 Date 对象。 |
| setYear()(已废弃) | 请使用 setFullYear() 方法代替。 |
设置实例对象的年、月、日、时、分、秒、毫秒的值(共 7 个):
注:没有 setDay 方法,因为星期几是计算出来的,而不是设置的。
| 方法 | 描述 |
|---|---|
| setFullYear() | 设置 Date 对象中的年份(四位数字) |
| setMonth() | 设置 Date 对象中月份(0-11) |
| setDate() | 设置 Date 对象中月的某一天(1-31) |
| setHours() | 设置 Date 对象中的小时(0-23) |
| setMinutes() | 设置 Date 对象中的分钟(0-59) |
| setSeconds() | 设置 Date 对象中的秒钟(0-59) |
| setMilliseconds() | 设置 Date 对象中的毫秒(0-999) |
根据世界时,设置实例对象的年、月、日、时、分、秒、毫秒的值(共 7 个):
| 方法 | 描述 |
|---|---|
| setUTCFullYear() | 根据世界时, 设置 Date 对象中的年份(四位数字) |
| setUTCMonth() | 根据世界时,设置 Date 对象中月份(0-11) |
| setUTCDate() | 根据世界时,设置 Date 对象中月份的一天(1-31)。 |
| setUTCHours() | 根据世界时,设置 Date 对象中的小时(0-23) |
| setUTCMinutes() | 根据世界时,设置 Date 对象中的分钟(0-59) |
| setUTCSeconds() | 根据世界时,设置 Date 对象中的秒钟(0-59) |
| setUTCMilliseconds() | 根据世界时,设置 Date 对象中的毫秒(0-999) |
const dateA = new Date('2020-05-31')
dateA // Sun May 31 2020 08:00:00 GMT+0800 (中国标准时间)
dateA.setFullYear(2030)
dateA // Fri May 31 2030 08:00:00 GMT+0800 (中国标准时间)
set 类方法和 get 类方法,可以结合使用,得到相对时间:
const dateA = new Date('2020-05-31')
dateA.setDate(dateA.getDate() + 1000) // 设置 1000 天以后
dateA // Sat Feb 25 2023 08:00:00 GMT+0800 (中国标准时间)
dateA.setFullYear(dateA.getFullYear() + 1) // 设置 1 年以后
dateA // Sun Feb 25 2024 08:00:00 GMT+0800 (中国标准时间)
to 类方法(共 12 个)
| 方法 | 描述 |
|---|---|
| toString() | 返回一个表示该日期对象的字符串 |
| toUTCString() | 把一个日期对象转换为一个以 UTC 时区计时的字符串。 |
| toISOString() | 把一个日期转换为符合 ISO 8601 扩展格式的字符串。注意:返回的总是 UTC 时区的时间。 |
| toJSON() | 默认用于 JSON 序列化 Date。有效日期时与 toISOString() 相同;Invalid Date 时 toJSON 返回 null,toISOString() 会抛错。 |
| toLocaleString() | 返回一个表示该日期对象的字符串,该字符串与系统设置的地区关联。 |
| toDateString() | 以人类易读(human-readable)的形式返回该日期对象日期部分的字符串。 |
| toLocaleDateString() | 返回一个表示该日期对象日期部分的字符串,该字符串格式与系统设置的地区关联。 |
| toTimeString() | 以人类易读格式返回日期对象时间部分的字符串。 |
| toLocaleTimeString() | 返回一个表示该日期对象时间部分的字符串,该字符串格式与系统设置的地区关联。 |
toUTCString()。 |
|
Object.prototype.toSource()。非标准,现行浏览器已移除。 |
|
const date = new Date(2020, 5, 30)
date.toString() // Sat May 30 2020 00:00:00 GMT+0800 (中国标准时间)
date.toUTCString() // Fri, 29 May 2020 16:00:00 GMT
date.toISOString() // 2020-05-29T16:00:00.000Z
date.toJSON() // 2020-05-29T16:00:00.000Z
date.toLocaleString() // 2020/5/30 上午12:00:00
date.toDateString() // Sat May 30 2020
date.toLocaleDateString() // 2020/5/30
date.toTimeString() // 00:00:00 GMT+0800 (中国标准时间)
date.toLocaleTimeString() // 上午12:00:00
toLocaleDateString()、toLocaleString()、toLocaleTimeString() 返回的字符串格式因不同语言而不同。
新增的参数 locales 和 options 使程序能够指定使用哪种语言格式化规则,允许定制该方法的表现。在旧版本浏览器中,locales 和 options 参数被忽略,使用的语言环境和返回的字符串格式是各自独立实现的。
const DateA = new Date(2020, 4, 30)
const options = {
timeZone: "UTC",
timeZoneName: "short",
hour12: true
}
DateA.toLocaleTimeString() // 上午12:00:00
DateA.toLocaleTimeString("zh-CN", options) // UTC 下午4:00:00
DateA.toLocaleTimeString("en-US", options) // 4:00:00 PM UTC
日期时间字符串格式
ECMAScript 只规定一种可移植格式:简化的 ISO 8601,即 Date Time String Format(YYYY-MM-DDTHH:mm:ss.sssZ)。不符合时,引擎可以自己决定怎么解析,也可以返回 NaN。
真正的 RFC 2822 是电子邮件日期语法(如 Fri, 21 Nov 1997 09:55:06 -0600),不是 YYYY/MM/DD。下面斜杠写法是 Chrome 等引擎的实现细节,不要当成规范。跨引擎应优先用带 Z 或 ±hh:mm 的 ISO 字符串。
斜杠日期(实现相关,常被误称为 RFC 2822)
邮件里的 RFC 2822 日期是 day month year 加时间。浏览器里常见的 2020/05/31 15:15:30 并不等于 RFC 2822,只是部分引擎愿意解析。
YYYY/MM/DD HH:MM:SS ±timezone(时区常用 4 位数字)
// 2020/05/31 15:15:30.500 +0800
YYYY = 4 位数年份
month = 2 位数月份或者 Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
day = 2 位数日期
hour = 小时的 2 位数
minute = 分钟的 2 位数
second = 秒的 2 位数
timezone = ±4 位数时区,即 ±[hh][mm]
以下为 Chrome 测试结果(lizhao,2020/05/31),其他引擎不必相同:
// 未指定时区,以本地时区解析
new Date('2020/05/31 15:15:30.500')
// 本地时区(东八区):Sun May 31 2020 15:15:30 GMT+0800 (中国标准时间)
new Date('2020/05/31 15:15:30.500Z')
// UTC 时间:Sun May 31 2020 23:15:30 GMT+0800 (中国标准时间)
new Date('2020/05/31 15:15:30.500 +0100')
// 东一区:Sun May 31 2020 22:15:30 GMT+0800 (中国标准时间)
// 毫秒可以是【:】或【.】分隔,标准中未说明
new Date('2020/05/31 15:15:30:500')
new Date('2020/05/31 15:15:30.500')
// Sun May 31 2020 15:15:30 GMT+0800 (中国标准时间)
Chrome 对这种斜杠格式相对宽松:
// 年、月、日的【/】前后可以有空格,时、分、秒不可以
new Date('2020 / 05 / 31 15:15:30:500') // Sun May 31 2020 15:15:30 GMT+0800 (中国标准时间)
new Date('2020 / 05 / 31 15 : 15 : 30: 500') // Invalid Date
// 英文月份按前缀匹配、不区分大小写(实现细节,不要依赖)
// 以下在当时 Chrome 都可以:Thu Feb 20 2020 00:00:00 GMT+0800 (中国标准时间)
new Date('2020/February/20')
new Date('2020/FEBRUARY/20')
new Date('2020/Februar/20')
new Date('2020/Februa/20')
new Date('2020/Febru/20')
new Date('2020/Febr/20')
new Date('2020/Feb/20')
// 以下得到 Invalid Date
new Date('2020/Fe/20') // Invalid Date
ISO 8601 与 ES Date Time String Format(推荐)
国际标准化组织的国际标准 ISO 8601 是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。第一版 ISO 8601:1988,第二版 2000,第三版 2004,后来还有 2019 版。JavaScript 并不实现完整 ISO 8601,只用其中一套简化形式。年由 4 位数组成,以公历公元 1 年为 0001 年,以公元前 1 年为 0000 年,公元前 2 年为 -0001 年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
注意:ISO 8601、Date Time String Format 里不能夹空格。
YYYY-MM-DDThh:mm:ss.sTZD
// 2020-05-31T15:15:30.500+08:00
YYYY = 四位数年份
MM = 两位数的月份(01 = 一月,依此类推)
DD = 两位数的日(01 到 31)
hh = 小时的两位数(00 到 23)(不允许上午、下午)
mm = 分钟的两位数(00 到 59)
ss = 秒的两位数字(00 到 59)
s = 一个或多个数字,代表秒的小数部分
TZD = 时区指示符,Z 或 ±[hh]:[mm]
以下为 Chrome 测试结果(lizhao,2020/05/31):
// 指定时区
new Date('2020-05-31T15:15:30.500Z') // Z 表示 UTC,即偏移 00:00
// 本地时间(东八区):Sun May 31 2020 23:15:30 GMT+0800 (中国标准时间)
new Date('2020-05-31T15:15:30.500+01:00') // +01:00 表示东一区
// 本地时间(东八区):Sun May 31 2020 22:15:30 GMT+0800 (中国标准时间)
// 不指定时区
new Date('2020-05-31') // 仅日期的 ISO 形式按 UTC 午夜:Sun May 31 2020 08:00:00 GMT+0800 (中国标准时间)
new Date('2020-5-31') // 非规范形式,Chrome 按本地时区:Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间)
new Date('2020-05-31T15:15:30.500') // 带时间、无时区:按本地时区:Sun May 31 2020 15:15:30 GMT+0800 (中国标准时间)
new Date('2020-5-31T15:15:30.500') // 月份不是两位:Invalid Date
- 日期与时间之间用【T】分隔,以指示时间元素的开始。标准是大写(T),Chrome 中小写(t)也能解析。
时区以 UTC(世界标准时间)表示,并带有特殊的 UTC 标记(「Z」)。
时区偏移量:
+hh:mm表示日期时间所用的本地时区比 UTC 提前 hh 小时和 mm 分钟;-hh:mm表示比 UTC 落后 hh 小时和 mm 分钟。
两者区别
跨引擎应使用带时区的 ISO、Date Time String Format(
2020-05-31T15:15:30.500Z或...+08:00)。斜杠日期、邮件风格 RFC 2822 都依赖实现。IE8、旧版 Safari 对 ISO 的坑是历史问题,现行 Safari、iOS 已能解析规范 ISO 形式。未指定时区且不是规范 ISO 日期时,很多引擎按本地时区解析。
现行规范仍规定:仅日期的 ISO 形式(
YYYY-MM-DD,无时间、无时区)按 UTC 午夜解析;带时间、无时区的按本地时间。// 仅日期 ISO:UTC 午夜 new Date('2020-05-31') // Sun May 31 2020 08:00:00 GMT+0800 (中国标准时间) // 以下全部以本地时区为标准进行解析(实现相关) new Date('2020/05/31') // 斜杠格式,不是 RFC 2822 // Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间) new Date('2020-5-31') // 非 ISO 8601 标准 // Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间) new Date("May 31, 2020") // 非标准格式 // Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间)邮件 RFC 2822 的数字时区常是
±[hh][mm](+0800);ISO 8601、ES 格式是±[hh]:[mm](+08:00)。// +0800 不是标准 ISO 8601、Date Time String Format。 // 当时 Chrome、Android 能解析,部分旧版 iOS 会出错。应写成 +08:00。 new Date("2018-11-12T00:00:00+0800")
其他非标准格式
new Date("May 31, 2020")
new Date("31 May, 2020")
// Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间)
new Date("Sun, 31 May 2020 00:00:00 GMT")
new Date("Sun May 31 2020 00:00:00 GMT")
new Date("Sun, 31 May 2020 00:00:00 +0000")
new Date("Sun, 31 May 2020 00:00:00 +00:00")
// Sun May 31 2020 08:00:00 GMT+0800 (中国标准时间)
// 注意月份与日期的顺序。以数字表示月份、日期时,月份在前。
new Date("05 31, 2020") // Sun May 31 2020 00:00:00 GMT+0800 (中国标准时间)
new Date("31 05, 2020") // Invalid Date
new Date("12 05, 2020") // Sat Dec 05 2020 00:00:00 GMT+0800 (中国标准时间)
夏令时问题
不同宿主环境下,new Date 创建的对象,返回的时间相差一个时区,导致获取的日期也相差一天:
new Date('1988-07-07T00:00:00.00') // Thu Jul 07 1988 00:00:00 GMT+0900 (中国夏令时间)
new Date(584290800000) // Fri Jul 08 1988 00:00:00 GMT+0900 (中国夏令时间)
// Safari、iOS
new Date(584290800000) // Thu Jul 07 1988 23:00:00 GMT+0800 (CST)
new Date(584290800000).getDate() // 7
// Android、Chrome
new Date(584290800000) // Fri Jul 08 1988 00:00:00 GMT+0900 (中国夏令时间)
new Date(584290800000).getDate() // 8
这是夏令时的原因,不同浏览器解析夏令时间的时间戳的返回结果有差异。
什么是夏令时?
夏时制、夏时令(Daylight Saving Time,DST),又称「日光节约时制」和「夏令时间」,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为「夏令时间」。一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前仍有不少国家和地区实行夏令时,具体名单会变。
夏令时实施期间,将时间调快一小时。
1986 年至 1991 年,中华人民共和国在全国范围实行了六年夏令时,每年从 4 月中旬的第一个星期日 2 时整(北京时间)到 9 月中旬第一个星期日的凌晨 2 时整(北京夏令时)。除 1986 年因是实行夏令时的第一年,从 5 月 4 日开始到 9 月 14 日结束外,其它年份均按规定的时段施行。1992 年 4 月 5 日后不再实行。
解决方法
跨环境比较时刻,优先存 UTC 毫秒时间戳,或带 Z、偏移的 ISO 字符串。不要把「无时区的本地日期字面量」当跨时区主键。
- 只关心日历日期、且环境时区固定时,也可以存
YYYY-MM-DD这类日期字符串。 - 若必须从落在 DST 缝上的时间戳取「当地日期」,不要假设所有引擎的 TZ 数据库一致;必要时用明确时区(如
Intl、Temporal)再取日。 - 旧办法里有人给时间戳加 1-2 小时再
getDate(),只能当权宜之计。
其他常见问题
日期的运算
类型自动转换时,Date 实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的日期字符串。
两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;进行加法运算时,返回的是两个字符串连接而成的新字符串。
let DateA = new Date(2020, 4, 30)
let DateB = new Date(2020, 4, 1)
console.log(DateA - DateB) // 2505600000
console.log(DateA + DateB) // Sat May 30 2020 00:00:00 GMT+0800 (中国标准时间)Fri May 01 2020 00:00:00 GMT+0800 (中国标准时间)
注:不能用 == 或 === 判断两个 Date 是否表示同一时刻。
const DateA = new Date(2020, 4, 30)
const DateB = new Date(2020, 4, 30)
// 对比的是引用是否指向同一个对象,不是时刻是否相同
DateA == DateB // false
DateA === DateB // false
// 可以在运算后,转化成时间戳再对比
+DateA == +DateB // true
+DateA === +DateB // true
getTime() 与 parse() 的区别
getTime() 返回该 Date 的实际毫秒时间戳。
Date.parse(字符串) 解析的是字符串。若把 Date 对象传进去,会先走 toString(),而 toString() 不含毫秒,再解析就会丢掉毫秒部分。直接解析带小数秒的 ISO 字符串时,现行引擎会保留毫秒,并不是「一律截到整秒」。
const DateA = new Date(2020, 4, 30, 8, 0, 0, 300)
DateA.getTime() // 1590796800300
Date.parse(DateA) // 1590796800000;先走 toString()(不含毫秒),再按实现解析
Date.parse('2020-05-30T00:00:00.300Z') // 现行引擎会带上 300 毫秒
生日换算年龄
function getAgeFromBirth(birth, d = new Date()) {
if (birth === undefined || birth === null || birth > d) {
return -1
}
const curDate = new Date(d)
const birthDate = new Date(birth)
let age = curDate.getFullYear() - birthDate.getFullYear()
if (curDate.getMonth() < birthDate.getMonth() || (curDate.getMonth() === birthDate.getMonth() && curDate.getDate() < birthDate.getDate())) {
age--
}
return age
}
时间字符串换算
function parseTime (time, cFormat = '{y}-{m}-{d} {h}:{i}:{s}') {
if (time === undefined || time === null) {
return null
}
let date
if (typeof time === 'object') {
date = time
} else {
if (('' + time).length === 10){
time = parseInt(time) * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const timeStr = cFormat.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return timeStr
}