日期和时间是业务开发中最常见也最容易踩坑的数据类型之一。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}`;
}
更为推荐:在工程中直接使用轻量日期库(如 dayjs、date-fns),它们提供了可定制、不可变、时区感知的格式化函数,比手写更可靠。
10.4.5 时区处理
Date 对象内部始终基于 UTC 时间存储(时间戳的本质就是 UTC 毫秒偏移量),但它的所有本地方法(getHours()、toString() 等)都会根据运行环境(操作系统/浏览器)的时区进行转换。这意味着:
- 同一段代码在中国的电脑上执行,显示的本地时间是北京时间;
- 在美国的电脑上执行,显示的是美国时区时间。
这种设计在处理跨时区应用时会带来问题。
常见需求与解法:
- 直接显示 UTC 时间:使用
getUTC*方法和toISOString()。 - 将本地时间转换成另一个时区的展示:
Date本身不提供该能力。需要自己计算时区偏移,或使用Intl.DateTimeFormat配合timeZone选项,或者直接用dayjs的utc和timezone插件。
// 使用 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()));
- 解析带时区的字符串:ISO 扩展格式
2024-01-01T12:00:00+08:00会正确解析并进行偏移,内部统一为 UTC。
- 获取 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 的基本能力以及其局限,便能避开大部分时间相关的坑。当业务复杂度上升时,引入 dayjs 或 date-fns 会是更明智的选择。