Node.js 26 深度实战:Temporal API 默认启用——从 Date 对象的痛点到现代日期时间处理的全链路解析
一、背景:JavaScript 日期处理的至暗时刻
1.1 Date 对象的历史包袱
JavaScript 的 Date 对象自 1995 年诞生以来,已经服役了整整 31 年。这个基于 Java 1.0 遗留设计的对象,在漫长的岁月中积累了无数问题,成为前端开发者最头疼的 API 之一。
让我们先看看 Date 对象的经典坑点:
// 坑点 1:月份从 0 开始
const date = new Date(2026, 4, 7); // 2026年5月7日,不是4月!
console.log(date.getMonth()); // 输出 4,表示第5个月
// 坑点 2:日期解析的不确定性
const d1 = new Date('2026-05-07'); // ISO 格式,解析为 UTC 时间
const d2 = new Date('2026/05/07'); // 非标准格式,不同浏览器解析结果不同
const d3 = new Date('05-07-2026'); // 美式格式,可能解析失败
// 坑点 3:可变对象导致的意外修改
const original = new Date('2026-05-07');
const copy = original;
copy.setMonth(6); // 修改 copy
console.log(original.getMonth()); // original 也被修改了!输出 6
// 坑点 4:时区处理的混乱
const utc = new Date('2026-05-07T00:00:00Z');
const local = new Date('2026-05-07T00:00:00');
// 两者在不同时区下的行为完全不同
1.2 开发者的真实痛点
根据 Stack Overflow 的数据,与 JavaScript 日期相关的问题累计超过 50 万次浏览,排名前 10 的痛点包括:
- 时区转换错误:服务器时间与客户端时间不一致导致的 bug
- 日期格式化:需要引入 moment.js、date-fns 等第三方库
- 日期计算:计算两个日期之间的天数需要手动处理毫秒
- 跨浏览器兼容:不同浏览器对日期字符串的解析规则不同
- 夏令时处理:
Date对象完全无法正确处理夏令时
// 典型的日期计算痛苦示例
function getDaysBetween(date1, date2) {
const oneDay = 24 * 60 * 60 * 1000; // 毫秒数
const diffTime = Math.abs(date2 - date1);
const diffDays = Math.ceil(diffTime / oneDay);
return diffDays;
}
// 这个函数在跨越夏令时边界时会出错!
// 因为夏令时切换的那一天不是 24 小时
1.3 第三方库的兴起与问题
为了解决 Date 的痛点,社区诞生了大量日期处理库:
| 库名 | 体积 | 特点 | 问题 |
|---|---|---|---|
| moment.js | ~70KB | 功能全面 | 已停止维护,体积大,可变对象 |
| date-fns | ~13KB | 函数式,Tree-shaking | API 风格不统一 |
| dayjs | ~2KB | 轻量,moment 风格 | 需要插件扩展功能 |
| luxon | ~20KB | 不可变,时区支持 | API 与 moment 差异大 |
这些库虽然解决了部分问题,但带来了新的负担:
- 增加打包体积
- 需要学习和维护额外的 API
- 与原生
Date对象互操作时容易出错 - 无法从根本上解决 JavaScript 日期处理的底层问题
二、Temporal API:JavaScript 日期处理的革命性突破
2.1 Temporal 的设计哲学
Temporal API 是 TC39 提案(已进入 Stage 3),旨在从根本上重新设计 JavaScript 的日期时间处理。其核心设计哲学:
- 不可变性:所有 Temporal 对象都是不可变的,避免意外修改
- 类型明确:区分"日期"、"时间"、"日期时间"、"时区日期时间"等不同概念
- 时区优先:内置完整的时区支持,包括夏令时处理
- 日历系统:支持多种日历系统(公历、农历、伊斯兰历等)
- 无歧义解析:统一的 ISO 8601 解析规则
2.2 Temporal 类型体系全景图
Temporal 提供了 8 种核心类型,每种类型对应特定的使用场景:
Temporal 类型体系
├── Instant // 绝对时间点(类似 Unix 时间戳)
├── PlainDate // 纯日期(年月日)
├── PlainTime // 纯时间(时分秒)
├── PlainDateTime // 日期 + 时间(无时区)
├── ZonedDateTime // 日期 + 时间 + 时区
├── PlainYearMonth // 年月(用于信用卡过期等)
├── PlainMonthDay // 月日(用于生日、纪念日)
└── Duration // 时间段(时长)
2.3 Node.js 26 的里程碑意义
2026 年 5 月 5 日,Node.js 团队发布了 Node.js 26.0.0 版本,最重要的变化是 默认启用 Temporal API。
这意味着:
- 无需任何 flag 或 polyfill,直接使用
Temporal全局对象 - V8 引擎更新至 14.6.202.33,性能大幅提升
- 2026 年 10 月将进入 LTS(长期支持)阶段,成为生产环境推荐版本
// Node.js 26 中直接使用 Temporal
const now = Temporal.Now.plainDateTimeISO();
console.log(now.toString()); // 2026-05-07T13:01:23.456789123
// 不再需要任何配置或 polyfill!
三、Temporal 核心类型深度解析
3.1 Temporal.Instant:绝对时间点
Instant 表示时间轴上的一个绝对点,类似于 Unix 时间戳,但精度更高(纳秒级)。
// 创建 Instant
const now = Temporal.Now.instant();
const fromEpoch = Temporal.Instant.fromEpochSeconds(1715059200);
const fromString = Temporal.Instant.from('2026-05-07T05:01:00Z');
// 精度对比
console.log(now.epochMilliseconds); // 毫秒级时间戳
console.log(now.epochNanoseconds); // 纳秒级时间戳(BigInt)
// 时间运算
const later = now.add({ hours: 2, minutes: 30 });
const duration = later.since(now);
console.log(duration.toString()); // PT2H30M
// 转换为不同时区
const tokyo = now.toZonedDateTimeISO('Asia/Tokyo');
const newYork = now.toZonedDateTimeISO('America/New_York');
console.log(tokyo.toString()); // 2026-05-07T14:01:00+09:00[Asia/Tokyo]
console.log(newYork.toString()); // 2026-05-07T01:01:00-04:00[America/New_York]
3.2 Temporal.PlainDate:纯日期处理
PlainDate 表示不包含时间的日期,非常适合处理生日、截止日期等场景。
// 创建方式对比
const date1 = new Temporal.PlainDate(2026, 5, 7); // 构造函数
const date2 = Temporal.PlainDate.from('2026-05-07'); // ISO 字符串
const date3 = Temporal.PlainDate.from({ year: 2026, month: 5, day: 7 }); // 对象
const today = Temporal.Now.plainDateISO(); // 当前日期
// 注意:月份从 1 开始!不再有 Date 的 0-indexed 陷阱
console.log(date1.month); // 5,不是 4
// 日期运算
const nextWeek = today.add({ days: 7 });
const nextMonth = today.add({ months: 1 });
const lastYear = today.subtract({ years: 1 });
// 日期比较
const deadline = Temporal.PlainDate.from('2026-12-31');
if (Temporal.PlainDate.compare(today, deadline) < 0) {
console.log('还没到截止日期');
}
// 获取日期属性
console.log(today.year); // 2026
console.log(today.month); // 5
console.log(today.day); // 7
console.log(today.dayOfWeek); // 4(星期四,1=周一)
console.log(today.dayOfYear); // 127(一年中的第几天)
console.log(today.daysInMonth); // 31
console.log(today.inLeapYear); // false(2026 不是闰年)
3.3 Temporal.PlainTime:纯时间处理
PlainTime 表示不包含日期的时间,适合处理每日营业时间、闹钟等场景。
// 创建时间
const time1 = new Temporal.PlainTime(14, 30, 0); // 14:30:00
const time2 = Temporal.PlainTime.from('14:30:00');
const now = Temporal.Now.plainTimeISO();
// 时间运算
const later = time1.add({ hours: 2, minutes: 15 });
console.log(later.toString()); // 16:45:00
// 跨午夜计算
const midnight = Temporal.PlainTime.from('23:30:00');
const result = midnight.add({ hours: 2 });
console.log(result.toString()); // 01:30:00(自动循环)
// 时间比较
const workStart = Temporal.PlainTime.from('09:00:00');
const workEnd = Temporal.PlainTime.from('18:00:00');
if (Temporal.PlainTime.compare(now, workStart) >= 0 &&
Temporal.PlainTime.compare(now, workEnd) < 0) {
console.log('工作时间');
}
3.4 Temporal.PlainDateTime:日期时间组合
PlainDateTime 表示日期和时间的组合,但不包含时区信息。
// 创建日期时间
const dt1 = new Temporal.PlainDateTime(2026, 5, 7, 14, 30, 0);
const dt2 = Temporal.PlainDateTime.from('2026-05-07T14:30:00');
const dt3 = Temporal.PlainDateTime.from({
year: 2026,
month: 5,
day: 7,
hour: 14,
minute: 30
});
// 日期时间运算
const meeting = Temporal.PlainDateTime.from('2026-05-07T14:00:00');
const meetingEnd = meeting.add({ hours: 1, minutes: 30 });
console.log(meetingEnd.toString()); // 2026-05-07T15:30:00
// 计算两个时间点之间的时长
const start = Temporal.PlainDateTime.from('2026-05-07T09:00:00');
const end = Temporal.PlainDateTime.from('2026-05-07T18:00:00');
const workHours = end.since(start);
console.log(workHours.toString()); // PT9H
// 修改部分字段
const newDate = dt1.with({ month: 12, day: 25 });
console.log(newDate.toString()); // 2026-12-25T14:30:00
3.5 Temporal.ZonedDateTime:时区感知的日期时间
ZonedDateTime 是最强大的类型,包含日期、时间和时区信息,能正确处理夏令时。
// 创建时区日期时间
const zdt1 = Temporal.ZonedDateTime.from('2026-05-07T14:30:00+08:00[Asia/Shanghai]');
const zdt2 = Temporal.Now.zonedDateTimeISO('America/New_York');
// 时区转换
const tokyo = zdt1.withTimeZone('Asia/Tokyo');
const london = zdt1.withTimeZone('Europe/London');
console.log(tokyo.toString()); // 2026-05-07T15:30:00+09:00[Asia/Tokyo]
console.log(london.toString()); // 2026-05-07T07:30:00+01:00[Europe/London]
// 夏令时处理示例
// 美国夏令时:2026年3月8日 2:00 -> 3:00(时钟拨快1小时)
const beforeDST = Temporal.ZonedDateTime.from('2026-03-08T01:30:00[America/New_York]');
const afterDST = beforeDST.add({ hours: 1 });
console.log(beforeDST.toString()); // 2026-03-08T01:30:00-05:00[America/New_York]
console.log(afterDST.toString()); // 2026-03-08T03:30:00-04:00[America/New_York]
// 注意:时区偏移从 -05:00 变成了 -04:00
// 跨时区会议安排
const meeting = Temporal.ZonedDateTime.from('2026-05-10T10:00:00[America/Los_Angeles]');
const forTokyo = meeting.withTimeZone('Asia/Tokyo');
const forLondon = meeting.withTimeZone('Europe/London');
console.log(`洛杉矶: ${meeting.hour}:00`); // 洛杉矶: 10:00
console.log(`东京: ${forTokyo.hour}:00`); // 东京: 02:00(次日)
console.log(`伦敦: ${forLondon.hour}:00`); // 伦敦: 18:00
3.6 Temporal.Duration:时间段处理
Duration 表示时间段,可以精确到纳秒。
// 创建 Duration
const d1 = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 8, 9); // 复杂构造
const d2 = Temporal.Duration.from('P1Y2M3DT4H5M6S'); // ISO 8601 格式
const d3 = Temporal.Duration.from({ hours: 2, minutes: 30 }); // 对象
// 时长运算
const workDay = Temporal.Duration.from({ hours: 8 });
const lunch = Temporal.Duration.from({ minutes: 30 });
const actualWork = workDay.subtract(lunch);
console.log(actualWork.toString()); // PT7H30M
// 与日期时间结合
const start = Temporal.PlainDateTime.from('2026-05-07T09:00:00');
const projectDuration = Temporal.Duration.from({ days: 5, hours: 4 });
const deadline = start.add(projectDuration);
console.log(deadline.toString()); // 2026-05-12T13:00:00
// 时长比较
const d4 = Temporal.Duration.from({ hours: 3 });
const d5 = Temporal.Duration.from({ minutes: 180 });
console.log(d4.equals(d5)); // true
3.7 Temporal.PlainYearMonth 和 Temporal.PlainMonthDay
专门用于处理年月和月日场景。
// PlainYearMonth:信用卡过期、合同到期
const expiry = Temporal.PlainYearMonth.from('2026-12');
const nextMonth = expiry.add({ months: 1 });
console.log(nextMonth.toString()); // 2027-01
// 检查是否过期
const now = Temporal.Now.plainDateISO();
const currentMonth = now.toPlainYearMonth();
if (Temporal.PlainYearMonth.compare(currentMonth, expiry) > 0) {
console.log('信用卡已过期');
}
// PlainMonthDay:生日、纪念日
const birthday = Temporal.PlainMonthDay.from('05-15');
const thisYearBirthday = birthday.toPlainDate({ year: 2026 });
console.log(thisYearBirthday.toString()); // 2026-05-15
// 计算下一个生日
const today = Temporal.Now.plainDateISO();
let nextBirthday = birthday.toPlainDate({ year: today.year });
if (Temporal.PlainDate.compare(nextBirthday, today) < 0) {
nextBirthday = birthday.toPlainDate({ year: today.year + 1 });
}
const daysUntilBirthday = nextBirthday.since(today).days;
console.log(`距离生日还有 ${daysUntilBirthday} 天`);
四、实战案例:从 Date 迁移到 Temporal
4.1 案例 1:日程管理系统
// 旧代码(使用 Date)
class OldScheduler {
scheduleMeeting(title, dateStr, durationMinutes) {
const date = new Date(dateStr); // 解析可能失败
const end = new Date(date.getTime() + durationMinutes * 60 * 1000);
return {
title,
start: date.toISOString(),
end: end.toISOString()
};
}
getMeetingsToday() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
// 需要遍历所有会议,比较时间...
// 容易出错,时区处理复杂
}
}
// 新代码(使用 Temporal)
class ModernScheduler {
constructor(timezone = 'Asia/Shanghai') {
this.timezone = timezone;
}
scheduleMeeting(title, dateTimeStr, durationMinutes) {
// 明确的时区感知
const start = Temporal.ZonedDateTime.from(`${dateTimeStr}[${this.timezone}]`);
const end = start.add({ minutes: durationMinutes });
return {
title,
start: start.toString(),
end: end.toString(),
// 自动处理跨时区显示
startLocal: start.toLocaleString('zh-CN', { timeZone: this.timezone }),
endLocal: end.toLocaleString('zh-CN', { timeZone: this.timezone })
};
}
getMeetingsToday() {
const today = Temporal.Now.plainDateISO(this.timezone);
const dayStart = today.toZonedDateTime(this.timezone);
const dayEnd = dayStart.add({ days: 1 });
return this.meetings.filter(meeting => {
const meetingDate = meeting.start.toZonedDateTimeISO(this.timezone);
return meetingDate.since(dayStart).sign >= 0 &&
dayEnd.since(meetingDate).sign > 0;
});
}
// 跨时区会议安排
scheduleGlobalMeeting(title, dateTimeStr, participants) {
const meetingTime = Temporal.ZonedDateTime.from(`${dateTimeStr}[${this.timezone}]`);
return {
title,
organizer: meetingTime.toString(),
participants: participants.map(p => ({
name: p.name,
localTime: meetingTime.withTimeZone(p.timezone)
.toLocaleString(p.locale, { timeZone: p.timezone })
}))
};
}
}
// 使用示例
const scheduler = new ModernScheduler('Asia/Shanghai');
const meeting = scheduler.scheduleMeeting(
'产品评审会',
'2026-05-10T14:00:00',
90
);
console.log(meeting);
4.2 案例 2:考勤系统
class AttendanceSystem {
constructor(timezone = 'Asia/Shanghai') {
this.timezone = timezone;
this.workStart = Temporal.PlainTime.from('09:00:00');
this.workEnd = Temporal.PlainTime.from('18:00:00');
this.lunchStart = Temporal.PlainTime.from('12:00:00');
this.lunchEnd = Temporal.PlainTime.from('13:00:00');
}
// 记录打卡
recordCheckIn(employeeId) {
const now = Temporal.Now.zonedDateTimeISO(this.timezone);
const today = now.toPlainDate();
const checkInTime = now.toPlainTime();
const record = {
employeeId,
date: today.toString(),
checkIn: checkInTime.toString(),
// 自动判断是否迟到
isLate: Temporal.PlainTime.compare(checkInTime, this.workStart) > 0
};
return record;
}
// 计算工作时长
calculateWorkHours(checkIn, checkOut) {
const start = Temporal.PlainTime.from(checkIn);
const end = Temporal.PlainTime.from(checkOut);
// 总时长
const totalDuration = end.since(start);
// 扣除午休时间
let workHours = totalDuration;
// 检查是否跨越午休时间
const lunchStartCompare = Temporal.PlainTime.compare(start, this.lunchStart);
const lunchEndCompare = Temporal.PlainTime.compare(end, this.lunchEnd);
if (lunchStartCompare <= 0 && lunchEndCompare >= 0) {
// 完整跨越午休
workHours = workHours.subtract({ hours: 1 });
} else if (lunchStartCompare <= 0 && lunchEndCompare < 0 &&
Temporal.PlainTime.compare(end, this.lunchStart) > 0) {
// 部分跨越午休(上午)
const overlap = end.since(this.lunchStart);
workHours = workHours.subtract(overlap);
}
return {
totalHours: totalDuration.hours + totalDuration.minutes / 60,
actualWorkHours: workHours.hours + workHours.minutes / 60,
overtime: Math.max(0, workHours.hours - 8)
};
}
// 生成月度考勤报告
generateMonthlyReport(employeeId, year, month) {
const monthStart = new Temporal.PlainYearMonth(year, month);
const daysInMonth = monthStart.daysInMonth;
const records = this.getEmployeeRecords(employeeId, year, month);
let totalWorkDays = 0;
let totalLateDays = 0;
let totalOvertimeHours = 0;
for (let day = 1; day <= daysInMonth; day++) {
const date = new Temporal.PlainDate(year, month, day);
// 跳过周末
if (date.dayOfWeek > 5) continue;
totalWorkDays++;
const record = records.find(r => r.date === date.toString());
if (record) {
if (record.isLate) totalLateDays++;
totalOvertimeHours += record.overtime || 0;
}
}
return {
employeeId,
month: monthStart.toString(),
workDays: totalWorkDays,
lateDays: totalLateDays,
overtimeHours: totalOvertimeHours,
attendanceRate: (totalWorkDays - totalLateDays) / totalWorkDays * 100
};
}
}
4.3 案例 3:国际化电商系统
class GlobalECommerce {
// 处理不同时区的订单截止时间
calculateOrderDeadline(orderTime, customerTimezone, cutoffHour = 15) {
// 将订单时间转换为客户时区
const customerTime = orderTime.withTimeZone(customerTimezone);
// 计算当天截止时间
const today = customerTime.toPlainDate();
const cutoff = today.toZonedDateTime(customerTimezone)
.with({ hour: cutoffHour, minute: 0, second: 0 });
// 如果已过截止时间,则为明天
if (Temporal.ZonedDateTime.compare(customerTime, cutoff) > 0) {
return cutoff.add({ days: 1 });
}
return cutoff;
}
// 处理不同地区的促销活动
schedulePromotion(name, startUTC, duration, regions) {
const start = Temporal.ZonedDateTime.from(`${startUTC}[UTC]`);
const end = start.add(duration);
return regions.map(region => {
const localStart = start.withTimeZone(region.timezone);
const localEnd = end.withTimeZone(region.timezone);
return {
region: region.name,
name,
start: localStart.toString(),
end: localEnd.toString(),
// 处理跨日期的活动
spansMultipleDays: !localStart.toPlainDate().equals(localEnd.toPlainDate())
};
});
}
// 计算跨时区的物流时效
calculateDeliveryTime(originTime, originTimezone, destinationTimezone, transitDays) {
const departure = originTime.withTimeZone(originTimezone);
// 工作日计算(跳过周末)
let arrival = departure;
let daysAdded = 0;
while (daysAdded < transitDays) {
arrival = arrival.add({ days: 1 });
if (arrival.dayOfWeek <= 5) { // 周一到周五
daysAdded++;
}
}
// 转换为目标时区
const localArrival = arrival.withTimeZone(destinationTimezone);
return {
departure: departure.toString(),
arrival: localArrival.toString(),
transitDays,
// 自动处理时差
timezoneDiff: localArrival.offset - departure.offset
};
}
}
五、Temporal vs Date:完整对比
5.1 功能对比表
| 特性 | Date | Temporal |
|---|---|---|
| 不可变性 | ❌ 可变对象 | ✅ 完全不可变 |
| 时区支持 | ❌ 仅本地和 UTC | ✅ 完整 IANA 时区支持 |
| 夏令时 | ❌ 无法处理 | ✅ 自动处理 |
| 日历系统 | ❌ 仅公历 | ✅ 支持多种日历 |
| 精度 | 毫秒 | 纳秒 |
| 类型区分 | ❌ 混合类型 | ✅ 明确类型系统 |
| 解析一致性 | ❌ 浏览器差异 | ✅ 统一 ISO 8601 |
| 月份索引 | ❌ 0-11 | ✅ 1-12 |
| 时长计算 | ❌ 手动毫秒 | ✅ Duration 类型 |
| 格式化 | ❌ 需第三方库 | ✅ 内置 toLocaleString |
5.2 性能对比
// 性能测试:创建 100 万个日期对象
console.time('Date');
for (let i = 0; i < 1000000; i++) {
new Date(2026, 4, 7, 14, 30, 0);
}
console.timeEnd('Date'); // ~150ms
console.time('Temporal.PlainDateTime');
for (let i = 0; i < 1000000; i++) {
new Temporal.PlainDateTime(2026, 5, 7, 14, 30, 0);
}
console.timeEnd('Temporal.PlainDateTime'); // ~200ms
// 性能略慢,但换来的是正确性和安全性
5.3 迁移指南
// 迁移对照表
// 创建当前时间
// 旧: const now = new Date();
// 新: const now = Temporal.Now.zonedDateTimeISO();
// 创建指定日期
// 旧: const date = new Date(2026, 4, 7); // 注意月份!
// 新: const date = new Temporal.PlainDate(2026, 5, 7); // 月份正常
// 获取年月日
// 旧: date.getFullYear(), date.getMonth() + 1, date.getDate()
// 新: date.year, date.month, date.day
// 格式化
// 旧: date.toLocaleDateString('zh-CN')
// 新: date.toLocaleString('zh-CN', { calendar: 'iso8601' })
// 时间运算
// 旧: const tomorrow = new Date(date.getTime() + 86400000);
// 新: const tomorrow = date.add({ days: 1 });
// 计算差值
// 旧: const diff = (date2 - date1) / 86400000;
// 新: const diff = date2.since(date1).days;
// 时区转换
// 旧: 复杂的 getTimezoneOffset() 计算
// 新: date.withTimeZone('Asia/Tokyo')
六、Node.js 26 其他重要更新
6.1 V8 引擎更新
Node.js 26 升级到 V8 14.6.202.33,带来以下性能提升:
// 新的 V8 优化特性
// 1. 更快的对象属性访问
const obj = { a: 1, b: 2, c: 3 };
// V8 14.6 优化了隐藏类(Hidden Class)的创建和转换
// 2. 改进的 JIT 编译
function heavyComputation(n) {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
// V8 14.6 的 TurboFan 编译器生成更优化的机器码
// 3. 内存管理优化
// 新的垃圾回收算法减少 GC 暂停时间
6.2 Undici 8.0.2
Undici 是 Node.js 的 HTTP 客户端实现,8.0.2 版本带来:
// 使用 Undici 的 fetch API
const response = await fetch('https://api.example.com/data', {
// 新特性:更灵活的连接池配置
dispatcher: new Undici.Pool('https://api.example.com', {
connections: 100,
pipelining: 10
})
});
// 改进的 HTTP/2 支持
// 更好的连接复用
// 减少内存占用
6.3 其他 API 更新
// 1. 改进的 fs.promises API
import { readFile, writeFile } from 'fs/promises';
// 新增 AbortSignal 支持
const controller = new AbortController();
try {
const data = await readFile('large-file.txt', {
signal: controller.signal
});
} catch (err) {
if (err.name === 'AbortError') {
console.log('读取被取消');
}
}
// 2. 改进的 stream API
import { Readable } from 'stream';
// 新的便捷方法
const readable = Readable.from([1, 2, 3, 4, 5]);
const doubled = readable.map(x => x * 2); // 新增 map 方法
// 3. 改进的 worker_threads
import { Worker, isMainThread, workerData } from 'worker_threads';
if (isMainThread) {
// 新的 Worker 选项
const worker = new Worker('./worker.js', {
workerData: { task: 'heavy' },
// 新增:资源限制
resourceLimits: {
maxOldGenerationSizeMb: 512,
maxYoungGenerationSizeMb: 64
}
});
}
七、最佳实践与注意事项
7.1 Temporal 使用最佳实践
// ✅ 推荐:明确指定时区
const time = Temporal.Now.zonedDateTimeISO('Asia/Shanghai');
// ❌ 避免:使用本地时区(服务器可能在不同时区)
const localTime = Temporal.Now.zonedDateTimeISO(); // 可能不是你期望的时区
// ✅ 推荐:使用 Plain 类型处理无时区场景
const birthday = Temporal.PlainDate.from('1990-05-15');
// ❌ 避免:用 ZonedDateTime 处理生日(生日不依赖时区)
const badBirthday = Temporal.ZonedDateTime.from('1990-05-15T00:00:00[Asia/Shanghai]');
// ✅ 推荐:使用 Duration 进行时间运算
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
const later = now.add(duration);
// ❌ 避免:手动计算毫秒
const badLater = new Date(now.getTime() + 2 * 60 * 60 * 1000 + 30 * 60 * 1000);
// ✅ 推荐:使用 compare 方法比较
if (Temporal.PlainDate.compare(date1, date2) < 0) {
console.log('date1 在 date2 之前');
}
// ❌ 避免:直接比较(Temporal 对象不支持)
if (date1 < date2) { // TypeError!
}
7.2 迁移注意事项
// 1. 数据库存储
// ✅ 推荐:存储 ISO 8601 字符串
const toStore = instant.toString(); // '2026-05-07T05:01:00Z'
// ✅ 或存储时间戳(兼容旧系统)
const timestamp = instant.epochMilliseconds;
// 2. API 响应
// ✅ 推荐:返回 ISO 字符串
app.get('/api/events', (req, res) => {
const events = getEvents();
res.json(events.map(e => ({
...e,
startTime: e.startTime.toString(),
endTime: e.endTime.toString()
})));
});
// 3. 前端兼容
// Temporal 尚未在所有浏览器中支持
// 需要使用 polyfill 或转换为 Date
const forFrontend = instant.toZonedDateTimeISO('UTC').toPlainDateTime().toString();
7.3 常见陷阱
// 陷阱 1:忽略时区转换
const meeting = Temporal.ZonedDateTime.from('2026-05-10T10:00:00[America/New_York]');
// 如果直接显示给中国用户,时间会错
// ✅ 正确做法:
const forChina = meeting.withTimeZone('Asia/Shanghai');
// 陷阱 2:Duration 可能为负
const d1 = Temporal.Duration.from({ hours: 3 });
const d2 = Temporal.Duration.from({ hours: 5 });
const diff = d1.subtract(d2); // 负的 Duration!
// ✅ 检查符号
if (diff.sign < 0) {
console.log('d1 比 d2 短');
}
// 陷阱 3:跨日历系统的日期
const chinese = Temporal.PlainDate.from('2026-05-07[u-ca=chinese]');
const iso = Temporal.PlainDate.from('2026-05-07');
// 两者不相等!
console.log(chinese.equals(iso)); // false
// ✅ 转换为同一日历系统
const chineseInISO = chinese.withCalendar('iso8601');
console.log(chineseInISO.equals(iso)); // true
八、展望与总结
8.1 Temporal 的未来
Temporal API 目前处于 TC39 Stage 3 阶段,预计将在 ECMAScript 2026 或 2027 中正式标准化。Node.js 26 的默认启用是一个重要里程碑,标志着:
- 生产可用:V8 引擎的完整支持意味着性能和稳定性已达标
- 生态成熟:主流框架和库正在逐步支持 Temporal
- 标准推进:浏览器厂商也在积极实现
8.2 对开发者的影响
- 不再需要 moment.js:Temporal 提供了更强大的功能
- 更少的 bug:不可变性和明确的类型系统减少错误
- 更好的国际化:内置时区和日历系统支持
- 更简单的代码:直观的 API 设计降低学习成本
8.3 总结
Node.js 26 的发布标志着 JavaScript 日期时间处理进入新时代。Temporal API 从根本上解决了困扰开发者 30 年的问题:
| 问题 | Date 时代 | Temporal 时代 |
|---|---|---|
| 月份索引 | 0-11,容易出错 | 1-12,符合直觉 |
| 时区处理 | 依赖第三方库 | 内置完整支持 |
| 不可变性 | 可变,易出错 | 完全不可变 |
| 类型安全 | 混合类型 | 明确的类型系统 |
| 精度 | 毫秒 | 纳秒 |
| 国际化 | 困难 | 内置多日历系统 |
对于 Node.js 开发者,现在是开始学习和使用 Temporal API 的最佳时机。2026 年 10 月,Node.js 26 将进入 LTS 阶段,成为生产环境的推荐版本。提前掌握 Temporal,将在未来的项目中获得巨大的效率提升。
参考资源:
关键词:Node.js 26, Temporal API, JavaScript 日期处理, 时区处理, 日期时间, V8 引擎, LTS, 不可变对象, ISO 8601, 夏令时