日期格式化也有兼容性问题?来捋一捋日期处理库的差异和操作技巧

举报
叶一一 发表于 2025/08/25 19:11:54 2025/08/25
【摘要】 引言我们的项目中,有一个日期格式化的功能,用户选择日期之后,比如选择了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-01 00:00:00)进行回显,同时提交的数据也是(2025-07-01 00:00:00)的时间戳。

但是,最近线上出现了一个非常典型的数据:用户提交的数据竟然是(2025-07-01 08:00:00)的时间戳。

整个团队尝试了各种浏览器都没有复现这个问题,客户那边没有提供更多的有效信息。紧急修复数据之后,我开始对日期格式化功能进行复盘。

花开两朵,先表一枝。

一、第一回合:复盘代码

1.1 功能描述

用户在创建促销活动的时候,可以选择活动上线和下线日期,如选择了2025-07-01 ~ 2025-07-31,那么会默认展示为2025-07-01 00:00:00 ~ 2025-07-31 23:59:59,旨在告知用户,活动是0点开始,23:59:59结束。

1.2 代码分析

我们日期格式化功能的代码如下:

/**
 * 设置特定格式的时间戳(将日期时间调整为当天的开始或结束时刻)
 * 
 * @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),并返回对应的时间戳(毫秒数);若输入无效则返回空字符串。

核心逻辑:

  • 输入检查:空输入直接返回空字符串。
  • 日期验证:使用moment解析日期,无效则返回空字符串。
  • 时间配置
    • begin类型设为[0,0,0,0](00:00:00.000)。
    • end类型设为[23,59,59,0](23:59:59.000)。
  • 时间调整:根据类型设置时分秒毫秒。
  • 返回值:返回调整后的时间戳(毫秒数)。

1.3 问题分析

结合日期的特点,我推测可能导致线上问题的原因如下:

  • Moment.js 默认使用本地时区解析。
  • 直接修改时间部分不会重置时区偏移。
  • 夏令时转换导致额外偏差。
  • Moment.js 在不同环境下的兼容性问题。

二、第二回合:Moment.js 的兼容性问题

2.1 典型兼容性问题清单

问题类型

表现场景

影响范围

时区解析不一致

夏令时转换期间

欧美地区用户

体积过大

生产包体积分析

低端安卓设备

可变性缺陷

日期对象被意外修改

状态管理场景

性能瓶颈

大规模日期操作

数据可视化大屏

2.2 问题示例

时区问题

// 美国东部时间夏令时问题
moment('2025-07-01 00:00:00').format(); 
// 输出: "2025-07-01T00:00:00-04:00" (自动跳转)


三、第三回合:Day.js 替代方案

3.1 Day.js 基本介绍

Day.js 是一个轻量级的日期处理库,它的核心文件只有 2KB 左右,而且提供了丰富的插件来扩展功能。Day.js 的 API 设计与 Moment.js 类似,这使得开发者可以很容易地从 Moment.js 迁移到 Day.js。

3.2 问题修复方案

最终方案:

/**
 * 设置特定格式的时间戳
 * 
 * 该函数根据指定的类型(开始/结束)和时区设置,对输入日期进行格式化处理,
 * 返回调整后的时间戳(毫秒数)。无效输入将返回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时间。

3.3 小插曲

这里有个小插曲,最初我改完方案,发现提交数据仍然有问题。

输入:

setFormatSpecificTimeStamp('2025-07-31 12:00:00', 'end')

输出:1753977599999。

最后3位数是999,实际应该是000。

这是因为毫秒没有归零,于是增加了millisecond(0)方法。

最后正确的输出:1753977599000。

四、第四回合:日期操作技巧总结

现在第三方库十分成熟,日常对日期操作频率不高,为了节省问题解决的成本,提升解决问题的效率,所以我总结了若干日期操作技巧。

4.1 跨月日期安全操作

// 避免2025-01-31加1月变成2025-03-03
dayjs('2025-01-31').add(1, 'month').endOf('month').format('YYYY-MM-DD') 
// → 2025-02-28

4.2 工作日计算

// 导入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

4.3 季度处理

// 导入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-30

4.4 设置为当月第一天

const dayjs = require('dayjs');
const date = '2025-07-10';
// 获取输入日期的当月第一天
dayjs(date).startOf('month').format('YYYY-MM-DD');// → 2025-07-01

功能说明:

  • 使用dayjs解析目标日期。
  • 通过startOf('month')获取该月起始时间。
  • 格式化为YYYY-MM-DD字符串。

4.5 设置为当周第一天(周一)

// 导入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

功能说明:

  • dayjs(date) - 将日期字符串转换为dayjs对象。
  • .startOf('week') - 获取该周的第一天(根据本地化设置,默认周日为一周起点)。
  • .format('YYYY-MM-DD') - 格式化为年-月-日字符串。

结语

本文从一个线上日期格式化问题出发,深入探讨了 Moment.js 和 Day.js 这两个日期处理库的差异点、兼容性问题以及特别操作。Moment.js 虽然功能强大,但存在体积过大、时区处理复杂、对象可变等问题。而 Day.js 作为替代方案,体积小、性能优,API 设计与 Moment.js 类似,且对象不可变,避免了一些潜在的问题。

通过本文的学习,我们对日期处理库有了更深入的了解,掌握了如何选择合适的日期处理库来解决实际问题。分享日期处理的三个原则:

  • 明确时区:始终显式指定时区上下文
  • 不可变性:避免隐式的日期对象修改
  • 语义化封装:业务逻辑与日期解耦

这些经验将帮助您构建更健壮的时间相关功能,避免因日期问题导致的线上事故,为业务创造稳定可靠的技术基础。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。