Day.js 前端日期处理:从轻量入门到实战优化 1. Day.js 入门为什么选择它如果你在前端项目里处理过日期和时间大概率听说过 Moment.js。它功能强大但体积也大动辄几十KB的大小在现代追求极致性能的打包环境下有时显得过于“臃肿”。而 Day.js 的出现就是为了解决这个痛点。它被设计为 Moment.js 的极简替代品拥有几乎完全相同的 API但核心包大小只有 2KB 左右。这意味着你可以用几乎零成本的学习代价获得一个轻量、快速、功能齐全的日期处理库。我第一次在项目里全面切换到 Day.js是因为一个性能审计报告。报告指出我们的首屏加载时间有近 100ms 花在了 Moment.js 及其本地化文件的解析上。换成 Day.js 后这个开销几乎可以忽略不计打包体积肉眼可见地缩小了。对于大多数日常的日期格式化、计算、比较操作Day.js 完全够用。它的核心理念是“按需加载”核心库只提供最基础的功能如果你需要时区、日历、相对时间等高级功能再通过插件引入真正做到“小而美”。所以无论你是刚入门的前端新手还是正在为项目性能优化寻找方案的老手Day.js 都是一个值得深入学习和使用的工具。接下来我会结合我多年的使用经验带你从零开始掌握 Day.js 最常用、最核心的方法并分享一些官方文档里不会写的实战技巧和避坑指南。2. 核心安装与基础实例化使用 Day.js 的第一步自然是把它引入到你的项目中。现在主流的项目构建方式无论是通过 npm/yarn/pnpm 安装还是直接通过 CDN 引入都非常方便。2.1 安装与引入对于现代前端项目通过包管理器安装是最推荐的方式npm install dayjs # 或 yarn add dayjs # 或 pnpm add dayjs安装完成后在你的 JavaScript 或 TypeScript 文件中就可以通过import或require来引入它了。// ES Modules import dayjs from dayjs; // CommonJS const dayjs require(dayjs);如果你是在一个简单的 HTML 页面中快速尝试也可以直接使用 CDNscript srchttps://unpkg.com/dayjs1/dayjs.min.js/script script // 此时 dayjs 已作为全局变量可用 console.log(dayjs().format(YYYY-MM-DD)); /script2.2 创建 Day.js 对象Day.js 的核心是一个不可变的Immutable日期对象。这意味着任何对它的操作都会返回一个新的实例而不会改变原始对象。这个特性在函数式编程和避免副作用时非常有用。创建 Day.js 对象主要有以下几种方式1. 获取当前时间这是最常用的方式直接调用dayjs()即可。const now dayjs(); // 创建一个代表当前时刻的 Day.js 对象 console.log(now); // 输出: Dayjs 对象例如 d { $L: en, ... }2. 解析字符串Day.js 可以解析符合 ISO 8601 标准的字符串或者一些常见的日期字符串格式。// ISO 8601 格式 (推荐) const date1 dayjs(2023-10-27); const date2 dayjs(2023-10-27T15:30:00.000Z); // 带有时区的 ISO 字符串 // 其他常见格式 (但行为可能因浏览器而异不推荐) const date3 dayjs(2023/10/27); const date4 dayjs(Oct 27, 2023);注意强烈建议使用 ISO 8601 (YYYY-MM-DD) 或时间戳来初始化这是最可靠、跨浏览器兼容性最好的方式。解析像MM/DD/YYYY这样的字符串在不同地区的浏览器中可能会得到不同的结果比如美国格式10/27/2023在美国浏览器是 10月27日在其他地区可能被解析为 27月10日。3. 解析时间戳传递一个 Unix 时间戳毫秒数或 JavaScript 的Date对象。// Unix 时间戳 (毫秒) const timestamp 1698412800000; const dateFromTimestamp dayjs(timestamp); // JavaScript Date 对象 const jsDate new Date(); const dateFromJsDate dayjs(jsDate);4. 使用对象参数你可以传递一个对象来指定年、月、日等。这里的月份是从 0 开始计数的0 代表一月和 JavaScript 的Date对象保持一致。const customDate dayjs({ year: 2023, month: 9, // 注意10月对应的是 9 day: 27, hour: 14, minute: 30, second: 0, millisecond: 0 }); console.log(customDate.format()); // 输出: 2023-10-27T14:30:0008:00 (取决于本地时区)理解并正确使用这些实例化方法是避免后续日期计算错误的第一步。我见过不少坑都是因为用dayjs(12/25/2023)这样的字符串初始化结果在非美国环境的服务器上运行时日期完全错乱。所以我的第一条实战心得就是坚持使用 ISO 8601 格式或时间戳来创建 Day.js 对象。3. 格式化与显示让日期“说人话”我们处理日期最终大多是为了展示给用户看。Day.js 的.format()方法是使用频率最高的方法之一它允许你按照指定的令牌tokens将日期对象转换成可读的字符串。3.1 基础格式化令牌Day.js 支持的格式化令牌和 Moment.js 高度一致。下面是一些最常用的令牌输出示例描述YYYY20234位数的年份YY232位数的年份MM102位数的月份01-12M10月份不足两位不补零1-12DD272位数的日期01-31D27日期不足两位不补零1-31HH1424小时制的小时00-23H1424小时制小时不补零0-23hh0212小时制的小时01-12h212小时制小时不补零1-12mm05分钟00-59m5分钟不补零0-59ss09秒00-59s9秒不补零0-59APM大写 AM/PMapm小写 am/pmdddFri星期的缩写Sun, Mon...ddddFriday星期的全称3.2 常用格式化示例const date dayjs(2023-10-27T14:05:09); console.log(date.format(YYYY-MM-DD)); // 输出: 2023-10-27 console.log(date.format(YYYY/MM/DD HH:mm:ss)); // 输出: 2023/10/27 14:05:09 console.log(date.format(DD/MM/YYYY)); // 输出: 27/10/2023 (欧洲常见格式) console.log(date.format(MMM D, YYYY)); // 输出: Oct 27, 2023 (英文缩写) console.log(date.format(YYYY年MM月DD日 dddd)); // 输出: 2023年10月27日 Friday console.log(date.format(h:mm A)); // 输出: 2:05 PM3.3 本地化格式化默认情况下Day.js 使用英语环境。如果你需要显示中文或其他语言的月份、星期就需要引入本地化插件。这是 Day.js “按需加载”哲学的典型体现。首先安装并引入本地化插件和对应的语言包npm install dayjs npm install dayjs/locale/zh-cn # 以中文为例import dayjs from dayjs; import dayjs/locale/zh-cn; // 引入中文语言包 dayjs.locale(zh-cn); // 全局设置为中文 const date dayjs(2023-10-27); console.log(date.format(YYYY年MM月DD日 dddd)); // 输出: 2023年10月27日 星期五 console.log(date.format(MMMM)); // 输出: 十月你也可以只为某个特定的 Day.js 对象设置语言而不影响全局import zh from dayjs/locale/zh-cn; const date dayjs(2023-10-27); console.log(date.locale(zh).format(dddd)); // 输出: 星期五实战技巧在大型多语言项目中我通常会在应用初始化时根据用户的语言偏好动态加载对应的 Day.js 语言包并设置全局语言环境。这样可以确保所有日期显示都与应用界面语言一致。同时要注意语言包的打包可以使用动态导入import()来避免将所有语言包都打进主包。4. 日期的获取与判断创建和格式化日期之后我们经常需要从中提取特定的部分如年、月、日或者对日期进行各种判断。Day.js 提供了一系列.get()和.isXxx()方法。4.1 获取日期各部分的值这些方法非常直观返回的是对应部分的数值。const date dayjs(2023-10-27T14:30:45.123); console.log(date.year()); // 输出: 2023 console.log(date.month()); // 输出: 9 (注意月份从0开始0一月9十月) console.log(date.date()); // 输出: 27 (一个月中的第几天) console.log(date.day()); // 输出: 5 (星期几0星期日5星期五) console.log(date.hour()); // 输出: 14 console.log(date.minute()); // 输出: 30 console.log(date.second()); // 输出: 45 console.log(date.millisecond()); // 输出: 123注意.month()返回 0-11这常常是新手混淆的地方。在显示给用户时记得要加 1date.month() 1。而.date()是获取“日”不是获取“日期对象”这命名和 jQuery 的.date()方法容易混淆需要留意。4.2 日期的比较与判断比较日期是业务逻辑中的常见操作比如判断一个任务是否过期或者比较两个日期的先后。1. 比较前后const dateA dayjs(2023-10-27); const dateB dayjs(2023-11-01); console.log(dateA.isBefore(dateB)); // 输出: true (dateA 在 dateB 之前) console.log(dateB.isAfter(dateA)); // 输出: true (dateB 在 dateA 之后) console.log(dateA.isSame(dateB)); // 输出: false (是否相同)你还可以指定比较的精度单位const date1 dayjs(2023-10-27T10:00:00); const date2 dayjs(2023-10-27T15:30:00); console.log(date1.isSame(date2, day)); // 输出: true (同一天) console.log(date1.isSame(date2, hour)); // 输出: false (不是同一小时) console.log(date1.isBefore(date2, minute)); // 输出: true (在分钟粒度上date1 早于 date2)支持的精度单位有year,month,week,day,hour,minute,second,millisecond。2. 判断范围const start dayjs(2023-10-01); const end dayjs(2023-10-31); const checkDate dayjs(2023-10-15); console.log(checkDate.isBetween(start, end)); // 输出: true (默认包含起止日期) console.log(checkDate.isBetween(start, end, null, ())); // 输出: true (开区间不包含起止) // 第三个参数是精度null 表示默认第四个参数是区间表示法[](包含), ()(不包含), [)(左包含), (](右包含)3. 其他常用判断const date dayjs(2023-02-28); console.log(date.isLeapYear()); // 输出: false (2023不是闰年) const leapYearDate dayjs(2024-02-29); console.log(leapYearDate.isLeapYear()); // 输出: true // 判断一个 Day.js 对象是否有效 const invalidDate dayjs(Invalid Date); console.log(invalidDate.isValid()); // 输出: false console.log(dayjs(2023-10-27).isValid()); // 输出: true.isValid()方法非常实用。在解析用户输入或外部 API 返回的日期字符串时总有可能遇到非法格式。在后续操作前先进行有效性校验可以避免很多运行时错误。避坑指南在比较日期时特别是涉及“天”的比较要特别注意时区问题。dayjs(‘2023-10-27’)创建的是一个本地时区的“天”的开始通常是 00:00:00。如果你用isSame(‘2023-10-27’, ‘day’)去比较一个带具体时间的日期结果可能符合预期。但如果是跨时区的系统比如前端是北京时间后端返回 UTC 时间直接比较字符串或时间戳就可能出错。稳妥的做法是在比较“天”时双方都统一用.format(‘YYYY-MM-DD’)转换成字符串再比较或者使用.startOf(‘day’)方法将时间归零到同一起点再比较。5. 日期的操作与计算Day.js 的日期操作非常强大且链式调用友好。所有操作都会返回一个新的 Day.js 实例符合不可变原则。5.1 增加与减少时间使用.add()和.subtract()方法第一个参数是数值第二个参数是单位。let date dayjs(2023-10-27); // 增加 console.log(date.add(7, day).format(YYYY-MM-DD)); // 输出: 2023-11-03 console.log(date.add(2, month).format(YYYY-MM-DD)); // 输出: 2023-12-27 console.log(date.add(1, year).format(YYYY-MM-DD)); // 输出: 2024-10-27 console.log(date.add(90, minute).format(HH:mm)); // 输出: 01:30 (假设原时间是 00:00) // 减少 console.log(date.subtract(1, day).format(YYYY-MM-DD)); // 输出: 2023-10-26 console.log(date.subtract(3, hour).format(HH:mm)); // 输出: 21:00 (假设原时间是 00:00)支持的单位和比较时一样year,month,week,day,hour,minute,second,millisecond。链式调用示例const newDate dayjs(2023-10-27 09:00) .add(2, hour) .subtract(30, minute) .add(1, day); console.log(newDate.format(YYYY-MM-DD HH:mm)); // 输出: 2023-10-28 10:305.2 设置时间的开始与结束.startOf()和.endOf()方法用于将日期时间调整到一个时间单位的开始或结束。这在生成报表如“今日数据”、“本月数据”时极其有用。const date dayjs(2023-10-27T14:30:45); console.log(date.startOf(day).format()); // 输出当天 00:00:00 console.log(date.startOf(month).format()); // 输出当月第一天 00:00:00 console.log(date.startOf(year).format()); // 输出当年第一天 00:00:00 console.log(date.startOf(hour).format()); // 输出: 2023-10-27T14:00:00 console.log(date.endOf(day).format()); // 输出当天 23:59:59.999 console.log(date.endOf(month).format()); // 输出当月最后一天 23:59:59.999 console.log(date.endOf(year).format()); // 输出当年最后一天 23:59:59.999 console.log(date.endOf(hour).format()); // 输出: 2023-10-27T14:59:59.999实战场景假设你要查询“今天”的订单数据。后端 API 通常需要开始时间和结束时间。错误的做法是传dayjs().format(‘YYYY-MM-DD’)作为开始和结束这相当于只传了一个日期字符串精度不对。正确的做法是const todayStart dayjs().startOf(day).toISOString(); // 或 .format() const todayEnd dayjs().endOf(day).toISOString(); // 将 todayStart 和 todayEnd 作为时间范围参数传给后端这样就能准确覆盖从今天 00:00:00.000 到 23:59:59.999 的所有数据。5.3 差异计算计算两个日期之间的差值使用.diff()方法。const date1 dayjs(2023-10-27); const date2 dayjs(2023-11-05); // 计算相差的天数 console.log(date2.diff(date1, day)); // 输出: 9 // 计算相差的月数 console.log(date2.diff(date1, month)); // 输出: 0 (不足一个月) console.log(date2.diff(date1, month, true)); // 输出: 0.2903225806451613 (浮点数精确差值) // 计算相差的小时数 const time1 dayjs(2023-10-27T09:00); const time2 dayjs(2023-10-27T17:30); console.log(time2.diff(time1, hour)); // 输出: 8 console.log(time2.diff(time1, hour, true)); // 输出: 8.5.diff()的第三个参数true表示返回浮点数结果否则返回整数向下取整。在计算年龄、工龄等需要精确到小数位的场景时这个参数很有用。避坑指南.diff()的结果是date2 - date1。如果date2在date1之后结果是正数反之是负数。这个顺序很容易搞反。我个人的记忆方法是“diff”求的是“差距”date2.diff(date1)就是“date2 比 date1 多多少”。6. 实用插件扩展核心能力Day.js 的核心非常轻量这意味着一些高级功能需要通过插件来实现。这种设计保证了基础用法的极致性能同时又能灵活扩展。下面介绍几个最常用、几乎是项目必备的插件。6.1 RelativeTime 插件显示“相对时间”“3分钟前”、“2天后”这样的相对时间表述在社交动态、消息通知里非常常见。RelativeTime 插件让这个功能变得非常简单。安装与引入npm install dayjs npm install dayjs/plugin/relativeTimeimport dayjs from dayjs; import relativeTime from dayjs/plugin/relativeTime; dayjs.extend(relativeTime); // 扩展 dayjs 功能 const now dayjs(); const pastDate dayjs().subtract(3, hour); const futureDate dayjs().add(2, day); console.log(pastDate.from(now)); // 输出: 3 hours ago console.log(now.from(pastDate)); // 输出: in 3 hours (视角不同) console.log(futureDate.from(now)); // 输出: in 2 days // 也可以使用 .to() 方法语义是“到某个时间” console.log(now.to(futureDate)); // 输出: in 2 days本地化相对时间同样相对时间的文字也需要本地化。你需要同时引入本地化语言包插件会自动使用当前语言环境。import dayjs from dayjs; import relativeTime from dayjs/plugin/relativeTime; import dayjs/locale/zh-cn; dayjs.extend(relativeTime); dayjs.locale(zh-cn); const now dayjs(); console.log(now.subtract(3, hour).from(now)); // 输出: 3小时前 console.log(now.add(2, day).from(now)); // 输出: 2天后实战技巧在渲染一个动态列表如评论列表时如果每条评论都实时计算from(now)而now是当前时间会导致每次渲染结果都不同可能引发不必要的组件重渲染。一个优化策略是在数据层或第一次渲染时就计算好相对时间字符串并缓存或者使用“时间阈值”例如超过7天的显示绝对日期如 10月20日7天内的才显示相对时间。6.2 CustomParseFormat 插件解析自定义格式前面提到Day.js 核心库对非 ISO 格式的字符串解析支持有限。如果你的数据源日期格式是DD/MM/YYYY或MM-DD-YYYY HH:mm这类自定义格式就需要这个插件。安装与引入npm install dayjs npm install dayjs/plugin/customParseFormatimport dayjs from dayjs; import customParseFormat from dayjs/plugin/customParseFormat; dayjs.extend(customParseFormat); // 现在可以解析自定义格式了 const date1 dayjs(27/10/2023, DD/MM/YYYY); const date2 dayjs(10-27-2023 14:30, MM-DD-YYYY HH:mm); const date3 dayjs(2023年10月27日, YYYY年MM月DD日); console.log(date1.format(YYYY-MM-DD)); // 输出: 2023-10-27 console.log(date2.format()); // 输出本地时区的 ISO 格式 console.log(date3.format(YYYY/MM/DD)); // 输出: 2023/10/27格式令牌插件使用的格式令牌和.format()方法基本一致。这保证了学习和使用的一致性。重要提醒使用此插件解析时必须明确提供格式字符串作为第二个参数否则 Day.js 会回退到默认的解析器很可能得到无效日期。6.3 UTC 插件处理时区难题时区是日期处理中最复杂的部分之一。Day.js 的 UTC 插件允许你创建一个 UTC 模式的 Day.js 对象所有操作都将在 UTC 时间下进行不受本地时区影响。安装与引入npm install dayjs npm install dayjs/plugin/utcimport dayjs from dayjs; import utc from dayjs/plugin/utc; dayjs.extend(utc); // 创建一个 UTC 时间对象 const utcDate dayjs.utc(2023-10-27T14:30:00Z); // Z 表示 UTC 时间 console.log(utcDate.format()); // 输出: 2023-10-27T14:30:00Z // 本地时间与 UTC 时间转换 const localDate dayjs(); // 本地时间 console.log(localDate.format()); // 输出本地时区时间如 2023-10-27T22:30:0008:00 const toUTCDate localDate.utc(); // 转换为 UTC 时间对象 console.log(toUTCDate.format()); // 输出: 2023-10-27T14:30:00Z const backToLocal toUTCDate.local(); // 再转换回本地时间 console.log(backToLocal.format()); // 输出本地时区时间 // 使用 UTC 模式进行格式化 console.log(dayjs.utc().format(YYYY-MM-DD HH:mm:ss)); // 输出当前的 UTC 时间为什么需要 UTC 插件考虑这个场景你的服务器在美国东部UTC-5用户在中国UTC8。服务器存储所有时间戳为 UTC 时间。如果没有统一处理前端直接new Date(timestamp)或dayjs(timestamp)显示出来的就是用户的本地时间。这可能导致“日期”错乱——比如 UTC 时间2023-10-27T23:00:00Z在北京用户看来是2023-10-28 07:00:00日期变成了第二天。使用 UTC 插件你可以确保在计算、比较“日期”时如“是否在同一天”是基于 UTC 日期来判断避免因时区产生的歧义。更复杂的时区处理对于需要支持全球多时区显示如日历应用、跨国会议系统UTC 插件可能还不够你需要更强大的timezone插件来在任意时区之间进行转换。但对于大多数“在后端统一用 UTC 存储和传输前端主要做显示和本地化计算”的场景UTC 插件加上.utc()、.local()方法已经足够。7. 实战综合案例与性能优化掌握了核心方法和常用插件我们来看几个真实的综合案例并探讨一下在大型应用中如何高效、正确地使用 Day.js。7.1 案例一生成一个日期范围选择器的默认值假设你需要为“最近30天”这个筛选条件生成开始和结束日期。import dayjs from dayjs; function getLast30DaysRange() { const endDate dayjs().endOf(day); // 结束日期为今天结束时刻 const startDate dayjs().subtract(29, day).startOf(day); // 开始日期为30天前含今天的开始时刻 // 注意subtract(29, day) 是因为要包含今天所以是最近30天而非前30天。 return { start: startDate.format(YYYY-MM-DD), // 根据后端接口要求返回格式 end: endDate.format(YYYY-MM-DD), // 或者返回时间戳 startTimestamp: startDate.valueOf(), endTimestamp: endDate.valueOf(), // 或者返回 ISO 字符串 startISO: startDate.toISOString(), endISO: endDate.toISOString() }; } const range getLast30DaysRange(); console.log(range); // 输出示例假设今天是2023-10-27: // { // start: 2023-09-28, // end: 2023-10-27, // startTimestamp: 1695830400000, // endTimestamp: 1698422399999, // startISO: 2023-09-27T16:00:00.000Z, // 注意时区转换 // endISO: 2023-10-27T15:59:59.999Z // }关键点使用.startOf(‘day’)和.endOf(‘day’)来确保范围覆盖完整的一天避免因时间部分缺失导致查询遗漏边界数据。7.2 案例二计算任务剩余时间倒计时实现一个显示“距离截止时间还有X天X小时X分”的倒计时功能。import dayjs from dayjs; import duration from dayjs/plugin/duration; // 需要 duration 插件 import relativeTime from dayjs/plugin/relativeTime; dayjs.extend(duration); dayjs.extend(relativeTime); function getTimeRemaining(deadlineISOString) { const now dayjs(); const deadline dayjs(deadlineISOString); if (now.isAfter(deadline)) { return 已过期; } // 方法1: 使用 .diff() 计算总毫秒数再用 duration 格式化 const diffMs deadline.diff(now); const durationObj dayjs.duration(diffMs); const days Math.floor(durationObj.asDays()); const hours durationObj.hours(); const minutes durationObj.minutes(); const seconds durationObj.seconds(); return ${days}天${hours}小时${minutes}分${seconds}秒; // 方法2: 使用 .to() 方法获得相对时间描述 (更简洁但格式固定) // return deadline.to(now); // 输出类似 in 2 days } // 使用 const deadline dayjs().add(2, day).add(5, hour).add(30, minute).toISOString(); console.log(getTimeRemaining(deadline)); // 输出类似 2天5小时29分55秒这个案例引入了Duration 插件(dayjs/plugin/duration)。它用于处理时间段时长而不是某个时间点。.duration()方法可以很好地解析毫秒数并让你方便地获取天、小时、分钟等各部分的值。对于复杂的时长计算和显示这个插件几乎是必需的。7.3 性能优化与最佳实践单例与复用在频繁创建 Day.js 对象的循环或高频函数中考虑复用对象。例如在渲染一个包含大量日期的列表时不要在每次渲染的组件函数内部都调用dayjs()获取当前时间而应该在外面获取一次通过 props 或 context 传入。避免在渲染中频繁计算像上面倒计时案例如果直接在 React/Vue 组件的渲染函数中调用getTimeRemaining并且使用setInterval每秒更新会导致每秒都创建新的 Day.js 对象和进行大量计算。更好的做法是将计算逻辑和状态更新放在组件的副作用如useEffect,setInterval中只更新一个简单的字符串状态到视图。谨慎使用插件只引入你确实需要的插件。每个插件都会增加一点点打包体积和解析开销。如果你只需要格式化就不要引入 RelativeTime 和 UTC 插件。利用 Tree Shaking确保你的打包工具能移除未使用的代码。时区一致性在整个项目中确立时区处理规范。我推荐的做法是后端永远以 UTC 时间戳或 ISO 8601 字符串带Z存储和传输时间。前端接收到时间数据后用dayjs.utc(timestamp)或dayjs(isoString)解析如果字符串带Zdayjs()会正确解析为本地时区对应的时间但理解上可能混淆显式使用.utc()更清晰。在需要进行“日期”比较如是否是同一天或显示“日期部分”时使用.utc()模式下的.format(‘YYYY-MM-DD’)或.startOf(‘day’)以确保全球用户看到的“日期”逻辑一致。在显示给用户时再调用.local().format(‘YYYY-MM-DD HH:mm:ss’)转换为其本地时间。 这套规则能最大程度减少时区引起的 bug。输入验证对于任何来自用户输入或第三方 API 的日期字符串在使用前先用.isValid()检查其有效性。无效的日期对象在进行操作时可能会抛出错误或返回不可预期的结果。Day.js 以其小巧的身躯和强大的 API 兼容性已经成为现代前端日期处理的事实标准之一。从简单的格式化到复杂的时区计算它通过“核心插件”的优雅设计提供了完整的解决方案。理解其不可变性、熟练使用常用方法、并根据项目需求明智地选择插件你就能轻松应对绝大多数前端日期时间相关的开发需求。