封装一个通用的JS时间戳格式化函数

作者:半夏

时间:2026-11-04

浏览:200

本文深入讲解如何在JavaScript中封装一个通用的时间戳格式化函数,涵盖10位与13位时间戳自适应识别、非法边界防御、常用Token字典映射与高性能正则替换,提供完整可运行代码及全流程架构解析。

文章目录
JavaScript 工具函数 代码封装 目标:构建健壮、自适应、零依赖的时间格式化工具
封装一个通用的JS时间戳格式化函数
图 0:通用时间戳格式化函数的输入、处理核心与输出流向示意

在前端业务开发中,时间戳格式化是一个高频但极易踩坑的基础操作。后端接口返回的时间数据标准并不统一:有的采用 10 位 Unix 时间戳(秒级),有的采用 13 位 JavaScript 时间戳(毫秒级),有的字段甚至可能以纯数字字符串形式返回,或掺杂 null、undefined 等边界异常值。若每次都在业务组件中零散调用 Date API,不仅代码冗余,而且容易因缺少边界判断导致页面白屏报错。本文的目标是封装一个纯原生、零外部依赖且具备类型自适应能力的通用函数 formatTimestamp,通过规范化的参数清洗、分量提取与模板替换,实现稳定高效的日期格式化。

一、核心设计思路与边界问题梳理

在动手写代码前,必须明确一个通用的时间戳格式化工具需要解决的核心矛盾。一个真正可以在生产环境中复用的工具函数,通常需要满足以下四项设计标准:

  • 时间戳长度自适应:由于 Unix 时间戳是以“秒”为单位(10 位,例如 1716000000),而 JavaScript 原生的 new Date(ms) 构造函数强制要求接收以“毫秒”为单位的数值(13 位,例如 1716000000000)。函数必须能自动识别输入位数并在需要时放大 1000 倍。
  • 容错与类型防御:面对字符串形式的纯数字(如 "1716000000")、空字符串、null、undefined 或无法解析的非法值时,函数必须能优雅降级,返回空字符串或预设缺省值,绝不抛出未捕获的运行时异常。
  • 自定义模板语法:支持类似流行库(如 Day.js / Moment.js)的占位符模式(如 YYYY、MM、DD、HH、mm、ss、SSS),允许调用方根据业务按需组装日期外观。
  • 智能补零机制:月份、日期、时分秒在单位数(1-9)时通常需要补零保持双字符对齐(如 05 而非 5),同时保留单字符占位符支持(如 M 代表单月)。
封装一个通用的JS时间戳格式化函数
图 1:时间戳自适应识别、分量提取及映射字典分层架构

二、格式化函数的分步构建流程

为了让逻辑清晰可维护,我们将格式化函数的执行逻辑划分为五个紧密配合的阶段。以下是各个环节的具体实现细节与设计考量:

步骤 1:输入清洗与类型判断

首先过滤绝对空值(null、undefined 以及空字符串 "")。接着判断输入是否已经是 Date 对象实例,如果不是,则将其转换为数字。对于字符串形式传入的时间戳(如 "1716000000"),Number("1716000000") 能顺利得到纯数字。

步骤 2:10位与13位自适应换算

通过取绝对值并向下取整后转为字符串,检测其字符长度:如果长度恰好为 10,则说明是秒级时间戳,必须乘以 1000 转换为毫秒;若长度为 13 则保持原样。转换后统一通过 new Date(...) 实例化。随后立即通过 isNaN(date.getTime()) 进行安全守卫,若日期不合法,直接返回空字符串,避免后续逻辑取值异常。

步骤 3:时间各分量精确提取与补零

调用 date.getFullYear()、date.getMonth() + 1(注意原生月份从 0 开始计,必须加 1)、date.getDate()、date.getHours()、date.getMinutes()、date.getSeconds() 与 date.getMilliseconds() 提取出数值。

利用现代 JavaScript 规范中的 String.prototype.padStart() 方法可以极简实现补零。例如 String(month).padStart(2, '0'),当月份为 5 时得到 "05",当月份为 11 时保持 "11",毫秒则使用 padStart(3, '0')。

步骤 4:Token 映射字典构建

将提取并规整后的分量映射到一个纯对象中,键名对应约定的模板字符,例如:YYYY 对应完整四位年份,YY 对应后两位年份,MM 对应两位月份,M 对应一位月份,依此类推。

步骤 5:正则匹配与一次性安全替换

通过包含所有合法 Token 的正则表达式进行全局替换。在编写正则表达式时,必须将较长的标记排在前面(如 YYYY|YY、MM|M、DD|D、HH|H),避免短标记先被贪婪匹配导致错误切分。

封装一个通用的JS时间戳格式化函数
图 2:格式化函数从输入到输出的五步流水线处理过程

三、通用函数的完整代码实现

结合上述设计,封装出的完整 JavaScript 函数如下。代码不依赖任何外部 NPM 包,可以直接放置在项目的 utils/date.js 文件中作为通用工具引入:

/**
 * 通用时间戳格式化函数
 * @param {number|string|Date} timestamp - 输入时间戳(支持10位秒级、13位毫秒级、数字字符串或Date对象)
 * @param {string} [pattern='YYYY-MM-DD HH:mm:ss'] - 目标格式化模板
 * @returns {string} 格式化后的时间字符串,非法输入时返回空字符串
 */
function formatTimestamp(timestamp, pattern = 'YYYY-MM-DD HH:mm:ss') {
  // 1. 空值快速拦截
  if (timestamp === null || timestamp === undefined || timestamp === '') {
    return '';
  }

  // 2. 自适应转换为毫秒级基准的 Date 实例
  let date;
  if (timestamp instanceof Date) {
    date = timestamp;
  } else {
    const num = Number(timestamp);
    if (!Number.isNaN(num)) {
      // 检测是否为 10 位秒级时间戳(按整数位长度判定)
      const isSeconds = String(Math.abs(Math.floor(num))).length === 10;
      date = new Date(isSeconds ? num * 1000 : num);
    } else {
      // 尝试作为标准日期字符串由底层解析(如 "2026-05-18")
      date = new Date(timestamp);
    }
  }

  // 3. 校验时间有效性,拦截 Invalid Date
  if (Number.isNaN(date.getTime())) {
    return '';
  }

  // 4. 提取时间分量
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const hours24 = date.getHours();
  const hours12 = hours24 % 12 || 12;
  const minutes = date.getMinutes();
  const seconds = date.getSeconds();
  const milliseconds = date.getMilliseconds();

  // 双位对齐辅助工具
  const pad = (val, len = 2) => String(val).padStart(len, '0');

  // 5. 组装 Token 映射字典
  const tokens = {
    YYYY: String(year),
    YY: String(year).slice(-2),
    MM: pad(month),
    M: String(month),
    DD: pad(day),
    D: String(day),
    HH: pad(hours24),
    H: String(hours24),
    hh: pad(hours12),
    h: String(hours12),
    mm: pad(minutes),
    m: String(minutes),
    ss: pad(seconds),
    s: String(seconds),
    SSS: pad(milliseconds, 3),
    A: hours24 >= 12 ? 'PM' : 'AM',
    a: hours24 >= 12 ? '下午' : '上午'
  };

  // 6. 使用全局正则匹配替换(长标记在前,避免短标记误伤)
  const tokenRegex = /YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g;

  return pattern.replace(tokenRegex, (match) => {
    return Object.prototype.hasOwnProperty.call(tokens, match) ? tokens[match] : match;
  });
}
封装一个通用的JS时间戳格式化函数
图 3:正则表达式单次遍历扫描与字典值原子替换机制

四、实际调用示例与多场景验证

为验证该通用函数的稳健性,我们在常见业务场景下进行输入验证:

// 场景 1:默认标准输出(10 位秒级时间戳与 13 位毫秒级时间戳)
const secTs = 1779085800;        // 10位 Unix 秒级时间戳
const msTs = 1779085800000;      // 13位 JS 毫秒级时间戳
console.log(formatTimestamp(secTs)); 
// 示例输出: "2026-05-18 14:30:00"
console.log(formatTimestamp(msTs)); 
// 示例输出: "2026-05-18 14:30:00"

// 场景 2:支持纯数字字符串格式的接口返回值
console.log(formatTimestamp("1779085800")); 
// 示例输出: "2026-05-18 14:30:00"

// 场景 3:多样化业务模式定制
console.log(formatTimestamp(msTs, 'YYYY年MM月DD日'));
// 示例输出: "2026年05月18日"

console.log(formatTimestamp(msTs, 'YY/M/D HH:mm'));
// 示例输出: "26/5/18 14:30"

console.log(formatTimestamp(msTs, 'YYYY-MM-DD hh:mm:ss a'));
// 示例输出: "2026-05-18 02:30:00 下午"

console.log(formatTimestamp(msTs, 'HH:mm:ss.SSS'));
// 示例输出: "14:30:00.000"

// 场景 4:异常输入与防御性容错测试
console.log(formatTimestamp(null));         // 输出: ""
console.log(formatTimestamp(undefined));    // 输出: ""
console.log(formatTimestamp("abc-invalid")); // 输出: ""
console.log(formatTimestamp(""));            // 输出: ""
注意事项:为什么使用单次正则替换 pattern.replace(regex, match => tokens[match]),而不是通过链式的 pattern.replace('YYYY', y).replace('YY', ...)?因为链式替换存在严重的“二次替换覆盖”隐患。例如,如果先替换了某个 Token 生成的字符串正好包含后续的占位字符,后续的 replace 将篡改已经格式化好的内容。单次扫描替换保证每个 Token 仅被消费一次。

五、工程化进阶建议与速查总结

在跨国业务或服务端渲染(SSR)项目中,还需要关注时区(Timezone)的统一性。JavaScript 默认的 getFullYear()、getHours() 等方法均读取宿主环境的本地时区。如果项目要求统一按照 UTC 时间呈现,只需将分量提取方法替换为对应的 getUTCFullYear()、getUTCHours() 即可。

封装一个通用的JS时间戳格式化函数
图 4:格式化占位符规则速查与边界防御策略矩阵

通过上述自适应机制与正则表达式单次遍历设计,我们实现了一个兼顾轻量与高健壮性的时间戳格式化工具。它无需引入动辄数十 KB 的重量级日期类库,即可覆盖绝大多数日常前端页面中的日期展示需求,并为后续的时区拓展提供了稳定可控的基础。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章