在前端工程中,日期与时间处理涉及解析、格式化、算术运算、时区与夏令时(DST)、本地化以及与后端/数据库的互操作。这四个库分别代表了截然不同的设计方向:
dayjs、js-joda、luxon、moment:工程化时间处理的核心差异日期与时间处理的共同难题是:解析与格式化的一致性、跨时区与 DST 的正确性、算术运算的日历学语义、以及在浏览器与 Node.js 间一致的可预测行为。这四个库在数据模型、依赖与边界行为上的取舍,直接决定了工程可维护性与正确性成本。
dayjs:近似 moment 的链式 API,但实例不可变,核心极小、插件化扩展能力(如 utc、timezone、duration、customParseFormat)。默认仅具备本地时间与 ISO 解析,复杂能力需自行引入插件。js-joda:完全不可变,模型与 java.time 对齐(LocalDate、LocalDateTime、ZonedDateTime、OffsetDateTime、Duration、Period 等)。不以“方便”为先,而是以“语义清晰、可证明的正确性”为最高优先级。luxon:不可变,以 DateTime 为中心,并提供 Duration 与 Interval。大量依赖平台 Intl 能力实现本地化与时区,API 直观且分层清晰。moment:可变对象、宽松解析、单体式设计,生态完备(含 moment-timezone)。但可变与宽松默认很容易在大型项目中产生隐式状态与数据质量问题。dayjs:通过 utc 与 timezone 插件获得 IANA 时区支持。需要确保运行时加载对应时区数据(常见做法是打包一份 IANA 数据,或复用 moment-timezone 的打包数据并通过 dayjs.tz.add(...) 注入)。未加载数据时只能做有限的偏移换算。DST 缺口/重叠时刻会被库内部解析逻辑调整到最近的合法本地时间,具体结果依赖所用数据与解析路径。js-joda:核心仅支持固定偏移与系统区信息,历史与未来规则需 js-joda-timezone。ZonedDateTime 能严格表达本地时间与区域规则,DST 缺口(gap)通常会“前移”到下一个有效时刻,重叠(overlap)默认选定更早或更符合规则的一侧,行为与 java.time 保持一致,可预测性好。luxon:直接依赖 Intl 与宿主时区数据库。一般浏览器表现稳定;Node.js 侧若无 full-icu,语言与部分区域格式可能受限。DateTime 在创建时即可带 zone,遇到无效本地时间会返回 invalid,可用 isValid 与 invalidReason 明确识别。moment:配合 moment-timezone 使用 IANA 数据。解析缺口/重叠会自动归一到最接近的有效时刻(例如将无效 2:30 推进到 3:30,或在重叠时采用较早偏移),细节长期稳定但可变对象易产生连带副作用。dayjs:原生仅可靠解析 ISO;非 ISO 需 customParseFormat。格式化使用 format('YYYY-MM-DD HH:mm') 风格,区域化须引入对应 locale 文件。js-joda:DateTimeFormatter 提供与 Java 一致的模式与本地化能力(完整本地化需额外 locale 支持)。解析严格,失败会抛出异常或需要显式处理。luxon:fromISO、fromFormat 与 toFormat 覆盖主流场景,失配会产生 invalid 对象而非静默吞错。moment:宽松解析容错高但也易误判。格式化能力成熟;与生态工具的兼容性最好。dayjs:add/subtract 采用单位语义(day/week/month)或时间量(hour/minute)。不可变,链式流畅。跨 DST 时 add(1, 'day') 与 add(24, 'hour') 行为不同,前者保证“按日历天”滚动,后者是绝对小时。js-joda:提供 plusDays/plusHours、Duration/Period,日历与时长语义分离清晰。以 ZonedDateTime 为基准可明确表达“本地日期推进”与“瞬时时长推进”的差异。luxon:plus/minus 接受对象字面量(如 { days: 1 }、{ hours: 24 }),Interval 对区间计算友好。moment:API 丰富,需留意对象是可变的;跨 DST 的“日 vs 时”差异同样存在。dayjs:通过引入 locale 文件启用本地化;格式化依赖自有规则。js-joda:需额外 locale 支持库才能获得本地化格式化;优势在于与 DateTimeFormatter 的强一致性。luxon:依赖 Intl,通常无需额外数据包;Node.js 环境需确保 ICU 数据充足以避免语言缺失。moment:自带大量 locale 定义,使用简便但维护负担较重。dayjs:插件化配置灵活,ESM 友好;TypeScript 通过模块扩展增强类型,需留意插件引入次序与声明文件。js-joda:类库结构清晰,ESM 友好;TypeScript 体验优秀;与 Java 生态的思想对齐利于多端一致性。luxon:自带类型定义与 ESM;与 Intl 深度绑定,减少维护数据成本。moment:历史包袱较多,CJS/副作用对 Tree Shaking 不友好;类型定义可用但不够现代化。moment 与未启用 customParseFormat 的 dayjs 可能把“看似合理”的字符串解析为错误日期。luxon 与 js-joda 倾向于“显式失败”。js-joda 与 luxon 显式/可检测,便于业务做二次确认;dayjs/moment 通常会自动归一,需要额外校验以避免业务误差。luxon 的本地化与时区完全依赖宿主环境;dayjs 与 moment 可通过打包时区数据获得可控一致性;js-joda 通过显式引入 js-joda-timezone 获得确定性。目标:在 America/New_York 下解析“2021-03-14 01:30”(DST 跳跃日的缺口),加 1 小时并输出带偏移的字符串。
dayjsimport dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);
// 确保已加载所需的 IANA 时区数据(若未内置,需通过 dayjs.tz.add(...) 注入)
const dt = dayjs.tz('2021-03-14 01:30', 'YYYY-MM-DD HH:mm', 'America/New_York');
const plus1h = dt.add(1, 'hour');
console.log(plus1h.format('YYYY-MM-DD HH:mm Z')); // 期望落到有效本地时间
js-jodaimport { ZonedDateTime, ZoneId, LocalDateTime, DateTimeFormatter } from '@js-joda/core';
import '@js-joda/timezone';
const ny = ZoneId.of('America/New_York');
const ldt = LocalDateTime.parse('2021-03-14T01:30');
const zdt = ldt.atZone(ny); // 缺口会解析为下一个有效时刻(通常前移到 03:30)
const plus1h = zdt.plusHours(1);
const fmt = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm XXX');
console.log(fmt.format(plus1h));
luxonimport { DateTime } from 'luxon';
let dt = DateTime.fromFormat('2021-03-14 01:30', 'yyyy-MM-dd HH:mm', { zone: 'America/New_York' });
if (!dt.isValid) {
// 可根据 invalidReason 决定前移、报错或人工干预
dt = DateTime.fromISO('2021-03-14T03:30', { zone: 'America/New_York' });
}
const plus1h = dt.plus({ hours: 1 });
console.log(plus1h.toFormat('yyyy-MM-dd HH:mm ZZ'));
momentimport moment from 'moment-timezone';
const dt = moment.tz('2021-03-14 01:30', 'YYYY-MM-DD HH:mm', 'America/New_York');
const plus1h = dt.clone().add(1, 'hour');
console.log(plus1h.format('YYYY-MM-DD HH:mm Z'));
以 America/New_York 的 2021-11-06 12:00 为起点(7 日凌晨 DST 结束)。
dayjsconst base = dayjs.tz('2021-11-06 12:00', 'YYYY-MM-DD HH:mm', 'America/New_York');
console.log(base.add(1, 'day').format()); // 按“日历天”推进
console.log(base.add(24, 'hour').format()); // 按“绝对小时”推进
js-jodaconst base = LocalDateTime.parse('2021-11-06T12:00').atZone(ZoneId.of('America/New_York'));
console.log(base.plusDays(1).toString());
console.log(base.plusHours(24).toString());
luxonconst base = DateTime.fromISO('2021-11-06T12:00', { zone: 'America/New_York' });
console.log(base.plus({ days: 1 }).toISO());
console.log(base.plus({ hours: 24 }).toISO());
momentconst base = moment.tz('2021-11-06 12:00', 'YYYY-MM-DD HH:mm', 'America/New_York');
console.log(base.clone().add(1, 'day').format());
console.log(base.clone().add(24, 'hours').format());
上述输出会体现“日历天”与“绝对小时”的差异:跨 DST 结束日时,+1 day 与 +24 hours 往往不等价。
计算两个时间点之间的小时数(可为小数)。
dayjsconst hours = dayjs('2021-03-14T12:00:00Z').diff(dayjs('2021-03-13T12:00:00Z'), 'hour', true);
js-jodaimport { Instant, Duration } from '@js-joda/core';
const hours = Duration.between(Instant.parse('2021-03-13T12:00:00Z'), Instant.parse('2021-03-14T12:00:00Z')).toMillis() / 3600000;
luxonimport { DateTime, Interval } from 'luxon';
const start = DateTime.fromISO('2021-03-13T12:00:00Z');
const end = DateTime.fromISO('2021-03-14T12:00:00Z');
const hours = Interval.fromDateTimes(start, end).length('hours');
momentconst hours = moment('2021-03-14T12:00:00Z').diff(moment('2021-03-13T12:00:00Z'), 'hours', true);
dayjs:插件化减少不必要能力的加载,链式 API 上手快。复杂时区依赖外部数据,需在“体积 vs 正确性”间取舍。js-joda:语义最清晰、边界最可控,长线维护成本低;但对团队的日期/时区建模能力有更高要求。luxon:以宿主能力换库内复杂度,整体复杂度中等且日常易用。moment:稳定但历史包袱大,可变对象与宽松解析需要严格代码规范与测试兜底。dayjs 以最小可用内核与插件化换取灵活度;js-joda 用严格模型确保可证明的正确性;luxon 将复杂性交给平台 Intl,在易用与能力间平衡;moment 在存量系统中仍具现实价值但已不再前进。核心差异集中在:不可变性与模型是否严格、时区数据的来源与可控程度、以及对 DST 边界与无效时间的显式处理能力。这些差异最终会体现在代码的可预期性、测试成本与长期维护质量上。
当需要在前端以最小心智成本完成常见日期格式化、加减、比较等任务,并希望沿用接近 moment 的链式 API 时可选。通过插件组合按需扩展(如 utc、timezone、customParseFormat、duration),便于控制打包体积。注意:复杂时区/DST 规则需要 timezone 插件并确保加载 IANA 时区数据;在类型层面,插件式扩展需要配合 TypeScript 的模块增强以获得完备类型。
官方已进入维护模式,不建议在新项目中使用。适用于维护既有系统或渐进式迁移场景(例如与依赖 moment 的第三方组件共存一段时间)。需谨慎处理其可变对象模型与宽松解析带来的隐含缺陷,并评估逐步替换为 dayjs、luxon 或 js-joda 的可行性。
若希望获得现代、区分清晰且默认即具备时区/本地化能力的库,同时不想自己维护时区数据,可选。其依赖运行环境的 Intl 能力:浏览器通常足够,但 Node.js 需确保 full-icu 才能覆盖全部语言环境。内置 Interval/Duration 让时间段与长度计算直观,适合作为中等复杂度项目的通用方案。
在对时间正确性有严格要求的领域模型或核心业务逻辑中(账务、清结算、跨时区排程)更合适。其不可变、类型严谨且与 java.time 一致的 API 能最大化降低 DST、闰年、周起始等规则导致的隐式错误。需要额外引入 js-joda-timezone 与(可选)locale 才能获得完整时区与本地化能力,学习曲线相对更陡,不太适合只做轻量 UI 格式化的场景。
English | 简体中文 | 日本語 | Português Brasileiro | 한국어 | Español (España) | Русский | Türkçe | සිංහල | עברית
Fast 2kB alternative to Moment.js with the same modern API
Day.js is a minimalist JavaScript library that parses, validates, manipulates, and displays dates and times for modern browsers with a largely Moment.js-compatible API. If you use Moment.js, you already know how to use Day.js.
dayjs().startOf('month').add(1, 'day').set('year', 2018).format('YYYY-MM-DD HH:mm:ss');
You can find more details, API, and other docs on day.js.org website.
npm install dayjs --save
It's easy to use Day.js APIs to parse, validate, manipulate, and display dates and times.
dayjs('2018-08-08') // parse
dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A') // display
dayjs().set('month', 3).month() // get & set
dayjs().add(1, 'year') // manipulate
dayjs().isBefore(dayjs()) // query
Day.js has great support for internationalization.
But none of them will be included in your build unless you use it.
import 'dayjs/locale/es' // load on demand
dayjs.locale('es') // use Spanish locale globally
dayjs('2018-05-05').locale('zh-cn').format() // use Chinese Simplified locale in a specific instance
A plugin is an independent module that can be added to Day.js to extend functionality or add new features.
import advancedFormat from 'dayjs/plugin/advancedFormat' // load on demand
dayjs.extend(advancedFormat) // use plugin
dayjs().format('Q Do k kk X x') // more available formats
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
[Become a sponsor via Github] [Become a sponsor via OpenCollective]
This project exists thanks to all the people who contribute.
Please give us a 💖 star 💖 to support us. Thank you.
And thank you to all our backers! 🙏
Day.js is licensed under a MIT License.