Date 对象表示一个时间点
JavaScript 中的 Date 表示一个单独的时刻:在内部,它只是自 1970 年 1 月 1 日 UTC(即“Unix 纪元”)以来的毫秒数。其余的一切,包括年、月、日、时区和格式,都是叠加在这个数字之上的视图。
now.getTime() 是原始的毫秒数。Date 做的所有事情,比较、加天数、格式化,归根结底都是操作这个数字,然后再重新解读它。
把这个模型记在脑子里。Date 并不是“巴黎的 3 月 14 日”,而是一个全球统一的时刻:从不同的时区去看,它可以显示为巴黎的 3 月 14 日,也可以显示为洛杉矶的 3 月 13 日。
创建日期
构造 Date 主要有四种方式:
有两点需要注意:
- 按各部分构造时,月份从 0 开始计数。
2表示三月,一月是0。这是差一错误的常见来源。好在整个 API 里的月份都是从 0 开始的,至少它自身是一致的。 new Date("2026-03-14")(不带时间)会被解析为 UTC 午夜。new Date("2026-03-14T09:30")(不带Z)会被解析为本地时间。这种不对称是一个经典陷阱。
如果你只想要表示“此刻”的数字,优先使用 Date.now(),它省去了创建对象的开销:
测量耗时、处理超时,以及任何不需要日历运算的场景,Date.now() 都是合适的工具。
读取日期的各个部分
拿到一个 Date 后,可以用 getter 取出各个组成部分。每个 getter 都有两种版本:本地时间和 UTC。
本地版本取决于运行代码的那台机器。如果你要在不同用户和服务器之间存储或比较日期,就要明确选择 UTC,否则你会追查一些莫名其妙的 bug。经验法则:写入数据库或日志的内容用 UTC getter;要展示给人看的内容用本地 getter。
不要使用 getYear()。它是一个遗留方法,返回 year - 1900,只是为了兼容而存在。始终使用 getFullYear()。
为人类格式化日期
只要你在意输出结果,就不要用 date.toString(),它的输出取决于语言区域和引擎。有两个格式化工具值得了解。
如果需要标准的机器可读字符串,就用 toISOString():
打印日志、存入 JSON 或通过网络发送时,都应该使用这种格式。它始终是 UTC,始终没有歧义。
如果是给人看的字符串,就用 Intl.DateTimeFormat,或者封装了它的 toLocale* 系列方法:
Intl.DateTimeFormat 能处理语言区域、时区以及你想要的各种字段组合。在手写 ${year}-${month}-${day} 这样的格式之前,先考虑用它:这种手动拼接字符串的写法,正是月份差一错误的温床。
比较日期
两个表示同一时刻的 Date 对象并不 === 相等:=== 检查的是对象是否为同一个,而不是值。应该比较它们的时间戳:
如果要比较先后,比较运算符可以直接使用,因为它们会把日期强制转换为数字:
两个日期相减,得到的是相差的毫秒数。除以 1000 * 60 * 60 * 24 就得到天数。第一次先把这个常量完整写出来,过一段时间你就能一眼认出 86_400_000。
日期运算
JavaScript 没有 addDays 方法。惯用的做法是使用 setDate、setMonth 等方法:它们接受超出范围的值,并能正确地进位:
有两点值得强调:
new Date(date)会复制日期。setDate会修改原对象,所以一定要先复制,否则你会改掉调用方的值。- 对一个有 31 天的月份调用
setDate(35),会自动进位到下个月。setMonth(14)也一样,它会把年份往后推。这让日期运算远比看上去轻松。
对于复杂的需求,比如工作日、周期性事件、需要考虑月份的时长,就用一个库(date-fns、Luxon,或者即将推出的 Temporal API)。除了“加几天”这种简单运算之外,自己实现日历计算就是一个泥潭。
认清时区的现实
时区是日期 bug 最大的单一来源。值得牢记的规则:
Date存储的是一个 UTC 时刻。只有在读取各部分或格式化时,才会应用时区。getHours()、getDate()等方法使用的时区,是运行代码的那台机器的本地时区。服务器和浏览器的时区常常不一致。new Date("2026-03-14")(只有日期)解析为 UTC。new Date("2026-03-14T00:00")(有时间、没有时区)解析为本地时间。new Date(2026, 2, 14)(各部分)是本地时间。
如果显示时需要指定时区,就把 timeZone 传给 Intl.DateTimeFormat:
同一个时刻,两种视图。Date 对象本身并没有改变。
一个可运行的小例子
把这些组合起来:一个格式化“多久之前”的函数:
输入时间戳,输出人类可读的字符串。现实中九成的日期代码都是这个模式:两个时刻相减,除以一个单位,取整,再格式化。
本篇要点
Date是一个 UTC 时刻。时区只在读取或格式化时出现。- 时间戳用
Date.now(),日历相关的工作用new Date()。 - 存储和日志用
toISOString(),给用户看用Intl.DateTimeFormat。 - 用
getTime()或</>比较,绝不要用===。 - 月份从 0 开始计数。小心只有日期的字符串的解析陷阱。
- 严肃的日期运算,交给库来做。
下一步:URL 和查询字符串
日期经常出现在 URL 中:按日期范围筛选,或者把时间戳作为查询参数传递。手动解析和构建 URL 和手动格式化日期一样容易出错,而标准库提供了一个 URL 对象,可以干净利落地处理它。下一篇就讲这个。
常见问题
JavaScript 中如何获取当前日期?
不带参数调用 new Date()。它返回一个 Date 对象,表示构造函数运行的那一刻。如果你只需要一个数字时间戳(自 1970 年以来的毫秒数),就用 Date.now():它更快,也不会创建完整的对象。
JavaScript 中如何比较两个日期?
比较它们的时间戳,而不是 Date 对象本身。a.getTime() < b.getTime() 可行,a < b 也可行,因为 < 会把日期强制转换为数字。但 a === b 不行:=== 检查的是对象是否为同一个,所以两个表示同一时刻的 Date 对象永远不会严格相等。
JavaScript 中如何格式化日期?
凡是给用户看的内容,都用 Intl.DateTimeFormat 或 date.toLocaleDateString(),它们能正确处理语言区域和时区。对于机器可读的输出,date.toISOString() 会给出类似 2026-03-14T09:30:00.000Z 的标准字符串。不要用 date.toString() 来存储,它的格式依赖于语言区域。
为什么我的 JavaScript 日期差了一天?
通常是时区问题。new Date('2026-03-14') 会被解析为 UTC 午夜,但 date.getDate() 返回的是本地时区的日期,可能是前一天。用 getUTCDate() 获取 UTC 日期,或者用 new Date(year, month, day) 构造日期,它从一开始就使用本地时间。