为什么日期时间是前端开发中的痛点
日期时间处理是前端开发中最常见也是最麻烦的问题之一。JavaScript 内置的 Date 对象设计缺陷很多——月份从 0 开始、时区处理混乱、API 不一致。理解这些问题,才能在项目中正确处理日期时间。
这篇文章解析 JavaScript 日期时间处理的机制,以及现代 Temporal API 如何解决这些问题。
一、Date 对象基础
1.1 创建 Date 对象
// 当前时间
const now = new Date();
console.log(now.toISOString()); // '2024-04-08T10:00:00.000Z'
// 指定时间戳
const timestamp = new Date(1712570400000);
console.log(timestamp.toISOString()); // '2024-04-08T10:00:00.000Z'
// 指定日期字符串(不推荐)
const fromString = new Date('2024-04-08');
console.log(fromString.toISOString()); // '2024-04-08T00:00:00.000Z'
// 指定年月日时分秒
const fromParts = new Date(2024, 3, 8, 10, 0, 0);
// 注意:月份从 0 开始,所以 3 = 四月
console.log(fromParts.toISOString()); // '2024-04-08T02:00:00.000Z'(取决于时区)
1.2 Date 的月份索引
// 月份从 0 开始!
const date = new Date(2024, 0, 1); // 2024 年 1 月 1 日
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
console.log(date.getMonth()); // 0
console.log(months[date.getMonth()]); // 'Jan'
1.3 Date.now vs Date.parse
// Date.now — 返回当前时间戳(推荐)
console.log(Date.now()); // 1712570400000
// Date.parse — 解析日期字符串
console.log(Date.parse('2024-04-08')); // 1712534400000(UTC 午夜)
console.log(Date.parse('2024/04/08')); // 1712534400000
// 不推荐:Date 构造函数直接解析
console.log(new Date('2024-04-08').getTime()); // 同 Date.parse
二、时区问题
2.1 本地时间 vs UTC
const date = new Date('2024-04-08T10:00:00Z');
console.log(date.toISOString()); // '2024-04-08T10:00:00.000Z'
console.log(date.toUTCString()); // 'Mon, 08 Apr 2024 10:00:00 GMT'
console.log(date.toString()); // 'Mon Apr 08 2024 18:00:00 GMT+0800 (中国标准时间)'
console.log(date.getHours()); // 18(本地时区)
console.log(date.getUTCHours()); // 10(UTC)
2.2 时区偏移
// 获取时区偏移(分钟)
const offset = new Date().getTimezoneOffset();
console.log(offset); // -480(中国 GMT+8,返回 -480 分钟)
// 转换为指定时区
function toTimezone(date, timezone) {
return new Date(date.toLocaleString('en-US', { timeZone: timezone }));
}
console.log(toTimezone(new Date(), 'America/New_York').toISOString());
console.log(toTimezone(new Date(), 'Asia/Shanghai').toISOString());
2.3 常见时区陷阱
// 陷阱:日期字符串解析是本地时间,不是 UTC
new Date('2024-04-08'); // 本地时区的 2024-04-08 00:00:00
// 正确做法:使用 ISO 格式
new Date('2024-04-08T00:00:00Z'); // 明确的 UTC
// 或使用日期部分(会自动解析为 UTC)
new Date(Date.UTC(2024, 3, 8));
三、Intl.DateTimeFormat
3.1 基本格式化
const date = new Date('2024-04-08T10:00:00Z');
// 完整本地化
console.log(new Intl.DateTimeFormat('zh-CN').format(date));
// '2024/4/8'
// 指定格式
console.log(new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date));
// 'Monday, April 8, 2024'
3.2 格式化选项
const date = new Date('2024-04-08T10:00:00Z');
const options = {
year: 'numeric', // '2024'
month: '2-digit', // '04'
day: '2-digit', // '08'
hour: '2-digit', // '02' (本地时区)
minute: '2-digit', // '00'
second: '2-digit', // '00'
timeZoneName: 'short', // 'GMT+8'
hour12: false // 24 小时制
};
console.log(new Intl.DateTimeFormat('zh-CN', options).format(date));
// '2024年04月08日 18:00:00 GMT+8'
四、日期计算
4.1 日期加减
const date = new Date('2024-04-08T10:00:00Z');
// 加一天
const tomorrow = new Date(date);
tomorrow.setDate(tomorrow.getDate() + 1);
console.log(tomorrow.toISOString()); // '2024-04-09T10:00:00.000Z'
// 减一个月
const lastMonth = new Date(date);
lastMonth.setMonth(lastMonth.getMonth() - 1);
console.log(lastMonth.toISOString()); // '2024-03-08T10:00:00.000Z'
4.2 日期差值
const date1 = new Date('2024-04-08');
const date2 = new Date('2024-04-15');
// 毫秒差
const diffMs = date2 - date1;
console.log(diffMs / (1000 * 60 * 60 * 24)); // 7 天
// 使用 diff 方法
function daysBetween(a, b) {
const diffMs = Math.abs(b - a);
return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
}
console.log(daysBetween(date1, date2)); // 7
4.3 日期边界处理
// 获取月份第一天
function startOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth(), 1);
}
// 获取月份最后一天
function endOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}
console.log(startOfMonth(new Date('2024-04-08')).toISOString());
// '2024-04-01T00:00:00.000Z'
console.log(endOfMonth(new Date('2024-04-08')).toISOString());
// '2024-04-30T00:00:00.000Z'
五、Temporal API(ES2022+)
5.1 为什么需要 Temporal
Date 的问题太多,TC39 正在开发 Temporal API 作为替代:
// Temporal 目前是 Stage 3,可以使用 polyfill
// import Temporal from '@js-temporal/polyfill';
// 获取当前时间(Instant)
const now = Temporal.Now.instant();
console.log(now.toString()); // '2024-04-08T10:00:00.000000000Z'
// 创建日期(PlainDate)
const date = Temporal.PlainDate.from({ year: 2024, month: 4, day: 8 });
console.log(date.toString()); // '2024-04-08'
// 带时区的时间(ZonedDateTime)
const zoned = Temporal.ZonedDateTime.from({
year: 2024,
month: 4,
day: 8,
hour: 10,
timeZone: 'Asia/Shanghai'
});
console.log(zoned.toString()); // '2024-04-08T10:00:00+08:00[Asia/Shanghai]'