人人都会AI编程

10.4 日期对象:Date 时间处理、格式化、时区

更新时间:2026-07-11

日期和时间是业务开发中最常见也最容易踩坑的数据类型之一。JavaScript 内置的 Date 对象提供了基本的创建、解析和计算能力,但它的设计早于现代 JavaScript,留有不少反直觉的“历史遗迹”。理解 Date 的运作方式以及它的局限性,是写好时间相关代码的基础。

10.4.1 创建日期对象

Date 是构造函数,通过 new Date() 创建实例。创建时如果不传参数,会得到当前时刻的日期对象。

const now = new Date();
console.log(now); // 当前时间,例如:Thu Mar 14 2024 10:30:00 GMT+0800(中国标准时间)

Date 支持多种参数形式,但强烈建议只使用下列几种,避免因字符串格式差异导致跨浏览器不一致

  • 时间戳(毫秒数):自 1970-01-01 00:00:00 UTC 至指定时刻的毫秒数。
  new Date(1700000000000); // 传入数字会被视为时间戳
  
  • 日期数字参数new Date(year, monthIndex, day?, hours?, minutes?, seconds?, milliseconds?)

注意:月份从 0 开始(0 表示一月,11 表示十二月)。

  new Date(2024, 0, 1);  // 2024年1月1日
  
  • ISO 8601 格式字符串(推荐):"YYYY-MM-DDTHH:mm:ss.sssZ"

该格式能够被所有主流浏览器正确解析,且明确表达 UTC 时间。

  new Date("2024-01-01T00:00:00.000Z");
  
  • Date 对象拷贝new Date(otherDate)

不推荐使用非标准格式的字符串,例如 "2024-01-01""01/01/2024",它们在旧引擎中可能被解析为 UTC 时间或本地时间,行为不可预测。

10.4.2 日期组件获取与设置

每个 Date 实例提供了一对 get / set 方法,用于读写日期组件。它们分为两组:本地时间UTC 时间

| 组件 | 获取(本地) | 设置(本地) | 获取(UTC) | 设置(UTC) |
| -------- | ---------------- | ---------------- | ------------------- | ------------------- |
| 年 | getFullYear() | setFullYear() | getUTCFullYear() | setUTCFullYear() |
| 月 | getMonth() | setMonth() | getUTCMonth() | setUTCMonth() |
| 日 | getDate() | setDate() | getUTCDate() | setUTCDate() |
| 星期 | getDay() | - | getUTCDay() | - |
| 时 | getHours() | setHours() | getUTCHours() | setUTCHours() |
| 分 | getMinutes() | setMinutes() | getUTCMinutes() | setUTCMinutes() |
| 秒 | getSeconds() | setSeconds() | getUTCSeconds() | setUTCSeconds() |
| 毫秒 | getMilliseconds()| setMilliseconds()| getUTCMilliseconds()| setUTCMilliseconds()|
| 时间戳 | getTime() | setTime() | - | - |

关键要点

  • getDay() 返回 0(星期日)到 6(星期六),没有 setDay() 方法。
  • getTime() 返回自 1970-01-01 UTC 至今的毫秒数(时间戳),它不受时区影响。
  • setFullYear(year, month?, date?) 可以一次性设置年月日,非常方便。
const d = new Date(2024, 2, 14); // 2024年3月14日
d.setFullYear(2025);
console.log(d.getFullYear()); // 2025
console.log(d.getMonth());    // 2   (仍是三月)
console.log(d.getTime());     // 时间戳已更新

10.4.3 时间戳与日期运算

时间戳(毫秒整数)是计算时间差的最可靠手段。因为日期是不可变的,直接对日期加减操作需要走以下模式:

const d = new Date();
d.setMinutes(d.getMinutes() + 30); // 增加30分钟

如果要计算两个日期之间的天数差,应使用时间戳:

const start = new Date('2024-01-01T00:00:00Z');
const end   = new Date('2024-01-10T00:00:00Z');
const diffMs = end.getTime() - start.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
console.log(diffDays); // 9

10.4.4 格式化日期为字符串

JavaScript 的 Date 提供了若干内置格式化方法,但这些方法的输出格式不可定制因浏览器而异,主要用于快速调试,不应在生产环境中依赖它们呈现给最终用户。

| 方法 | 输出示例(Chrome) | 说明 |
| ----------------- | ------------------------------------------- | ---------------------- |
| toString() | "Thu Mar 14 2024 10:30:00 GMT+0800" | 完整日期时间字符串 |
| toDateString() | "Thu Mar 14 2024" | 只有日期 |
| toTimeString() | "10:30:00 GMT+0800" | 只有时间 + 时区 |
| toISOString() | "2024-03-14T02:30:00.000Z" | ISO 8601,始终 UTC |
| toUTCString() | "Thu, 14 Mar 2024 02:30:00 GMT" | UTC 时间 |
| toLocaleString()| "2024/3/14 10:30:00" (中文环境) | 本地化,可配置选项 |

自定义格式化:手动拼接 get* 方法并补零是最简单的方式。

function formatDate(date) {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  const h = String(date.getHours()).padStart(2, '0');
  const mi = String(date.getMinutes()).padStart(2, '0');
  const s = String(date.getSeconds()).padStart(2, '0');
  return `${y}-${m}-${d} ${h}:${mi}:${s}`;
}

更为推荐:在工程中直接使用轻量日期库(如 dayjsdate-fns),它们提供了可定制、不可变、时区感知的格式化函数,比手写更可靠。

10.4.5 时区处理

Date 对象内部始终基于 UTC 时间存储(时间戳的本质就是 UTC 毫秒偏移量),但它的所有本地方法(getHours()toString() 等)都会根据运行环境(操作系统/浏览器)的时区进行转换。这意味着:

  • 同一段代码在中国的电脑上执行,显示的本地时间是北京时间;
  • 在美国的电脑上执行,显示的是美国时区时间。

这种设计在处理跨时区应用时会带来问题。

常见需求与解法

  1. 直接显示 UTC 时间:使用 getUTC* 方法和 toISOString()
  2. 将本地时间转换成另一个时区的展示Date 本身不提供该能力。需要自己计算时区偏移,或使用 Intl.DateTimeFormat 配合 timeZone 选项,或者直接用 dayjsutctimezone 插件。
// 使用 Intl 格式化纽约时间
const formatter = new Intl.DateTimeFormat('zh-CN', {
  timeZone: 'America/New_York',
  year: 'numeric', month: '2-digit', day: '2-digit',
  hour: '2-digit', minute: '2-digit', second: '2-digit'
});
console.log(formatter.format(new Date()));
  1. 解析带时区的字符串:ISO 扩展格式 2024-01-01T12:00:00+08:00 会正确解析并进行偏移,内部统一为 UTC。
  1. 获取 UTC 偏移getTimezoneOffset() 返回当前环境与 UTC 的分钟差(正值表示落后于 UTC,负值表示领先 UTC,这个正负号与传统认知相反,需留意)。

10.4.6 常见陷阱与最佳实践

  • 月份从 0 开始:0 代表一月,11 代表十二月。永远记得在设置或从用户输入构建时做 ±1 处理。
  • new Date(str) 的浏览器差异:避免使用 "YYYY-MM-DD" 这种只含日期的简化写法,它在 ES5 之前被视为 UTC 时间,ES6 后部分引擎视为本地时间。统一使用 "YYYY-MM-DDTHH:mm:ssZ" 这种完整 UTC 格式。
  • Date 对象是可变的:对一个已有的 Date 调用 set* 会改变它本身,可能带来副作用。如有需要,先复制一份:const copied = new Date(original.getTime())
  • 用时间戳比较:直接用 date1 > date2 会发生隐式转换为数字(比较时间戳),通常可以正常工作,但显式用 getTime() 更清晰。
  • 不做重型日期运算Date 不处理闰秒、夏令时转换规则等复杂场景。对于日历计算、调度系统等需求,使用专业库或服务端统一处理更稳妥。

掌握了 Date 的基本能力以及其局限,便能避开大部分时间相关的坑。当业务复杂度上升时,引入 dayjsdate-fns 会是更明智的选择。