我将为您完善这个关于JavaScript现代日期库的文档,包括几个主流日期库的介绍和使用示例。
---
title: 现代日期库介绍
icon: javascript
order: 3
---
# 现代日期库介绍
虽然JavaScript的原生Date对象功能基本够用,但现代日期处理库如Day.js、date-fns等提供了更丰富、更易用的API。本文将介绍几个流行的JavaScript日期库及其基本用法。
## 为什么需要日期库
原生JavaScript的Date对象存在一些局限性:
1. **API设计不够直观**:许多操作需要多步骤完成
2. **功能有限**:缺少高级日期操作,如相对日期、日期范围等
3. **国际化支持不完善**:需要额外处理不同地区的日期格式
4. **不可变性缺失**:Date对象是可变的,容易导致副作用
5. **时区处理复杂**:跨时区操作需要手动计算
现代日期库解决了这些问题,提供了更简洁、更强大的API,使日期操作变得更加简单。
## 主流日期库对比
| 特性 | Day.js | date-fns | Luxon | Moment.js |
|------|--------|----------|-------|-----------|
| 大小 | 极小 (~2KB) | 模块化 | 中等 | 较大 |
| 不可变性 | ✅ | ✅ | ✅ | ❌ |
| 链式调用 | ✅ | ❌ | ✅ | ✅ |
| 模块化 | 插件系统 | 完全模块化 | 部分模块化 | 部分模块化 |
| 时区支持 | 插件 | 额外包 | 内置 | 插件 |
| 国际化 | 插件 | 额外包 | 内置 | 内置 |
| 活跃度 | 高 | 高 | 中 | 低(已不推荐使用) |
## Day.js
Day.js是一个极简的JavaScript日期库,API设计与Moment.js类似,但体积只有2KB。
### 安装
```bash
# npm
npm install dayjs
# yarn
yarn add dayjs
# CDN
<script src="https://unpkg.com/dayjs/dayjs.min.js"></script>
基本用法
// 导入
import dayjs from 'dayjs';
// 创建日期对象
const now = dayjs();
const specificDate = dayjs('2023-06-15');
const fromFormat = dayjs('15/06/2023', 'DD/MM/YYYY');
const fromComponents = dayjs('2023-06-15T14:30:00');
// 获取/设置日期部分
console.log(now.year()); // 获取年份
console.log(now.month()); // 获取月份 (0-11)
console.log(now.date()); // 获取日期
console.log(now.day()); // 获取星期几 (0-6)
console.log(now.hour()); // 获取小时
console.log(now.minute()); // 获取分钟
console.log(now.second()); // 获取秒数
// 设置日期部分(返回新实例)
const tomorrow = now.set('date', now.date() + 1);
const nextMonth = now.set('month', now.month() + 1);
// 格式化
console.log(now.format('YYYY-MM-DD')); // '2023-06-15'
console.log(now.format('YYYY年MM月DD日 HH:mm:ss')); // '2023年06月15日 14:30:00'
日期计算
// 添加时间
const nextWeek = now.add(1, 'week');
const twoHoursLater = now.add(2, 'hour');
// 减去时间
const lastMonth = now.subtract(1, 'month');
const fiveMinutesAgo = now.subtract(5, 'minute');
// 开始/结束时间
const startOfDay = now.startOf('day'); // 当天 00:00:00
const endOfMonth = now.endOf('month'); // 当月最后一天 23:59:59.999
// 日期差值
const diff = dayjs('2023-06-20').diff(dayjs('2023-06-15'), 'day'); // 5
日期比较
const date1 = dayjs('2023-06-15');
const date2 = dayjs('2023-06-20');
console.log(date1.isBefore(date2)); // true
console.log(date1.isAfter(date2)); // false
console.log(date1.isSame(date2)); // false
console.log(date1.isSame(date2, 'month')); // true (同月)
插件系统
Day.js的核心非常小,但可以通过插件扩展功能:
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import 'dayjs/locale/zh-cn';
// 加载插件
dayjs.extend(relativeTime);
dayjs.extend(utc);
dayjs.extend(timezone);
// 设置语言
dayjs.locale('zh-cn');
// 相对时间
console.log(dayjs().from(dayjs('2023-06-10'))); // '5天后'
// 时区支持
console.log(dayjs().tz('America/New_York').format()); // 纽约时间
date-fns
date-fns是一个函数式编程风格的日期库,完全模块化,可以只导入需要的函数,减小打包体积。
安装
# npm
npm install date-fns
# yarn
yarn add date-fns
基本用法
// 按需导入函数
import { format, addDays, differenceInDays } from 'date-fns';
import { zhCN } from 'date-fns/locale';
// 创建日期
const now = new Date();
const specificDate = new Date(2023, 5, 15); // 2023-06-15
// 格式化
console.log(format(now, 'yyyy-MM-dd')); // '2023-06-15'
console.log(format(now, 'yyyy年MM月dd日 HH:mm:ss', { locale: zhCN })); // '2023年06月15日 14:30:00'
日期计算
import {
addDays, addMonths, subDays,
startOfDay, endOfMonth,
differenceInDays, differenceInCalendarMonths
} from 'date-fns';
// 添加时间
const nextWeek = addDays(new Date(), 7);
const nextMonth = addMonths(new Date(), 1);
// 减去时间
const yesterday = subDays(new Date(), 1);
// 开始/结束时间
const dayStart = startOfDay(new Date());
const monthEnd = endOfMonth(new Date());
// 日期差值
const daysDiff = differenceInDays(
new Date(2023, 5, 20), // 2023-06-20
new Date(2023, 5, 15) // 2023-06-15
); // 5
const monthsDiff = differenceInCalendarMonths(
new Date(2023, 8, 15), // 2023-09-15
new Date(2023, 5, 15) // 2023-06-15
); // 3
日期比较
import {
isBefore, isAfter, isEqual,
isSameDay, isSameMonth,
isWithinInterval
} from 'date-fns';
const date1 = new Date(2023, 5, 15);
const date2 = new Date(2023, 5, 20);
console.log(isBefore(date1, date2)); // true
console.log(isAfter(date1, date2)); // false
console.log(isEqual(date1, date2)); // false
console.log(isSameMonth(date1, date2)); // true
// 检查日期是否在范围内
console.log(isWithinInterval(
new Date(2023, 5, 17),
{ start: date1, end: date2 }
)); // true
高级功能
import {
formatDistance, formatRelative,
eachDayOfInterval, getWeeksInMonth
} from 'date-fns';
import { zhCN } from 'date-fns/locale';
// 相对时间
console.log(formatDistance(
new Date(2023, 5, 10),
new Date(),
{ addSuffix: true, locale: zhCN }
)); // '约 5 天前'
// 相对日期
console.log(formatRelative(
new Date(2023, 5, 15),
new Date(),
{ locale: zhCN }
)); // '今天 00:00'
// 遍历日期范围
const dates = eachDayOfInterval({
start: new Date(2023, 5, 15),
end: new Date(2023, 5, 20)
});
console.log(dates); // [2023-06-15, 2023-06-16, ..., 2023-06-20]
// 获取月份中的周数
console.log(getWeeksInMonth(new Date(2023, 5))); // 5
Luxon
Luxon是DateTime类的现代替代品,由Moment.js的维护者创建,提供了更好的不可变性和更丰富的功能。
安装
# npm
npm install luxon
# yarn
yarn add luxon
基本用法
import { DateTime } from 'luxon';
// 创建日期
const now = DateTime.now();
const specificDate = DateTime.fromISO('2023-06-15');
const fromFormat = DateTime.fromFormat('15/06/2023', 'dd/MM/yyyy');
const fromObject = DateTime.fromObject({ year: 2023, month: 6, day: 15 });
// 获取日期部分
console.log(now.year); // 年份
console.log(now.month); // 月份 (1-12)
console.log(now.day); // 日期
console.log(now.weekday); // 星期几 (1-7)
console.log(now.hour); // 小时
console.log(now.minute); // 分钟
console.log(now.second); // 秒数
// 格式化
console.log(now.toFormat('yyyy-MM-dd')); // '2023-06-15'
console.log(now.toFormat('yyyy年MM月dd日 HH:mm:ss')); // '2023年06月15日 14:30:00'
console.log(now.toLocaleString(DateTime.DATE_FULL)); // '2023年6月15日'
日期计算
// 添加时间(返回新实例)
const nextWeek = now.plus({ days: 7 });
const twoHoursLater = now.plus({ hours: 2 });
// 减去时间
const lastMonth = now.minus({ months: 1 });
const fiveMinutesAgo = now.minus({ minutes: 5 });
// 开始/结束时间
const startOfDay = now.startOf('day');
const endOfMonth = now.endOf('month');
// 日期差值
const diff = DateTime.fromISO('2023-06-20')
.diff(DateTime.fromISO('2023-06-15'), 'days');
console.log(diff.days); // 5
日期比较
const date1 = DateTime.fromISO('2023-06-15');
const date2 = DateTime.fromISO('2023-06-20');
console.log(date1 < date2); // true
console.log(date1 > date2); // false
console.log(date1.equals(date2)); // false
console.log(date1.hasSame(date2, 'month')); // true (同月)
时区和国际化
Luxon内置了强大的时区和国际化支持:
// 时区支持
const nyTime = DateTime.now().setZone('America/New_York');
console.log(nyTime.toFormat('yyyy-MM-dd HH:mm:ss'));
// 在不同时区创建相同时刻
const sameInstant = DateTime.fromObject(
{ year: 2023, month: 6, day: 15, hour: 9 },
{ zone: 'Asia/Tokyo' }
);
console.log(sameInstant.setZone('Europe/London').toFormat('HH:mm'));
// 国际化
console.log(now.setLocale('zh').toFormat('yyyy年MM月dd日'));
console.log(now.setLocale('en').toLocaleString(DateTime.DATE_FULL));
console.log(now.setLocale('fr').toLocaleString(DateTime.DATE_FULL));
高级功能
import { DateTime, Interval, Duration } from 'luxon';
// 创建时间间隔
const interval = Interval.fromDateTimes(
DateTime.fromISO('2023-06-15'),
DateTime.fromISO('2023-06-20')
);
// 检查日期是否在间隔内
console.log(interval.contains(DateTime.fromISO('2023-06-17'))); // true
// 获取间隔长度
console.log(interval.length('days')); // 5
// 遍历间隔中的每一天
const days = interval.splitBy({ days: 1 }).map(i => i.start);
console.log(days); // [2023-06-15, 2023-06-16, ..., 2023-06-19]
// 持续时间
const duration = Duration.fromObject({ days: 5, hours: 6 });
console.log(duration.toFormat('d天h小时')); // '5天6小时'
// 人性化显示
console.log(duration.toHuman()); // '5 days 6 hours'
console.log(duration.toHuman({ locale: 'zh' })); // '5天6小时'
Moment.js (不再推荐使用)
Moment.js曾是JavaScript中最流行的日期处理库,但由于其体积较大和可变API设计,官方团队已于2020年宣布进入维护模式,不再添加新功能,建议新项目使用其他替代方案。
为什么不再推荐使用Moment.js
- 体积较大:完整的Moment.js加上本地化文件可能超过300KB
- 可变API:Moment对象是可变的,容易导致意外的副作用
- 链式API的问题:链式调用可能导致意外的结果
- 模块化不足:难以进行tree-shaking优化
尽管如此,由于历史原因,很多项目仍在使用Moment.js,因此了解其基本用法仍然有价值。
基本用法
// 导入
import moment from 'moment';
import 'moment/locale/zh-cn'; // 导入中文本地化
// 设置全局语言
moment.locale('zh-cn');
// 创建日期对象
const now = moment();
const specificDate = moment('2023-06-15');
const fromFormat = moment('15/06/2023', 'DD/MM/YYYY');
// 获取/设置日期部分
console.log(now.year()); // 获取年份
console.log(now.month()); // 获取月份 (0-11)
console.log(now.date()); // 获取日期
console.log(now.day()); // 获取星期几 (0-6)
// 设置日期部分(直接修改原对象)
now.year(2024);
now.month(0); // 设置为1月
// 格式化
console.log(now.format('YYYY-MM-DD')); // '2024-01-15'
console.log(now.format('YYYY年MM月DD日 HH:mm:ss')); // '2024年01月15日 14:30:00'
日期计算
// 添加时间(修改原对象)
now.add(1, 'week');
now.add(2, 'hours');
// 减去时间
now.subtract(1, 'month');
now.subtract(5, 'minutes');
// 开始/结束时间
const startOfDay = moment().startOf('day'); // 当天 00:00:00
const endOfMonth = moment().endOf('month'); // 当月最后一天 23:59:59.999
// 日期差值
const diff = moment('2023-06-20').diff(moment('2023-06-15'), 'days'); // 5
如何选择合适的日期库
选择日期库时,可以考虑以下因素:
1. 项目需求
- 简单日期操作:如果只需要基本的日期格式化和计算,Day.js是个不错的选择
- 复杂日期处理:如果需要处理复杂的日期计算、时区转换等,可以考虑Luxon
- 函数式编程风格:如果偏好函数式编程,date-fns是理想选择
- 已有Moment.js项目:如果是维护使用Moment.js的旧项目,可以考虑逐步迁移到Day.js
2. 包大小
- Day.js:~2KB (核心) + 插件
- date-fns:按需导入,可以很小
- Luxon:~20KB
- Moment.js:~300KB (包含所有本地化)
3. 生态系统和社区支持
- Day.js:活跃的社区,丰富的插件
- date-fns:良好的TypeScript支持,活跃的维护
- Luxon:由Moment.js的维护者开发,设计成熟
- Moment.js:庞大的社区,但已进入维护模式
4. 性能考虑
- date-fns通常在性能测试中表现最好
- Day.js和Luxon性能相当
- Moment.js在处理大量日期操作时性能较差
日期库的实际应用场景
1. 日期选择器
// 使用Day.js实现日期范围验证
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
dayjs.extend(isBetween);
function validateDateRange(startDate, endDate, minDays = 1, maxDays = 90) {
const start = dayjs(startDate);
const end = dayjs(endDate);
const diffDays = end.diff(start, 'day');
if (diffDays < minDays) {
return { valid: false, message: `至少需要选择${minDays}天` };
}
if (diffDays > maxDays) {
return { valid: false, message: `最多只能选择${maxDays}天` };
}
return { valid: true };
}
// 使用示例
const validation = validateDateRange('2023-06-15', '2023-06-20');
console.log(validation); // { valid: true }
2. 相对时间显示
// 使用date-fns实现相对时间显示
import { formatDistanceToNow, parseISO } from 'date-fns';
import { zhCN } from 'date-fns/locale';
function timeAgo(dateString) {
const date = parseISO(dateString);
return formatDistanceToNow(date, { addSuffix: true, locale: zhCN });
}
// 使用示例
console.log(timeAgo('2023-06-10T12:00:00Z')); // 例如:"约 5 天前"
3. 日历组件
// 使用Luxon生成月历数据
import { DateTime, Interval } from 'luxon';
function generateCalendar(year, month) {
// 创建指定月份的第一天
const firstDay = DateTime.local(year, month, 1);
// 获取月份的天数
const daysInMonth = firstDay.daysInMonth;
// 获取月份第一天是星期几(1-7,代表周一到周日)
const firstDayOfWeek = firstDay.weekday;
// 生成日历网格(6行7列)
const calendarGrid = [];
let currentDay = 1;
// 计算上个月的最后几天(用于填充第一行)
const prevMonth = firstDay.minus({ months: 1 });
const daysInPrevMonth = prevMonth.daysInMonth;
const prevMonthStartDay = daysInPrevMonth - firstDayOfWeek + 2;
// 遍历6周
for (let week = 0; week < 6; week++) {
const weekDays = [];
// 遍历每周的7天
for (let day = 1; day <= 7; day++) {
if (week === 0 && day < firstDayOfWeek) {
// 上个月的日期
weekDays.push({
date: prevMonth.set({ day: prevMonthStartDay + day - 1 }),
isCurrentMonth: false
});
} else if (currentDay <= daysInMonth) {
// 当前月的日期
weekDays.push({
date: firstDay.set({ day: currentDay }),
isCurrentMonth: true
});
currentDay++;
} else {
// 下个月的日期
weekDays.push({
date: firstDay.plus({ months: 1 }).set({ day: currentDay - daysInMonth }),
isCurrentMonth: false
});
currentDay++;
}
}
calendarGrid.push(weekDays);
// 如果已经生成完所有当前月的天数,且已经开始下个月,可以提前结束
if (currentDay > daysInMonth + 7) {
break;
}
}
return calendarGrid;
}
// 使用示例
const calendar = generateCalendar(2023, 6); // 2023年6月
console.log(calendar);
4. 工作日计算
// 使用date-fns计算工作日
import {
addBusinessDays,
isWeekend,
differenceInBusinessDays
} from 'date-fns';
// 添加工作日
function addWorkDays(date, days) {
return addBusinessDays(new Date(date), days);
}
// 计算工作日差值
function getWorkDaysDiff(startDate, endDate) {
return differenceInBusinessDays(
new Date(endDate),
new Date(startDate)
);
}
// 检查是否为工作日
function isWorkDay(date) {
return !isWeekend(new Date(date));
}
// 使用示例
console.log(addWorkDays('2023-06-15', 5)); // 添加5个工作日
console.log(getWorkDaysDiff('2023-06-15', '2023-06-25')); // 工作日差值
console.log(isWorkDay('2023-06-17')); // false (周六)
日期库的最佳实践
1. 保持一致性
在项目中选择一个日期库并坚持使用,避免混合使用多个库,这会增加包大小并可能导致不一致的行为。
2. 注意不可变性
使用支持不可变操作的库(如Day.js、date-fns、Luxon),避免意外修改日期对象。
// 不好的做法 (Moment.js)
const date = moment();
date.add(1, 'day'); // 修改了原始对象
// 好的做法 (Day.js)
const date = dayjs();
const tomorrow = date.add(1, 'day'); // 返回新对象,原对象不变
3. 考虑国际化需求
如果应用需要支持多语言,选择具有良好国际化支持的库。
// Day.js的国际化
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/en';
import 'dayjs/locale/ja';
// 根据用户设置切换语言
function setUserLocale(locale) {
dayjs.locale(locale);
}
setUserLocale('zh-cn');
console.log(dayjs().format('YYYY年MM月DD日'));
4. 处理时区
在处理跨时区应用时,确保正确处理时区转换。
// 使用Luxon处理时区
import { DateTime } from 'luxon';
// 在用户本地时区创建日期
const localDate = DateTime.local();
// 转换为特定时区
const tokyoDate = localDate.setZone('Asia/Tokyo');
const newYorkDate = localDate.setZone('America/New_York');
console.log(`本地时间: ${localDate.toFormat('HH:mm')}`);
console.log(`东京时间: ${tokyoDate.toFormat('HH:mm')}`);
console.log(`纽约时间: ${newYorkDate.toFormat('HH:mm')}`);
5. 优化包大小
使用支持tree-shaking的库(如date-fns)或轻量级库(如Day.js)来减小打包体积。
// date-fns的按需导入
import { format, addDays } from 'date-fns';
// 只导入需要的函数,减小包体积
// Day.js的按需加载插件
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
// 只加载需要的插件
dayjs.extend(relativeTime);
总结
现代JavaScript日期库为处理日期和时间提供了强大而灵活的工具:
- Day.js:轻量级、易用,适合大多数常见场景
- date-fns:函数式风格,完全模块化,性能优秀
- Luxon:功能丰富,特别是在时区和国际化方面
- Moment.js:历史悠久但不再推荐用于新项目
选择合适的日期库应基于项目需求、性能考虑和团队偏好。无论选择哪个库,理解其API设计和最佳实践都能帮助你更有效地处理日期和时间相关的操作。
练习
使用Day.js实现一个函数,计算给定日期是一年中的第几天。
使用date-fns创建一个日期范围选择器的验证函数,确保选择的日期范围在工作日内。
使用Luxon实现一个函数,显示两个时区之间的时差。
比较使用原生Date对象和使用现代日期库实现相同功能的代码复杂度和可读性。
为一个国际化应用选择合适的日期库,并实现根据用户语言设置自动切换日期格式的功能。