本文目录导读:
我来详细解析源码中日期格式化的实现逻辑。
核心实现方式
基本模板替换法(最常见)
function formatDate(date, format) {
const map = {
'yyyy': date.getFullYear(),
'MM': String(date.getMonth() + 1).padStart(2, '0'),
'dd': String(date.getDate()).padStart(2, '0'),
'HH': String(date.getHours()).padStart(2, '0'),
'mm': String(date.getMinutes()).padStart(2, '0'),
'ss': String(date.getSeconds()).padStart(2, '0'),
'S': date.getMilliseconds()
};
return format.replace(/yyyy|MM|dd|HH|mm|ss|S/g, match => map[match]);
}
正则替换实现
function format(date, fmt = 'yyyy-MM-dd HH:mm:ss') {
return fmt.replace(/[yMdHmsS]+/g, (match) => {
const year = date.getFullYear();
switch(match[0]) {
case 'y':
return String(year).slice(-match.length);
case 'M':
return pad(date.getMonth() + 1, match.length);
case 'd':
return pad(date.getDate(), match.length);
case 'H':
return pad(date.getHours(), match.length);
case 'm':
return pad(date.getMinutes(), match.length);
case 's':
return pad(date.getSeconds(), match.length);
case 'S':
return date.getMilliseconds();
}
});
function pad(num, len) {
return String(num).padStart(len, '0');
}
}
主流框架实现分析
Vue.js 源码实现
// Vue 2.x 中的日期格式化
function formatDate(date, fmt) {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const str = o[k] + '';
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str));
}
}
return fmt;
function padLeftZero(str) {
return ('00' + str).substr(str.length);
}
}
Day.js 核心实现
// day.js 的格式化逻辑
const REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}/g;
function format(date, formatStr) {
const instance = this;
const locale = instance.$locale();
const result = formatStr.replace(REGEX_FORMAT, (match) => {
const args = [match, instance];
switch (match) {
case 'YYYY': return instance.year();
case 'YY': return pad(instance.year() % 100, 2);
case 'MM': return pad(instance.month() + 1, 2);
case 'M': return instance.month() + 1;
case 'DD': return pad(instance.date(), 2);
case 'D': return instance.date();
case 'HH': return pad(instance.hour(), 2);
case 'H': return instance.hour();
case 'hh': return pad(instance.hour() % 12 || 12, 2);
case 'h': return instance.hour() % 12 || 12;
case 'mm': return pad(instance.minute(), 2);
case 'm': return instance.minute();
case 'ss': return pad(instance.second(), 2);
case 's': return instance.second();
case 'SSS': return pad(instance.millisecond(), 3);
case 'S': return instance.millisecond();
default: return match;
}
});
return result;
}
关键实现技巧
补零处理
// 方法一:padStart(ES2017)
String(num).padStart(2, '0');
// 方法二:数组拼接
('00' + num).slice(-2);
// 方法三:条件判断
num < 10 ? '0' + num : String(num);
月份从0开始
// JavaScript 月份是 0-11 const month = date.getMonth() + 1; // 需要 +1
转义处理
function format(date, formatStr) {
// 支持转义字符,[YYYY] 输出 YYYY 而非年份
return formatStr.replace(/\[([^\]]+)]|Y{1,4}|M{1,2}|D{1,2}/g, (match) => {
if (match.startsWith('[')) {
return match.slice(1, -1);
}
// 正常格式化逻辑
});
}
完整封装示例
class DateFormatter {
constructor(date) {
this.date = date instanceof Date ? date : new Date(date);
}
format(formatStr = 'YYYY-MM-DD HH:mm:ss') {
const pad = (num, len = 2) => String(num).padStart(len, '0');
const tokens = {
'YYYY': this.date.getFullYear(),
'YY': String(this.date.getFullYear()).slice(-2),
'MM': pad(this.date.getMonth() + 1),
'M': this.date.getMonth() + 1,
'DD': pad(this.date.getDate()),
'D': this.date.getDate(),
'HH': pad(this.date.getHours()),
'H': this.date.getHours(),
'hh': pad(this.date.getHours() % 12 || 12),
'h': this.date.getHours() % 12 || 12,
'mm': pad(this.date.getMinutes()),
'm': this.date.getMinutes(),
'ss': pad(this.date.getSeconds()),
's': this.date.getSeconds(),
'SSS': pad(this.date.getMilliseconds(), 3),
'S': this.date.getMilliseconds()
};
return formatStr.replace(/\[([^\]]+)]|YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|S/g,
(match) => tokens[match] || match.slice(1, -1)
);
}
}
性能优化建议
- 缓存正则表达式
- 使用 Map 替代对象
- 预编译格式化模板
- 减少字符串拼接
这样的源码实现逻辑清晰、可扩展性强,是大多数日期库的核心实现方式。
标签: 日期格式化