date-fns、dayjs、luxon 和 moment 都是 JavaScript 生态中用于处理日期和时间的流行库,提供了日期解析、格式化、计算、国际化以及时区转换等功能。moment 曾长期占据主导地位,但因其可变状态设计、较大的 bundle 体积和缺乏 tree-shaking 支持,官方已于 2020 年宣布进入维护模式,不再推荐用于新项目。date-fns 采用函数式编程范式,基于原生 Date 对象,强调不可变性和模块化;dayjs 提供类似 moment 的链式 API,但内部实现为不可变对象,且核心体积极小;luxon 则由 Moment 团队成员开发,深度集成浏览器 Intl API,专注于不可变性和强大的时区处理能力。这些库各有侧重,适用于不同的工程场景和架构需求。
在现代前端开发中,处理日期和时间看似简单,实则充满陷阱 —— 时区转换、格式化、国际化、不可变性、性能优化等问题层出不穷。date-fns、dayjs、luxon 和 moment 是四个主流的 JavaScript 日期库,但它们的设计哲学、API 风格和适用场景大不相同。本文将从真实工程角度出发,深入比较它们的核心能力。
⚠️ 重要提示:根据 moment 官方文档,该库已进入维护模式("maintenance mode"),不再推荐用于新项目。官方明确建议开发者评估
date-fns、dayjs或luxon等替代方案。
不同库对“如何表示时间”有根本分歧,这直接影响代码风格和可维护性。
date-fns 采用纯函数式设计 —— 所有操作都返回新的 Date 对象,原始输入不变。它像 Lodash 一样提供大量独立函数,按需导入。
// date-fns: 纯函数 + 原生 Date
import { addDays, format } from 'date-fns';
const today = new Date();
const tomorrow = addDays(today, 1); // 返回新 Date 实例
const formatted = format(tomorrow, 'yyyy-MM-dd');
dayjs 模仿 moment 的链式 API,但内部使用不可变对象。每次调用方法都会返回新实例,避免意外修改。
// dayjs: 链式调用 + 不可变
import dayjs from 'dayjs';
const tomorrow = dayjs().add(1, 'day');
const formatted = tomorrow.format('YYYY-MM-DD');
luxon 基于 Immutable.js 思想,所有 DateTime 对象都是不可变的,并深度集成 Intl API 处理时区和本地化。
// luxon: 不可变 + 强大的时区支持
import { DateTime } from 'luxon';
const dt = DateTime.now().plus({ days: 1 });
const formatted = dt.toFormat('yyyy-MM-dd');
moment(已弃用)使用可变对象设计 —— 调用 .add() 会直接修改原对象,容易引发隐蔽 bug。
// moment: 可变对象(不推荐)
import moment from 'moment';
const now = moment();
now.add(1, 'day'); // now 被直接修改!
时区是日期库最复杂的部分。各库能力差异显著。
date-fns 本身不处理时区,但通过 date-fns-tz 插件提供有限支持。适合简单场景。
// date-fns + date-fns-tz
import { zonedTimeToUtc, utcToZonedTime, format } from 'date-fns-tz';
const utcDate = zonedTimeToUtc('2023-06-01 12:00', 'Asia/Shanghai');
const shanghaiTime = utcToZonedTime(utcDate, 'Asia/Shanghai');
format(shanghaiTime, 'yyyy-MM-dd HH:mm', { timeZone: 'Asia/Shanghai' });
dayjs 通过插件 timezone 支持时区,但功能较基础,依赖外部数据(如 Intl)。
// dayjs + timezone plugin
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
const shanghai = dayjs().tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm');
luxon 内置强大时区引擎,直接利用浏览器 Intl API,无需额外插件。支持 IANA 时区、夏令时自动处理。
// luxon: 原生时区支持
import { DateTime } from 'luxon';
const dt = DateTime.fromISO('2023-06-01T12:00:00', { zone: 'Asia/Shanghai' });
const inNY = dt.setZone('America/New_York');
console.log(inNY.toISO()); // 自动转换并保留时区信息
moment 需要单独引入 moment-timezone 包才能处理时区,且体积庞大。
// moment + moment-timezone(不推荐)
import moment from 'moment-timezone';
const shanghai = moment.tz('2023-06-01 12:00', 'Asia/Shanghai');
多语言支持对全球化应用至关重要。
date-fns 提供 70+ 语言的 locale 文件,可 tree-shake,只打包用到的语言。
// date-fns i18n
import { format } from 'date-fns';
import { zhCN } from 'date-fns/locale';
format(new Date(), 'PPPP', { locale: zhCN }); // "2023年6月1日星期四"
dayjs 通过 locale 插件支持多语言,同样可按需引入。
// dayjs i18n
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
dayjs().format('dddd, MMMM D, YYYY'); // "星期四, 六月 1, 2023"
luxon 直接调用浏览器 Intl.DateTimeFormat,自动适配系统语言,无需额外包。但格式控制不如其他库灵活。
// luxon i18n
import { DateTime } from 'luxon';
// 自动使用浏览器语言
DateTime.now().toLocaleString(DateTime.DATE_FULL);
// 中文环境输出:"2023年6月1日"
moment 内置多语言支持,但所有 locale 都包含在主包中,无法 tree-shake,导致 bundle 体积膨胀。
格式化字符串语法各不相同,影响开发体验。
date-fns 使用类似 Unicode Technical Standard #35 的 tokens(如 yyyy, MM, dd),清晰直观。
format(new Date(), 'yyyy-MM-dd HH:mm:ss'); // "2023-06-01 14:30:00"
dayjs 模仿 moment 的格式语法(YYYY, MM, DD),老用户迁移成本低。
dayjs().format('YYYY-MM-DD HH:mm:ss'); // "2023-06-01 14:30:00"
luxon 使用自己的格式 tokens(yyyy, LL, dd),或推荐使用 toLocaleString() 避免自定义格式。
DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss');
moment 使用 YYYY-MM-DD 风格,但因可变性问题,格式化时可能产生副作用。
date-fns 无插件系统,所有功能通过独立函数提供。扩展需自行封装。
dayjs 设计了轻量插件机制(.extend()),官方提供 timezone、customParseFormat 等插件。
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
dayjs('2023/06/01', 'YYYY/MM/DD'); // 支持自定义解析格式
luxon 功能完整内置于核心,无需插件。通过设置全局配置(如 Settings.defaultLocale)调整行为。
moment 有丰富插件生态,但因项目停滞,新插件几乎不再更新。
常见操作如加减天数、比较大小等:
// 加 7 天
// date-fns
addDays(new Date(), 7);
// dayjs
dayjs().add(7, 'day');
// luxon
DateTime.now().plus({ days: 7 });
// moment (avoid)
moment().add(7, 'days');
// 比较两个日期
// date-fns
isAfter(date1, date2);
// dayjs
dayjs(date1).isAfter(date2);
// luxon
DateTime.fromJSDate(date1) > DateTime.fromJSDate(date2);
| 特性 | date-fns | dayjs | luxon | moment(已弃用) |
|---|---|---|---|---|
| 设计范式 | 函数式 + 原生 Date | 链式 + 不可变 | 不可变对象 + Intl 深度集成 | 可变对象(危险) |
| 时区支持 | 需 date-fns-tz 插件 | 需 timezone 插件 | 内置,强大 | 需 moment-timezone |
| 国际化 | 按需引入 locale,可 tree-shake | 按需引入 locale | 自动使用浏览器 Intl | 内置所有 locale,无法摇树 |
| Bundle 优化 | 极佳(仅导入用到的函数) | 良好(核心小,插件按需) | 中等(功能完整但不可分割) | 差(全量打包) |
| 学习曲线 | 低(类似 Lodash) | 低(熟悉 moment 的开发者) | 中(需理解 Immutable 和 Intl) | 低(但有陷阱) |
| 新项目推荐度 | ✅✅✅ | ✅✅✅ | ✅✅✅(尤其涉及时区) | ❌(官方不推荐) |
date-fns。适合内容型网站、营销页等对 bundle 敏感的场景。dayjs。适合从 moment 迁移的项目或偏好链式调用的团队。luxon。它的不可变性和 Intl 集成能避免大量边界 case 错误。moment —— 官方已明确其生命周期结束,继续使用将带来长期维护风险。最终,没有“最好”的库,只有“最合适”当前项目需求的工具。理解它们的设计取舍,才能做出明智的技术决策。
选择 date-fns 如果你追求极致的 bundle 体积优化和 tree-shaking 能力,偏好函数式编程风格,并且项目对时区支持要求不高(或可通过 date-fns-tz 插件满足)。它特别适合内容型网站、营销落地页等对加载性能敏感的场景,也适合喜欢 Lodash 风格工具库的团队。
选择 dayjs 如果你或你的团队熟悉 moment 的链式 API,希望平滑迁移至现代化替代品,同时需要小巧的核心体积和按需加载的插件机制。它在保持简洁的同时提供了良好的时区和国际化支持,适合大多数常规 Web 应用,尤其是中后台管理系统。
不要在新项目中选择 moment。根据其官方文档,该项目已进入维护模式,不再接受新功能,且存在可变状态、bundle 体积大、无法 tree-shaking 等固有问题。现有项目应制定迁移计划,评估迁移到 date-fns、dayjs 或 luxon 的可行性。
选择 luxon 如果你的应用涉及复杂的时区逻辑(如全球协作、航班调度、金融交易)、需要深度依赖浏览器 Intl API 实现精准的本地化,或强调不可变数据流以避免状态污染。它由 Moment 团队核心成员开发,代表了日期处理的现代最佳实践,适合对时间精度和可靠性要求高的专业级应用。
🔥️ NEW: date-fns v4.0 with first-class time zone support is out!
date-fns provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in a browser & Node.js
👉 Blog
It's like Lodash for dates
import { compareAsc, format } from "date-fns";
format(new Date(2014, 1, 11), "yyyy-MM-dd");
//=> '2014-02-11'
const dates = [
new Date(1995, 6, 2),
new Date(1987, 1, 11),
new Date(1989, 6, 10),
];
dates.sort(compareAsc);
//=> [
// Wed Feb 11 1987 00:00:00,
// Mon Jul 10 1989 00:00:00,
// Sun Jul 02 1995 00:00:00
// ]
The library is available as an npm package. To install the package run:
npm install date-fns --save
See date-fns.org for more details, API, and other docs.