
1. Day.js 项目概述与核心价值如果你在前端项目里处理过日期和时间大概率对Date对象的原生 API 感到头疼。时区转换、格式化、日期计算每一件看似简单的事情背后都藏着不少“坑”。几年前Moment.js 几乎是解决这些问题的唯一选择但它庞大的体积在现代前端追求极致性能的背景下显得有些不合时宜。正是在这种背景下Day.js 应运而生并迅速成为轻量级日期处理库的代名词。简单来说Day.js 是一个极简的 JavaScript 库用于解析、验证、操作和格式化日期。它的核心卖点就两个API 与 Moment.js 高度兼容以及体积极小压缩后仅约 2KB。这意味着如果你熟悉 Moment.js几乎可以零成本切换到 Day.js同时你无需再为引入一个日期库而担心打包体积的膨胀。它解决了前端开发者在处理日期时最核心的痛点需要一个功能足够、使用顺手但又不会拖累应用性能的工具。无论是构建大型企业级应用还是开发对加载速度有苛刻要求的移动端 H5 页面Day.js 都是一个非常可靠的选择。2. Day.js 核心设计理念与生态解析2.1 不可变性与链式调用Day.js 的设计哲学深受函数式编程思想的影响其最核心的两个特性是不可变性Immutable和链式调用Chainable。不可变性意味着当你调用一个 Day.js 对象的方法时例如.add(1, ‘day’)它不会修改原始的 Day.js 实例而是返回一个全新的、包含了操作结果的 Day.js 实例。这个特性在状态管理如 Redux、Vuex和避免由共享可变状态引发的 Bug 时至关重要。你可以放心地将 Day.js 对象存储在变量中或在组件间传递而不用担心它在某个角落被意外修改。链式调用则让代码变得异常简洁和可读。因为每个方法都返回一个新的 Day.js 实例所以你可以将多个操作像链条一样连接起来。例如dayjs().add(7, ‘day’).subtract(1, ‘month’).format(‘YYYY-MM-DD’)这行代码清晰地表达了“从当前时间开始加7天再减1个月最后格式化”这一系列操作。这种表达方式非常符合人类的线性思维极大地提升了开发体验。2.2 插件化架构按需引入的精髓Day.js 本身只提供了最核心的日期解析、获取和格式化功能。它的强大之处在于其插件化架构。所有高级功能如时区处理、相对时间“2分钟前”、日历时间、日期范围计算等都被设计成独立的插件。这种设计带来了几个显著优势极致的体积控制你的项目只会包含你真正用到的功能。如果你只需要格式化日期那么引入核心库就够了。如果你需要处理 UTC 时间再单独引入utc插件。这避免了“为了一碟醋包了顿饺子”的尴尬。清晰的职责分离每个插件解决一个特定的问题文档和代码结构都非常清晰降低了学习和维护成本。灵活的生态扩展社区可以自由地为 Day.js 开发插件不断丰富其生态而核心库始终保持精简和稳定。例如处理时区是日期库的难点。Day.js 通过utc和timezone插件来应对。utc插件让你能以 UTC 模式来解析和展示时间而timezone插件则提供了更强大的、基于 IANA 时区数据库如America/New_York的时区转换能力。你应该根据项目实际场景是只做后端时间统一展示还是需要支持全球多时区用户来选择合适的插件。注意插件需要单独安装和引入并且引入顺序有时很关键。通常像utc、timezone这类修改日期核心行为的插件需要在其他插件之前引入。3. 常用方法深度解析与实操要点3.1 解析与构造打好日期处理的第一块基石创建一个 Day.js 对象有多种方式理解它们的区别是正确使用的第一步。3.1.1 当前时间与字符串解析最常用的是dayjs()不传任何参数它会返回一个代表当前时刻的 Day.js 对象。const now dayjs(); // 当前时间从字符串解析日期时Day.js 默认支持 ISO 8601 格式如‘2023-04-01’、‘2023-04-01T10:30:00Z’和一些常见的字符串格式。但为了避免跨浏览器或环境下的解析差异强烈建议在解析非标准格式字符串时明确指定格式。// 不推荐依赖隐式解析可能产生意外结果 const d1 dayjs(‘04/01/2023’); // 在美国环境下可能是4月1日在其他地区可能是1月4日 // 推荐使用自定义格式解析 const d2 dayjs(‘01/04/2023’ ‘DD/MM/YYYY’); // 明确指定日/月/年顺序得到1月4日 const d3 dayjs(‘2023年4月1日’ ‘YYYY年M月D日’); // 支持中文格式3.1.2 时间戳与 Date 对象从 Unix 时间戳毫秒数或原生 JavaScriptDate对象创建 Day.js 对象是非常可靠的方式因为它们没有二义性。const fromTimestamp dayjs(1680336000000); // 2023-04-01 00:00:00 UTC const fromDateObject dayjs(new Date(2023 3 1)); // 注意JS Date的month是从0开始的3.1.3 克隆与验证由于不可变性直接赋值并不会创建克隆。你需要使用.clone()方法。const original dayjs(‘2023-04-01’); const modified original.add(1 ‘day’); console.log(original.format()); // 输出: 2023-04-01 原对象未变 console.log(modified.format()); // 输出: 2023-04-02 const clone original.clone(); // 创建一份独立的拷贝使用.isValid()方法可以检查一个 Day.js 对象是否代表一个有效的日期。这在处理用户输入或外部 API 数据时非常有用。const invalid dayjs(‘invalid string’); console.log(invalid.isValid()); // 输出: false if (invalid.isValid()) { // 安全地进行后续操作 }3.2 获取与设置精准操控日期的每一个部分Day.js 提供了丰富的 Getter 和 Setter 方法来读取和修改日期的特定部分。3.2.1 常用 Getter 方法这些方法用于获取日期对象的各个组成部分返回的是数字。const d dayjs(‘2023-04-01 14:30:25’); console.log(d.year()); // 2023 console.log(d.month()); // 3 (注意0代表一月11代表十二月) console.log(d.date()); // 1 (月份中的第几天) console.log(d.day()); // 6 (星期几0代表星期日6代表星期六) console.log(d.hour()); // 14 console.log(d.minute()); // 30 console.log(d.second()); // 25 console.log(d.millisecond()); // 03.2.2 常用 Setter 方法Setter 方法遵循不可变原则返回一个新的 Day.js 对象。它们通常有两种调用形式传入单个值或传入一个对象进行批量设置。const d dayjs(‘2023-04-01’); const nextMonth d.month(4); // 设置为5月month索引为4 console.log(nextMonth.format(‘YYYY-MM-DD’)); // 2023-05-01 // 批量设置 const updated d.set(‘year’ 2024).set(‘month’ 5); // 链式调用 // 或者使用对象形式 const updated2 d.set({ year: 2024 month: 5 });实操心得在设置月份时最容易踩的坑就是混淆“月份数字”和“月份索引”。d.month(4)设置的是五月因为一月是0。我个人的习惯是在写涉及月份设置的业务逻辑时会加一行清晰的注释或者先定义一个月份映射常量来避免混淆。3.3 日期操作与计算让时间“动”起来日期计算是 Day.js 最常用的功能之一主要依靠.add()、.subtract()和.diff()方法。3.3.1 增加与减少时间.add(value unit)和.subtract(value unit)方法非常直观。unit支持字符串如‘day’、‘month’、‘year’、‘hour’、‘minute’、‘second’等。const d dayjs(‘2023-04-01’); const nextWeek d.add(7 ‘day’); const lastMonth d.subtract(1 ‘month’); const inTwoHours d.add(120 ‘minute’); // 也支持分钟对于月份和年份的加减Day.js 处理了边缘情况。例如1月31日加1个月结果是2月28日或闰年的29日而不是不存在的2月31日。这个行为符合大多数业务场景的预期。3.3.2 计算时间差.diff(compared unit float)方法用于计算两个日期之间的差值。compared: 要比较的 Day.js 对象、Date 对象或字符串。unit: 差值单位可选默认为‘millisecond’。float: 是否返回浮点数可选默认为false返回整数向下取整。const date1 dayjs(‘2023-04-01’); const date2 dayjs(‘2023-04-15’); console.log(date2.diff(date1)); // 1209600000 毫秒 console.log(date2.diff(date1 ‘day’)); // 14 天 console.log(date2.diff(date1 ‘week’ true)); // 2 周 (浮点数) console.log(date2.diff(date1 ‘month’ true)); // 0.451612... 月 (浮点数)注意事项.diff()的结果是date2 - date1。如果date1在date2之后结果为负数。另外计算月份、年份差时由于月份天数不同浮点数结果可能是一个小数需要根据业务场景决定是否使用。3.4 格式化与显示将日期变成人类可读的文字.format()方法是日期库的“门面”负责将日期对象转换成字符串。Day.js 使用占位符系统与 Moment.js 完全兼容。3.4.1 常用格式化占位符以下是一些最常用的占位符YYYY: 四位数的年份 (如 2023)YY: 两位数的年份 (如 23)MM: 两位数的月份 (01-12)M: 一位或两位数的月份 (1-12)DD: 两位数的日期 (01-31)D: 一位或两位数的日期 (1-31)HH: 24小时制的小时 (00-23)hh: 12小时制的小时 (01-12)需配合A使用mm: 分钟 (00-59)ss: 秒钟 (00-59)A: 大写的 AM/PMa: 小写的 am/pmconst d dayjs(‘2023-04-01 14:30:00’); console.log(d.format(‘YYYY-MM-DD’)); // “2023-04-01” console.log(d.format(‘DD/MM/YYYY HH:mm’)); // “01/04/2023 14:30” console.log(d.format(‘MMMM D YYYY’)); // “April 1 2023” (需要 AdvancedFormat 插件) console.log(d.format(‘YYYY年M月D日 HH时mm分’)); // “2023年4月1日 14时30分”3.4.2 本地化格式化Day.js 核心库的格式化是英文的如月份名April。如果需要本地化如中文“四月”你需要引入并配置locale插件并导入对应的语言包。import dayjs from ‘dayjs’; import ‘dayjs/locale/zh-cn’; // 导入中文语言包 dayjs.locale(‘zh-cn’); // 全局使用中文 const d dayjs(‘2023-04-01’); console.log(d.format(‘MMMM’)); // “四月”3.5 查询与比较判断日期的关系和状态这类方法返回布尔值常用于条件判断。3.5.1 基础比较.isBefore(compared unit): 是否在某个日期/时间之前。.isAfter(compared unit): 是否在某个日期/时间之后。.isSame(compared unit): 是否与某个日期/时间相同。unit参数可选用于将比较精度限定到特定单位。例如比较是否在同一天忽略时间部分。const d1 dayjs(‘2023-04-01 10:00’); const d2 dayjs(‘2023-04-01 15:00’); const d3 dayjs(‘2023-04-02’); console.log(d1.isBefore(d2)); // true (d1时间早于d2) console.log(d1.isBefore(d3 ‘day’)); // false (在“天”这个单位上d1和d3是同一天不d3是2号) console.log(d1.isSame(d2 ‘day’)); // true (在“天”这个单位上它们是同一天)3.5.2 范围判断与其它查询.isBetween(from to unit inclusivity): 判断日期是否在两个日期之间。inclusivity参数控制是否包含边界默认为‘()’不包含‘[]’表示包含‘(]’、‘[)’为半包含。.isLeapYear(): 是否是闰年需要IsLeapYear插件。.startOf(unit)/.endOf(unit): 获取某个时间单位的开始或结束时间。这是非常实用的方法。const d dayjs(‘2023-04-01 14:30:25’); console.log(d.startOf(‘day’).format()); // 2023-04-01T00:00:0008:00 (当天零点) console.log(d.startOf(‘month’).format()); // 2023-04-01T00:00:0008:00 (当月第一天零点) console.log(d.endOf(‘month’).format()); // 2023-04-30T23:59:5908:00 (当月最后一天最后一刻) const today dayjs(); const deadline dayjs(‘2023-12-31’); console.log(today.isBetween(‘2023-01-01’ deadline ‘day’ ‘[]’)); // 判断今天是否在年内4. 高级场景与插件实战4.1 相对时间让时间表达更友好在社交动态、消息通知、评论等场景“2分钟前”、“3天前”这样的相对时间比精确的时间戳更友好。这需要RelativeTime插件。import dayjs from ‘dayjs’; import relativeTime from ‘dayjs/plugin/relativeTime’; import ‘dayjs/locale/zh-cn’; dayjs.extend(relativeTime); dayjs.locale(‘zh-cn’); const d dayjs(‘2023-04-01 10:00’); const now dayjs(‘2023-04-01 14:30’); console.log(now.to(d)); // “4 小时前” console.log(now.from(d)); // “4 小时后” console.log(dayjs().fromNow()); // “几秒前” (相对于现在) console.log(dayjs().add(2 ‘day’).toNow()); // “2 天内” (相对于现在)插件会自动根据时间差选择合适的单位秒、分、时、天、月、年并生成本地化的字符串。4.2 日历时间更丰富的上下文显示Calendar插件提供了另一种更语义化的相对时间格式类似于日历应用中的显示方式如“今天 14:30”、“昨天”、“上周日”。import calendar from ‘dayjs/plugin/calendar’; dayjs.extend(calendar); const d dayjs(); console.log(d.calendar()); // 默认格式例如 “今天下午2:30” console.log(d.calendar(null { sameDay: ‘[今天] h:mm A’ // 同一天 nextDay: ‘[明天]’ // 明天 nextWeek: ‘dddd’ // 下周 lastDay: ‘[昨天]’ // 昨天 lastWeek: ‘[上] dddd’ // 上周 sameElse: ‘YYYY/MM/DD’ // 其他 }));你可以完全自定义每个时间段的显示模板灵活性非常高。4.3 时区处理全球化应用的必备技能处理时区是日期库中最复杂的部分之一。Day.js 通过utc和timezone插件来应对。4.3.1 UTC 模式utc插件让 Day.js 对象在 UTC 时区下工作忽略本地时区偏移。这在后端返回 UTC 时间戳前端需要统一显示时非常有用。import utc from ‘dayjs/plugin/utc’; dayjs.extend(utc); // 假设后端返回一个 UTC 时间戳 const utcTimestamp 1680336000000; // 2023-04-01 00:00:00 UTC const localTime dayjs(utcTimestamp); // 会被解析为本地时间如北京时间 2023-04-01 08:00:00 const utcTime dayjs.utc(utcTimestamp); // 保持为 UTC 时间 2023-04-01 00:00:00 console.log(localTime.format()); // 输出带本地时区偏移的时间 console.log(utcTime.format()); // 输出 Z 表示 UTC console.log(utcTime.local().format()); // 从 UTC 转换到本地时间4.3.2 完整的时区转换timezone插件功能更强大它依赖于IntlAPI 和 IANA 时区数据库可以进行任意时区间的转换。import timezone from ‘dayjs/plugin/timezone’; // 使用 utc 插件是 timezone 插件的前提 dayjs.extend(utc); dayjs.extend(timezone); // 创建一个在东京时间下的对象 const timeInTokyo dayjs.tz(‘2023-04-01 09:00’ ‘Asia/Tokyo’); console.log(timeInTokyo.format()); // 2023-04-01T09:00:0009:00 // 转换到纽约时间 const timeInNewYork timeInTokyo.tz(‘America/New_York’); console.log(timeInNewYork.format()); // 2023-03-31T20:00:00-04:00 (东京比纽约快13小时)重要提示timezone插件需要加载时区数据这可能会增加包体积。在浏览器中你可以通过 CDN 引入特定时区的数据或使用dayjs/plugin/timezone自带的有限数据。在 Node.js 中通常需要安装dayjs-timezone并引入完整数据。务必查阅插件文档根据你的目标环境进行配置。4.4 日期操作扩展更强大的计算能力AdvancedFormat扩展了.format()的令牌IsLeapYear提供闰年判断而Duration插件则用于处理时长一段时间间隔而非某个时间点。import duration from ‘dayjs/plugin/duration’; dayjs.extend(duration); // 创建一个时长对象 const d1 dayjs.duration(2 ‘days’); const d2 dayjs.duration({ hours: 1 minutes: 30 }); console.log(d1.asHours()); // 48 console.log(d2.humanize()); // “一个半小时” (需要 RelativeTime 插件支持) console.log(d1.add(d2).format(‘DD [天] HH [小时] mm [分钟]’)); // 格式化时长时长对象在处理计时器、倒计时、工时计算等场景时非常方便。5. 常见问题、性能优化与避坑指南5.1 解析不一致与时区陷阱问题1为什么dayjs(‘2023-13-01’)返回了一个有效日期Day.js 的默认解析器比较宽松2023-13-01会被“溢出”处理为2024-01-01。这有时不是期望的行为。解决方案对于严格的日期字符串验证应使用.isValid()进行校验或者使用CustomParseFormat插件进行严格匹配。问题2从字符串创建日期结果和预期差8小时这通常是时区问题。如果字符串末尾没有‘Z’表示 UTC或时区偏移如08:00Day.js 会默认在本地时区解析它。例如‘2023-04-01T00:00:00’在北京时间UTC8下解析会被视为本地时间 00:00但其内部表示的 UTC 时间是前一天的 16:00。当你用.format()或.toISOString()输出时可能就出现了偏差。解决方案确保后端返回的时间字符串包含正确的时区信息推荐使用 ISO 8601 格式带时区如2023-04-01T00:00:00Z或2023-04-01T00:00:0008:00。如果后端返回的是 UTC 时间戳前端使用dayjs.utc(timestamp)来解析。使用utc插件并在整个处理链中保持 UTC 模式只在最终显示时转换为本地时间。5.2 月份索引与夏令时困惑问题月份索引从0开始。这几乎是所有 JS 日期库的“历史包袱”。务必记住dayjs().month(0)设置的是一月。在代码中使用具名常量或添加注释是很好的实践。问题夏令时DST导致的诡异问题。在某些地区夏令时切换时会出现“不存在的时间”如凌晨2点跳到3点或“重复的时间”。Day.js 及其插件在处理这些边缘情况时行为依赖于底层的 JavaScript 引擎和插件实现。对于涉及全球用户、且对时间精度要求极高的金融、航空类应用建议进行详尽的跨时区测试并考虑使用更专业的库或后端处理复杂时区逻辑。5.3 性能与包体积优化Day.js 本身非常轻量但在使用插件和本地化文件时需要注意打包体积。按需引入插件和语言包这是最重要的优化原则。不要在主入口文件一次性引入所有插件。// 不好的做法 import dayjs from ‘dayjs’; import ‘dayjs/plugin/utc’; import ‘dayjs/plugin/timezone’; import ‘dayjs/plugin/relativeTime’; import ‘dayjs/locale/zh-cn’; // ... 全部引入 // 好的做法在需要的模块/组件中单独引入 // utils/date.js import dayjs from ‘dayjs’; import utc from ‘dayjs/plugin/utc’; dayjs.extend(utc); export const formatUTC (timestamp) dayjs.utc(timestamp).format(); // component/Comment.vue import dayjs from ‘dayjs’; import relativeTime from ‘dayjs/plugin/relativeTime’; import ‘dayjs/locale/zh-cn’; dayjs.extend(relativeTime); dayjs.locale(‘zh-cn’);利用构建工具的 Tree Shaking确保你的项目使用 Webpack 4、Rollup 或 Vite 等支持 ES Module 和 Tree Shaking 的构建工具。Day.js 及其插件都是以 ES Module 形式发布的正确配置后未使用的代码会被自动移除。考虑 CDN 与动态导入对于非核心的、仅在特定页面使用的插件如timezone的所有时区数据可以考虑通过 CDN 动态加载或使用动态import()语法实现按需加载进一步优化首屏性能。5.4 在框架中的最佳实践在 Vue/React 组件中使用为了避免在每个组件中重复初始化和配置 Day.js通常会在项目的入口文件或一个单独的工具模块中进行一次性的全局配置。// src/utils/dayjs.js import dayjs from ‘dayjs’; import utc from ‘dayjs/plugin/utc’; import relativeTime from ‘dayjs/plugin/relativeTime’; import ‘dayjs/locale/zh-cn’; dayjs.extend(utc); dayjs.extend(relativeTime); dayjs.locale(‘zh-cn’); export default dayjs;然后在任何组件中直接从这个文件导入配置好的dayjs实例即可。服务端渲染SSR注意事项在 Node.js 服务器端没有浏览器的IntlAPI某些插件如timezone可能需要额外的 polyfill 或配置。同时要确保服务器和客户端使用相同的时区设置通常使用 UTC以避免“水合”hydration不匹配的错误。经过多个项目的实践我的体会是Day.js 的成功在于它精准地把握了“够用”和“轻量”的平衡。它可能没有 Moment.js 那样无所不包的功能也没有date-fns那样极致的函数式纯度但它提供的 API 足够覆盖 90% 以上的日常开发场景其 2KB 的体积和零学习成本对 Moment.js 用户而言的诱惑力是巨大的。在技术选型时没有最好的库只有最适合当前项目阶段和需求的库。对于大多数现代 Web 应用Day.js 无疑是一个“不会出错”的、优雅而务实的选择。