本文深入讲解如何在JavaScript中封装一个通用的时间戳格式化函数,涵盖10位与13位时间戳自适应识别、非法边界防御、常用Token字典映射与高性能正则替换,提供完整可运行代码及全流程架构解析。
封装一个通用的JS时间戳格式化函数
时间:2026-11-04
浏览:200
文章目录
在前端业务开发中,时间戳格式化是一个高频但极易踩坑的基础操作。后端接口返回的时间数据标准并不统一:有的采用 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代表单月)。
二、格式化函数的分步构建流程
为了让逻辑清晰可维护,我们将格式化函数的执行逻辑划分为五个紧密配合的阶段。以下是各个环节的具体实现细节与设计考量:
步骤 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),避免短标记先被贪婪匹配导致错误切分。
三、通用函数的完整代码实现
结合上述设计,封装出的完整 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;
});
}
四、实际调用示例与多场景验证
为验证该通用函数的稳健性,我们在常见业务场景下进行输入验证:
// 场景 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() 即可。
通过上述自适应机制与正则表达式单次遍历设计,我们实现了一个兼顾轻量与高健壮性的时间戳格式化工具。它无需引入动辄数十 KB 的重量级日期类库,即可覆盖绝大多数日常前端页面中的日期展示需求,并为后续的时区拓展提供了稳定可控的基础。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。