
我们的项目中,有一个日期格式化的功能,用户选择日期之后,比如选择了2025-07-01,前端会处理成零点的时间(2025-07-01 00:00:00)进行回显,同时提交的数据也是(2025-07-01 00:00:00)的时间戳。
但是,最近线上出现了一个非常典型的数据:用户提交的数据竟然是(2025-07-01 08:00:00)的时间戳。
整个团队尝试了各种浏览器都没有复现这个问题,客户那边没有提供更多的有效信息。紧急修复数据之后,我开始对日期格式化功能进行复盘。
花开两朵,先表一枝。
用户在创建促销活动的时候,可以选择活动上线和下线日期,如选择了2025-07-01 ~ 2025-07-31,那么会默认展示为2025-07-01 00:00:00 ~ 2025-07-31 23:59:59,旨在告知用户,活动是0点开始,23:59:59结束。

我们日期格式化功能的代码如下:
/**
* 设置特定格式的时间戳(将日期时间调整为当天的开始或结束时刻)
*
* @param {string} dateString - 输入的日期字符串,将被解析为moment对象
* @param {string} [type='begin'] - 时间类型,可选值为'begin'(当天开始时间)或'end'(当天结束时间)
* @returns {number} - 返回调整后的时间戳(毫秒数),如果输入无效则返回空字符串
*/
const setFormatSpecificTimeStamp = (dateString, type = 'begin') => {
// 处理空输入情况
if (!dateString) {
return '';
}
// 验证日期有效性
const date = moment(dateString);
if (!date.isValid()) {
return '';
}
// 定义时间格式配置(小时、分钟、秒、毫秒)
const formatter = {
begin: [0, 0, 0, 0], // 当天开始时间(00:00:00.000)
end: [23, 59, 59, 0], // 当天结束时间(23:59:59.000)
};
// 获取对应的时间格式配置,默认使用begin配置
const format = formatter[type] || formatter.begin;
// 设置时间分量
date.set('hour', format[0]);
date.set('minute', format[1]);
date.set('second', format[2]);
date.set('millisecond', format[3]);
// 返回时间戳
return date.valueOf();
};基本功能:
将输入的日期字符串调整为当天的开始时刻(00:00:00)或结束时刻(23:59:59),并返回对应的时间戳(毫秒数);若输入无效则返回空字符串。
核心逻辑:
[0,0,0,0](00:00:00.000)。[23,59,59,0](23:59:59.000)。结合日期的特点,我推测可能导致线上问题的原因如下:

问题类型 | 表现场景 | 影响范围 |
|---|---|---|
时区解析不一致 | 夏令时转换期间 | 欧美地区用户 |
体积过大 | 生产包体积分析 | 低端安卓设备 |
可变性缺陷 | 日期对象被意外修改 | 状态管理场景 |
性能瓶颈 | 大规模日期操作 | 数据可视化大屏 |
时区问题:
// 美国东部时间夏令时问题
moment('2025-07-01 00:00:00').format();
// 输出: "2025-07-01T00:00:00-04:00" (自动跳转)
Day.js 是一个轻量级的日期处理库,它的核心文件只有 2KB 左右,而且提供了丰富的插件来扩展功能。Day.js 的 API 设计与 Moment.js 类似,这使得开发者可以很容易地从 Moment.js 迁移到 Day.js。
特性 | Moment.js | Day.js |
|---|---|---|
不可变性 | ❌ 需要.clone() | ✅ 默认不可变 |
体积 | 67.8KB | 7.2KB |
时区支持 | 内置 | 需插件 |
性能 | 较慢 | 快3倍 |
最终方案:
/**
* 设置特定格式的时间戳
*
* 该函数根据指定的类型(开始/结束)和时区设置,对输入日期进行格式化处理,
* 返回调整后的时间戳(毫秒数)。无效输入将返回NaN。
*
* @param {*} dateInput - 输入的日期值,可以是能被dayjs解析的任何格式
* @param {'begin'|'end'} [type='begin'] - 时间调整类型:
* 'begin':调整为当天的开始时间(00:00:00.000)
* 'end':调整为当天的结束时间(23:59:59.999)
* @param {'local'|'utc'} [timezone='local'] - 时区设置:
* 'local':使用本地时区
* 'utc':使用UTC时区
* @returns {number} 调整后的时间戳(毫秒数),无效输入返回空
*/
const setFormatSpecificTimeStamp = (
dateInput,
type = 'begin',
timezone = 'local',
) => {
// 处理空输入情况
if (!dateInput) return '';
// 日期有效性验证
let date = dayjs(dateInput);
if (!date.isValid()) {
console.error(`Invalid date input: ${dateInput}`);
return '';
}
// 应用时区转换
if (timezone === 'utc') {
date = date.utc();
}
// 定义时间调整策略
const timeAdjusters = {
begin: d => d.startOf('day'),
end: d => d.endOf('day'),
};
// 选择并应用时间调整器
const adjuster = timeAdjusters[type] || timeAdjusters.begin;
// 执行调整并返回时间戳(毫秒部分归零)
return adjuster(date).millisecond(0).valueOf();
};功能总结:
type='begin' → 返回当天起始时间戳(00:00:00)。type='end' → 返回当天结束时间戳(23:59:59)。timezone='local' → 基于系统时区。timezone='utc' → 基于标准UTC时间。这里有个小插曲,最初我改完方案,发现提交数据仍然有问题。
输入:
setFormatSpecificTimeStamp('2025-07-31 12:00:00', 'end')输出:1753977599999。
最后3位数是999,实际应该是000。
这是因为毫秒没有归零,于是增加了millisecond(0)方法。
最后正确的输出:1753977599000。
现在第三方库十分成熟,日常对日期操作频率不高,为了节省问题解决的成本,提升解决问题的效率,所以我总结了若干日期操作技巧。
// 避免2025-01-31加1月变成2025-03-03
dayjs('2025-01-31').add(1, 'month').endOf('month').format('YYYY-MM-DD')
// → 2025-02-28// 导入dayjs及其插件
const dayjs = require('dayjs');
const isSameOrBefore = require('dayjs/plugin/isSameOrBefore');
const weekOfYear = require('dayjs/plugin/weekOfYear');
// 扩展dayjs功能
dayjs.extend(isSameOrBefore);
dayjs.extend(weekOfYear);
/**
* 计算两个日期之间的工作日数量(排除周六和周日)
* @param {string} start - 起始日期字符串(YYYY-MM-DD格式)
* @param {string} end - 结束日期字符串(YYYY-MM-DD格式)
* @returns {number} 两个日期之间的工作日总数
*/
const getWorkdays = (start, end) => {
let count = 0;
let cur = dayjs(start);
// 遍历日期范围内的每一天
while (cur.isSameOrBefore(end)) {
// 检查当前日是否为工作日(非周六/周日)
if (cur.day() !== 0 && cur.day() !== 6) count++;
cur = cur.add(1, 'day');
}
return count;
};示例:
// 示例:计算2025年7月的工作日数量
const days = getWorkdays('2025-07-01', '2025-07-31');
console.log(days); // 23// 导入dayjs及其插件
const dayjs = require('dayjs');
const quarterOfYear = require('dayjs/plugin/quarterOfYear');
dayjs.extend(quarterOfYear);
// 获取季度末第一天
dayjs().startOf('quarter').format('YYYY-MM-DD'); // → 2025-07-01
// 获取季度末最后一天
dayjs().endOf('quarter').format('YYYY-MM-DD'); // → 2025-09-30const dayjs = require('dayjs');
const date = '2025-07-10';
// 获取输入日期的当月第一天
dayjs(date).startOf('month').format('YYYY-MM-DD');// → 2025-07-01功能说明:
// 导入dayjs及其插件
const dayjs = require('dayjs');
const weekOfYear = require('dayjs/plugin/weekOfYear');
dayjs.extend(weekOfYear);
const date = '2025-07-10';
// 计算给定日期所在周的第一天
dayjs(date).startOf('week').format('YYYY-MM-DD'); // → 2025-07-06功能说明:
本文从一个线上日期格式化问题出发,深入探讨了 Moment.js 和 Day.js 这两个日期处理库的差异点、兼容性问题以及特别操作。Moment.js 虽然功能强大,但存在体积过大、时区处理复杂、对象可变等问题。而 Day.js 作为替代方案,体积小、性能优,API 设计与 Moment.js 类似,且对象不可变,避免了一些潜在的问题。
通过本文的学习,我们对日期处理库有了更深入的了解,掌握了如何选择合适的日期处理库来解决实际问题。分享日期处理的三个原则:
这些经验将帮助您构建更健壮的时间相关功能,避免因日期问题导致的线上事故,为业务创造稳定可靠的技术基础。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。