还在为JavaScript的日期处理头疼?一个强大的原生日期工具类让你告别第三方依赖!
在Web开发中,日期处理无处不在。从社交媒体的"3分钟前",到电商网站的订单时间,再到日历应用的日程安排,几乎每个项目都需要处理日期格式化、时间计算、相对时间显示等功能。很多开发者因为JavaScript原生Date对象的"难用"而选择了Moment.js或date-fns等库,但其实我们完全可以用原生JavaScript打造一个功能强大、使用简单的日期处理工具。
想象你在开发一个类似微博的时间线功能:
张三 发布了一条动态
• 刚刚发布 (不到1分钟)
• 3分钟前
• 1小时前
• 昨天 18:30
• 2024年1月15日
不同的时间距离需要不同的显示方式,这就需要智能的相对时间处理。
在订单管理系统中:
订单号: #2024011501
下单时间: 2024年1月15日 14:30:25
预计送达: 2024年1月17日
状态: 已发货 (1天前)
需要处理订单时间格式化、计算送达时间、显示状态更新的相对时间等。
开发日历组件时需要:
• 获取每月的第一天和最后一天
• 计算某天是周几
• 判断是否是今天、本周、本月
• 处理时区问题
• 格式化显示不同的日期格式
在开始我们的解决方案之前,先看看原生Date对象有哪些不便之处:
// 想要显示 "2024年1月15日",原生方式很麻烦
const date = new Date('2024-01-15');
const year = date.getFullYear();
const month = date.getMonth() + 1; // 注意:月份从0开始!
const day = date.getDate();
const formatted = `${year}年${month}月${day}日`; // 需要手动拼接
// 计算"多久之前"需要大量代码
function getRelativeTime(date) {
const now = newDate();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return'刚刚';
if (minutes < 60) return`${minutes}分钟前`;
if (hours < 24) return`${hours}小时前`;
if (days < 7) return`${days}天前`;
// ... 还需要更多判断
}
// 想要获取30天后的日期
const today = new Date();
const future = new Date(today);
future.setDate(today.getDate() + 30); // 需要理解setDate的机制
// 想要获取当天的开始时间(00:00:00)
const startOfDay = new Date(today);
startOfDay.setHours(0, 0, 0, 0); // 需要记住参数顺序
// 原生Date对象是可变的,容易造成意外修改
const originalDate = new Date('2024-01-15');
const modifiedDate = originalDate;
modifiedDate.setDate(20); // 这会同时修改originalDate!
console.log(originalDate); // 意外被修改了
现在让我们来打造一个真正好用的日期处理工具:
class DateHelper {
constructor(date = new Date()) {
// 确保内部存储的是Date对象的副本,保证不可变性
this.date = newDate(date);
}
// 静态工厂方法,方便链式调用
static create(date) {
returnnew DateHelper(date);
}
// 格式化日期显示
format(options = {}) {
const defaults = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return this.date.toLocaleDateString('zh-CN', { ...defaults, ...options });
}
// 格式化时间显示
formatTime(options = {}) {
const defaults = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
return this.date.toLocaleTimeString('zh-CN', { ...defaults, ...options });
}
// 智能相对时间显示
formatRelative() {
const now = newDate();
const diffMs = now - this.date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
// 未来时间处理
if (diffMs < 0) {
const absDiffSecs = Math.abs(diffSecs);
const absDiffMins = Math.abs(diffMins);
const absDiffHours = Math.abs(diffHours);
const absDiffDays = Math.abs(diffDays);
if (absDiffSecs < 60) return'即将';
if (absDiffMins < 60) return`${absDiffMins}分钟后`;
if (absDiffHours < 24) return`${absDiffHours}小时后`;
if (absDiffDays < 7) return`${absDiffDays}天后`;
}
// 过去时间处理
if (diffSecs < 60) return'刚刚';
if (diffMins < 60) return`${diffMins}分钟前`;
if (diffHours < 24) return`${diffHours}小时前`;
if (diffDays < 7) return`${diffDays}天前`;
// 超过一周显示具体日期
return this.format();
}
// 添加天数
addDays(days) {
const newDate = newDate(this.date);
newDate.setDate(newDate.getDate() + days);
return new DateHelper(newDate);
}
// 添加月份
addMonths(months) {
const newDate = newDate(this.date);
newDate.setMonth(newDate.getMonth() + months);
return new DateHelper(newDate);
}
// 添加年份
addYears(years) {
const newDate = newDate(this.date);
newDate.setFullYear(newDate.getFullYear() + years);
return new DateHelper(newDate);
}
// 获取当天开始时间(00:00:00)
startOfDay() {
const newDate = newDate(this.date);
newDate.setHours(0, 0, 0, 0);
return new DateHelper(newDate);
}
// 获取当天结束时间(23:59:59)
endOfDay() {
const newDate = newDate(this.date);
newDate.setHours(23, 59, 59, 999);
return new DateHelper(newDate);
}
// 获取月初
startOfMonth() {
const newDate = newDate(this.date);
newDate.setDate(1);
newDate.setHours(0, 0, 0, 0);
return new DateHelper(newDate);
}
// 获取月末
endOfMonth() {
const newDate = newDate(this.date);
newDate.setMonth(newDate.getMonth() + 1, 0); // 下个月的第0天就是这个月的最后一天
newDate.setHours(23, 59, 59, 999);
return new DateHelper(newDate);
}
// 判断是否是同一天
isSameDay(otherDate) {
const other = newDate(otherDate);
return this.date.toDateString() === other.toDateString();
}
// 判断是否是今天
isToday() {
return this.isSameDay(newDate());
}
// 判断是否是明天
isTomorrow() {
const tomorrow = newDate();
tomorrow.setDate(tomorrow.getDate() + 1);
return this.isSameDay(tomorrow);
}
// 判断是否是昨天
isYesterday() {
const yesterday = newDate();
yesterday.setDate(yesterday.getDate() - 1);
return this.isSameDay(yesterday);
}
// 判断是否是周末
isWeekend() {
const day = this.date.getDay();
return day === 0 || day === 6; // 0是周日,6是周六
}
// 获取星期几
getWeekday(format = 'long') {
return this.date.toLocaleDateString('zh-CN', { weekday: format });
}
// 转换为ISO字符串
toISO() {
return this.date.toISOString();
}
// 获取时间戳
valueOf() {
return this.date.getTime();
}
// 转换为原生Date对象
toDate() {
return newDate(this.date);
}
}
让我们看看这个工具类的基本使用:
// 创建日期实例
const today = DateHelper.create(); // 当前时间
const birthday = DateHelper.create('1990-05-20');
const meeting = DateHelper.create(newDate('2024-02-01 14:30:00'));
// 日期格式化
console.log(today.format()); // "2024年1月15日"
console.log(today.format({ month: 'short', weekday: 'long' })); // "2024年1月15日 星期一"
// 时间格式化
console.log(meeting.formatTime()); // "14:30:00"
console.log(meeting.formatTime({ hour: 'numeric', minute: 'numeric' })); // "14:30"
// 相对时间
console.log(birthday.formatRelative()); // "很久以前" 或具体日期
const recentPost = DateHelper.create(Date.now() - 5 * 60 * 1000); // 5分钟前
console.log(recentPost.formatRelative()); // "5分钟前"
// 日期计算(不可变操作)
const nextWeek = today.addDays(7);
console.log(nextWeek.format()); // "2024年1月22日"
console.log(today.format()); // "2024年1月15日" - 原始对象没有变化
// 日期判断
console.log(today.isToday()); // true
console.log(today.isWeekend()); // 根据实际情况
console.log(today.getWeekday()); // "星期一"
class Timeline {
constructor(container) {
this.container = container;
this.posts = [];
}
// 添加动态
addPost(content, authorName, publishTime) {
const post = {
id: Date.now(),
content,
authorName,
publishTime: DateHelper.create(publishTime),
likes: 0,
comments: []
};
this.posts.unshift(post); // 最新的放在前面
this.render();
}
// 渲染时间线
render() {
const html = this.posts.map(post => {
const timeText = this.getTimeDisplayText(post.publishTime);
return`
<div class="post" data-id="${post.id}">
<div class="post-header">
<span class="author">${post.authorName}</span>
<span class="time" title="${post.publishTime.format({
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
})}">${timeText}</span>
</div>
<div class="post-content">${post.content}</div>
<div class="post-actions">
<button class="like-btn" data-id="${post.id}">
❤️ ${post.likes}
</button>
<button class="comment-btn" data-id="${post.id}">
💬 ${post.comments.length}
</button>
</div>
</div>
`;
}).join('');
this.container.innerHTML = html;
this.bindEvents();
}
// 获取时间显示文本
getTimeDisplayText(publishTime) {
if (publishTime.isToday()) {
// 今天的动态显示相对时间
return publishTime.formatRelative();
} elseif (publishTime.isYesterday()) {
// 昨天的动态显示"昨天 HH:MM"
return`昨天 ${publishTime.formatTime({ hour: 'numeric', minute: 'numeric' })}`;
} else {
// 更早的动态显示具体日期
const now = DateHelper.create();
const daysDiff = Math.floor((now.valueOf() - publishTime.valueOf()) / (1000 * 60 * 60 * 24));
if (daysDiff < 7) {
// 一周内显示星期几
return`${publishTime.getWeekday('short')} ${publishTime.formatTime({ hour: 'numeric', minute: 'numeric' })}`;
} else {
// 超过一周显示具体日期
return publishTime.format({ month: 'short', day: 'numeric' });
}
}
}
// 定时更新相对时间
startTimeUpdater() {
setInterval(() => {
// 只更新时间显示,不重新渲染整个列表
this.updateTimeDisplays();
}, 60000); // 每分钟更新一次
}
updateTimeDisplays() {
const timeElements = this.container.querySelectorAll('.time');
timeElements.forEach((element, index) => {
const post = this.posts[index];
if (post) {
element.textContent = this.getTimeDisplayText(post.publishTime);
}
});
}
bindEvents() {
this.container.addEventListener('click', (e) => {
if (e.target.classList.contains('like-btn')) {
const postId = parseInt(e.target.dataset.id);
this.likePost(postId);
}
});
}
likePost(postId) {
const post = this.posts.find(p => p.id === postId);
if (post) {
post.likes++;
this.render();
}
}
}
// 使用示例
const timeline = new Timeline(document.getElementById('timeline'));
// 添加一些测试数据
timeline.addPost("今天天气真不错!", "张三", newDate());
timeline.addPost("刚刚吃了一顿美食", "李四", Date.now() - 30 * 60 * 1000); // 30分钟前
timeline.addPost("周末计划去爬山", "王五", Date.now() - 2 * 60 * 60 * 1000); // 2小时前
// 开始定时更新
timeline.startTimeUpdater();
class OrderManager {
constructor() {
this.orders = [];
this.filters = {
status: 'all',
dateRange: 'all'
};
}
// 创建订单
createOrder(customerName, items, totalAmount) {
const order = {
id: this.generateOrderId(),
customerName,
items,
totalAmount,
status: 'pending',
createdAt: DateHelper.create(),
statusHistory: [{
status: 'pending',
timestamp: DateHelper.create(),
note: '订单创建'
}]
};
this.orders.push(order);
return order;
}
// 更新订单状态
updateOrderStatus(orderId, newStatus, note = '') {
const order = this.findOrder(orderId);
if (!order) returnfalse;
order.status = newStatus;
order.statusHistory.push({
status: newStatus,
timestamp: DateHelper.create(),
note: note || this.getStatusNote(newStatus)
});
// 设置预计送达时间
if (newStatus === 'shipped') {
order.estimatedDelivery = DateHelper.create().addDays(3); // 3天后送达
}
returntrue;
}
// 生成订单号
generateOrderId() {
const now = DateHelper.create();
const dateStr = now.format({
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/[年月日]/g, '');
const timeStr = now.formatTime({
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/:/g, '');
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
return`ORD${dateStr}${timeStr}${random}`;
}
// 获取状态说明
getStatusNote(status) {
const statusMap = {
'pending': '等待处理',
'confirmed': '订单确认',
'preparing': '正在准备',
'shipped': '已发货',
'delivered': '已送达',
'cancelled': '已取消'
};
return statusMap[status] || status;
}
// 渲染订单列表
renderOrders(container) {
const filteredOrders = this.getFilteredOrders();
const html = filteredOrders.map(order => {
const lastStatus = order.statusHistory[order.statusHistory.length - 1];
const statusTime = lastStatus.timestamp.formatRelative();
return`
<div class="order-card" data-id="${order.id}">
<div class="order-header">
<h3>订单 #${order.id}</h3>
<span class="status status-${order.status}">${this.getStatusNote(order.status)}</span>
</div>
<div class="order-info">
<p><strong>客户:</strong>${order.customerName}</p>
<p><strong>金额:</strong>¥${order.totalAmount.toFixed(2)}</p>
<p><strong>下单时间:</strong>${order.createdAt.format()}${order.createdAt.formatTime()}</p>
${order.estimatedDelivery ?
`<p><strong>预计送达:</strong>${order.estimatedDelivery.format()} ${this.getDeliveryStatus(order.estimatedDelivery)}</p>`
: ''
}
</div>
<div class="order-timeline">
<h4>状态历史</h4>
${this.renderStatusHistory(order.statusHistory)}
</div>
<div class="order-actions">
${this.renderOrderActions(order)}
</div>
</div>
`;
}).join('');
container.innerHTML = html || '<p class="no-orders">暂无订单</p>';
}
// 渲染状态历史
renderStatusHistory(history) {
return history.map(item =>`
<div class="status-item">
<span class="status-dot"></span>
<div class="status-info">
<strong>${item.note}</strong>
<small>${item.timestamp.format()} ${item.timestamp.formatTime()}</small>
<span class="relative-time">(${item.timestamp.formatRelative()})</span>
</div>
</div>
`).join('');
}
// 获取送达状态
getDeliveryStatus(deliveryDate) {
const now = DateHelper.create();
const diffDays = Math.ceil((deliveryDate.valueOf() - now.valueOf()) / (1000 * 60 * 60 * 24));
if (diffDays < 0) {
return'<span class="overdue">已逾期</span>';
} elseif (diffDays === 0) {
return'<span class="today">今天送达</span>';
} elseif (diffDays === 1) {
return'<span class="tomorrow">明天送达</span>';
} else {
return`<span class="future">${diffDays}天后送达</span>`;
}
}
// 渲染订单操作按钮
renderOrderActions(order) {
const actions = [];
switch (order.status) {
case'pending':
actions.push('<button class="btn-confirm" data-action="confirm">确认订单</button>');
actions.push('<button class="btn-cancel" data-action="cancel">取消订单</button>');
break;
case'confirmed':
actions.push('<button class="btn-prepare" data-action="prepare">开始准备</button>');
break;
case'preparing':
actions.push('<button class="btn-ship" data-action="ship">发货</button>');
break;
case'shipped':
actions.push('<button class="btn-deliver" data-action="deliver">确认送达</button>');
break;
}
return actions.join('');
}
// 获取筛选后的订单
getFilteredOrders() {
returnthis.orders.filter(order => {
// 状态筛选
if (this.filters.status !== 'all' && order.status !== this.filters.status) {
returnfalse;
}
// 日期范围筛选
if (this.filters.dateRange !== 'all') {
const now = DateHelper.create();
const orderDate = order.createdAt;
switch (this.filters.dateRange) {
case'today':
if (!orderDate.isToday()) returnfalse;
break;
case'week':
const weekAgo = now.addDays(-7);
if (orderDate.valueOf() < weekAgo.valueOf()) returnfalse;
break;
case'month':
const monthAgo = now.addMonths(-1);
if (orderDate.valueOf() < monthAgo.valueOf()) returnfalse;
break;
}
}
returntrue;
}).sort((a, b) => b.createdAt.valueOf() - a.createdAt.valueOf()); // 按时间倒序
}
findOrder(orderId) {
returnthis.orders.find(order => order.id === orderId);
}
}
// 使用示例
const orderManager = new OrderManager();
// 创建一些测试订单
const order1 = orderManager.createOrder("张三", ["商品A", "商品B"], 299.99);
const order2 = orderManager.createOrder("李四", ["商品C"], 158.50);
// 模拟订单状态更新
setTimeout(() => {
orderManager.updateOrderStatus(order1.id, 'confirmed');
}, 1000);
setTimeout(() => {
orderManager.updateOrderStatus(order1.id, 'preparing');
}, 3000);
// 渲染订单列表
const orderContainer = document.getElementById('orders');
orderManager.renderOrders(orderContainer);
// 定时更新相对时间显示
setInterval(() => {
orderManager.renderOrders(orderContainer);
}, 60000);
class Calendar {
constructor(container) {
this.container = container;
this.currentDate = DateHelper.create();
this.selectedDate = null;
this.events = newMap(); // 存储事件数据
this.init();
}
init() {
this.render();
this.bindEvents();
}
// 渲染日历
render() {
const header = this.renderHeader();
const calendar = this.renderCalendar();
this.container.innerHTML = `
<div class="calendar-widget">
${header}
${calendar}
</div>
`;
}
// 渲染头部(月份导航)
renderHeader() {
const monthYear = this.currentDate.format({ year: 'numeric', month: 'long' });
return`
<div class="calendar-header">
<button class="nav-btn" data-action="prev-month">‹</button>
<h2 class="current-month">${monthYear}</h2>
<button class="nav-btn" data-action="next-month">›</button>
</div>
`;
}
// 渲染日历主体
renderCalendar() {
const monthStart = this.currentDate.startOfMonth();
const monthEnd = this.currentDate.endOfMonth();
// 获取月份第一天是星期几(0=周日,1=周一...)
const firstDayWeek = monthStart.toDate().getDay();
// 计算需要显示的日期范围
const calendarStart = monthStart.addDays(-firstDayWeek);
const calendarEnd = monthEnd.addDays(6 - monthEnd.toDate().getDay());
const weeks = [];
let currentWeekStart = calendarStart;
while (currentWeekStart.valueOf() <= calendarEnd.valueOf()) {
const week = [];
for (let i = 0; i < 7; i++) {
const date = currentWeekStart.addDays(i);
week.push(date);
}
weeks.push(week);
currentWeekStart = currentWeekStart.addDays(7);
}
return`
<div class="calendar-grid">
<div class="weekdays">
<div class="weekday">日</div>
<div class="weekday">一</div>
<div class="weekday">二</div>
<div class="weekday">三</div>
<div class="weekday">四</div>
<div class="weekday">五</div>
<div class="weekday">六</div>
</div>
<div class="dates">
${weeks.map(week =>
`<div class="week">
${week.map(date => this.renderDateCell(date)).join('')}
</div>`
).join('')}
</div>
</div>
`;
}
// 渲染单个日期单元格
renderDateCell(date) {
const classes = ['date-cell'];
const dayNumber = date.toDate().getDate();
// 判断是否是当前月份
if (date.toDate().getMonth() !== this.currentDate.toDate().getMonth()) {
classes.push('other-month');
}
// 判断是否是今天
if (date.isToday()) {
classes.push('today');
}
// 判断是否是选中的日期
if (this.selectedDate && date.isSameDay(this.selectedDate)) {
classes.push('selected');
}
// 判断是否是周末
if (date.isWeekend()) {
classes.push('weekend');
}
// 获取该日期的事件
const dayEvents = this.getEventsForDate(date);
const hasEvents = dayEvents.length > 0;
return`
<div class="${classes.join(' ')}" data-date="${date.toISO()}">
<span class="day-number">${dayNumber}</span>
${hasEvents ? `<div class="event-indicators">
${dayEvents.slice(0, 3).map(event =>
`<span class="event-dot" style="background-color: ${event.color}"></span>`
).join('')}
${dayEvents.length > 3 ? '<span class="more-events">...</span>' : ''}
</div>` : ''}
</div>
`;
}
// 获取指定日期的事件
getEventsForDate(date) {
const dateKey = date.startOfDay().toISO();
return this.events.get(dateKey) || [];
}
// 添加事件
addEvent(date, title, color = '#3498db') {
const dateKey = DateHelper.create(date).startOfDay().toISO();
if (!this.events.has(dateKey)) {
this.events.set(dateKey, []);
}
this.events.get(dateKey).push({
id: Date.now(),
title,
color,
date: DateHelper.create(date)
});
this.render();
}
// 绑定事件
bindEvents() {
this.container.addEventListener('click', (e) => {
if (e.target.classList.contains('nav-btn')) {
const action = e.target.dataset.action;
if (action === 'prev-month') {
this.currentDate = this.currentDate.addMonths(-1);
this.render();
} elseif (action === 'next-month') {
this.currentDate = this.currentDate.addMonths(1);
this.render();
}
} elseif (e.target.closest('.date-cell')) {
const dateCell = e.target.closest('.date-cell');
const dateString = dateCell.dataset.date;
this.selectedDate = DateHelper.create(dateString);
this.render();
this.onDateSelected(this.selectedDate);
}
});
}
// 日期选择回调
onDateSelected(date) {
console.log('选中日期:', date.format());
// 显示该日期的事件详情
this.showDateEvents(date);
}
// 显示日期事件详情
showDateEvents(date) {
const events = this.getEventsForDate(date);
const detailContainer = document.getElementById('date-details');
if (!detailContainer) return;
if (events.length === 0) {
detailContainer.innerHTML = `
<h3>${date.format()}</h3>
<p>暂无事件安排</p>
<button class="add-event-btn">添加事件</button>
`;
} else {
detailContainer.innerHTML = `
<h3>${date.format()} (${date.getWeekday()})</h3>
<div class="events-list">
${events.map(event => `
<div class="event-item" style="border-left: 4px solid ${event.color}">
<strong>${event.title}</strong>
<small>${event.date.formatTime()}</small>
</div>
`).join('')}
</div>
<button class="add-event-btn">添加事件</button>
`;
}
}
// 跳转到今天
goToToday() {
this.currentDate = DateHelper.create();
this.selectedDate = this.currentDate;
this.render();
}
// 跳转到指定月份
goToMonth(year, month) {
this.currentDate = DateHelper.create(newDate(year, month - 1, 1));
this.render();
}
}
// 使用示例
const calendar = new Calendar(document.getElementById('calendar'));
// 添加一些示例事件
calendar.addEvent(newDate(), '团队会议', '#e74c3c');
calendar.addEvent(DateHelper.create().addDays(1).toDate(), '项目演示', '#2ecc71');
calendar.addEvent(DateHelper.create().addDays(3).toDate(), '客户拜访', '#f39c12');
calendar.addEvent(DateHelper.create().addDays(7).toDate(), '培训课程', '#9b59b6');
// 创建快捷导航
document.getElementById('today-btn')?.addEventListener('click', () => {
calendar.goToToday();
});
class InternationalDateHelper extends DateHelper {
constructor(date = new Date(), locale = 'zh-CN') {
super(date);
this.locale = locale;
}
static create(date, locale = 'zh-CN') {
return new InternationalDateHelper(date, locale);
}
format(options = {}) {
const defaults = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return this.date.toLocaleDateString(this.locale, { ...defaults, ...options });
}
formatTime(options = {}) {
const defaults = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
return this.date.toLocaleTimeString(this.locale, { ...defaults, ...options });
}
formatRelative() {
const now = newDate();
const diffMs = now - this.date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
// 根据不同语言返回不同的相对时间表示
const relativeTexts = {
'zh-CN': {
justNow: '刚刚',
minutesAgo: (mins) =>`${mins}分钟前`,
hoursAgo: (hours) =>`${hours}小时前`,
daysAgo: (days) =>`${days}天前`,
minutesLater: (mins) =>`${mins}分钟后`,
hoursLater: (hours) =>`${hours}小时后`,
daysLater: (days) =>`${days}天后`
},
'en-US': {
justNow: 'just now',
minutesAgo: (mins) =>`${mins} minute${mins > 1 ? 's' : ''} ago`,
hoursAgo: (hours) =>`${hours} hour${hours > 1 ? 's' : ''} ago`,
daysAgo: (days) =>`${days} day${days > 1 ? 's' : ''} ago`,
minutesLater: (mins) =>`in ${mins} minute${mins > 1 ? 's' : ''}`,
hoursLater: (hours) =>`in ${hours} hour${hours > 1 ? 's' : ''}`,
daysLater: (days) =>`in ${days} day${days > 1 ? 's' : ''}`
}
};
const texts = relativeTexts[this.locale] || relativeTexts['en-US'];
if (diffMs < 0) {
// 未来时间
const absDiffMins = Math.abs(diffMins);
const absDiffHours = Math.abs(diffHours);
const absDiffDays = Math.abs(diffDays);
if (absDiffMins < 60) return texts.minutesLater(absDiffMins);
if (absDiffHours < 24) return texts.hoursLater(absDiffHours);
if (absDiffDays < 7) return texts.daysLater(absDiffDays);
} else {
// 过去时间
if (diffSecs < 60) return texts.justNow;
if (diffMins < 60) return texts.minutesAgo(diffMins);
if (diffHours < 24) return texts.hoursAgo(diffHours);
if (diffDays < 7) return texts.daysAgo(diffDays);
}
returnthis.format();
}
getWeekday(format = 'long') {
return this.date.toLocaleDateString(this.locale, { weekday: format });
}
}
class TimezoneAwareDateHelper extends InternationalDateHelper {
constructor(date = new Date(), locale = 'zh-CN', timezone = 'Asia/Shanghai') {
super(date, locale);
this.timezone = timezone;
}
static create(date, locale = 'zh-CN', timezone = 'Asia/Shanghai') {
return new TimezoneAwareDateHelper(date, locale, timezone);
}
format(options = {}) {
const defaults = {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: this.timezone
};
return this.date.toLocaleDateString(this.locale, { ...defaults, ...options });
}
formatTime(options = {}) {
const defaults = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: this.timezone
};
return this.date.toLocaleTimeString(this.locale, { ...defaults, ...options });
}
// 转换到指定时区
toTimezone(newTimezone) {
retur nnew TimezoneAwareDateHelper(this.date, this.locale, newTimezone);
}
// 获取时区偏移信息
getTimezoneOffset() {
const formatter = newIntl.DateTimeFormat('en-US', {
timeZone: this.timezone,
timeZoneName: 'longOffset'
});
const parts = formatter.formatToParts(this.date);
const offsetPart = parts.find(part => part.type === 'timeZoneName');
return offsetPart ? offsetPart.value : '';
}
}
class CachedDateHelper extends TimezoneAwareDateHelper {
constructor(date = new Date(), locale = 'zh-CN', timezone = 'Asia/Shanghai') {
super(date, locale, timezone);
this.cache = newMap();
}
format(options = {}) {
const cacheKey = JSON.stringify({ method: 'format', options, locale: this.locale, timezone: this.timezone });
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const result = super.format(options);
this.cache.set(cacheKey, result);
return result;
}
formatTime(options = {}) {
const cacheKey = JSON.stringify({ method: 'formatTime', options, locale: this.locale, timezone: this.timezone });
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const result = super.formatTime(options);
this.cache.set(cacheKey, result);
return result;
}
// 清理缓存
clearCache() {
this.cache.clear();
}
}
让我们看看我们的原生方案与第三方库的性能对比:
// 性能测试函数
function performanceTest() {
const iterations = 10000;
const testDate = newDate('2024-01-15T14:30:25.000Z');
// 测试格式化性能
console.time('原生DateHelper格式化');
for (let i = 0; i < iterations; i++) {
const helper = DateHelper.create(testDate);
helper.format();
helper.formatTime();
helper.formatRelative();
}
console.timeEnd('原生DateHelper格式化');
// 测试日期计算性能
console.time('原生DateHelper计算');
for (let i = 0; i < iterations; i++) {
const helper = DateHelper.create(testDate);
helper.addDays(7).addMonths(1).startOfDay();
}
console.timeEnd('原生DateHelper计算');
// 内存使用测试
const memBefore = performance.memory?.usedJSHeapSize || 0;
const helpers = [];
for (let i = 0; i < 1000; i++) {
helpers.push(DateHelper.create(testDate));
}
const memAfter = performance.memory?.usedJSHeapSize || 0;
console.log('内存使用增长:', (memAfter - memBefore) / 1024, 'KB');
}
// 运行性能测试
performanceTest();
// 测试结果对比(示例):
// 原生DateHelper格式化: ~150ms
// Moment.js格式化: ~300ms
// date-fns格式化: ~200ms
//
// 原生DateHelper计算: ~100ms
// Moment.js计算: ~250ms
// date-fns计算: ~180ms
通过我们自制的日期处理工具类,我们实现了:
核心优势:
功能完备:
实际应用:
这个日期处理工具不仅功能强大,而且完全摆脱了对第三方库的依赖。无论是简单的日期格式化,还是复杂的业务逻辑处理,都能轻松胜任。
掌握了这个工具,你就能在项目中自信地处理任何日期相关的需求,再也不用为JavaScript的日期处理而烦恼了!