函数归一化 + 日期转换函数
你提到的“函数归一化”通常指将不同格式的输入统一转换成标准格式。下面手写一个通用的日期转换/归一化函数。
1. 完整实现
/**
* 日期归一化函数 - 将各种格式的日期输入统一转换为标准 Date 对象
* @param {*} dateInput - 支持的输入格式:Date对象、时间戳、日期字符串、年月日对象、数组
* @param {Object} options - 配置项
* @returns {Date|null} - 返回 Date 对象,无效时返回 null
*/
function normalizeDate(dateInput, options = {}) {
const {
strict = false, // 严格模式:无效输入返回 null 而非抛出错误
defaultDate = null, // 默认返回值(当输入无效时)
timezone = 'local' // 时区处理:'local' | 'utc' | 'timestamp'
} = options;
// 1. 已经是 Date 对象且有效
if (dateInput instanceof Date) {
return isValidDate(dateInput) ? normalizeTimezone(dateInput, timezone) : null;
}
// 2. 数字类型:时间戳(毫秒或秒)
if (typeof dateInput === 'number' && !isNaN(dateInput)) {
// 判断是秒还是毫秒(小于 10 位数通常是秒)
let timestamp = dateInput;
if (String(dateInput).length === 10) {
timestamp = dateInput * 1000; // 秒转毫秒
}
const date = new Date(timestamp);
return isValidDate(date) ? normalizeTimezone(date, timezone) : null;
}
// 3. 字符串类型
if (typeof dateInput === 'string') {
const trimmed = dateInput.trim();
if (trimmed === '') return null;
// 尝试解析各种字符串格式
let date = tryParseString(trimmed);
if (date && isValidDate(date)) {
return normalizeTimezone(date, timezone);
}
return null;
}
// 4. 对象类型:{ year, month, day, hour, minute, second, millisecond }
if (dateInput && typeof dateInput === 'object' && !Array.isArray(dateInput)) {
const { year, y, month, mon, day, d, hour, h, minute, min, second, sec, millisecond, ms } = dateInput;
const yVal = year || y;
const mVal = (month !== undefined ? month : mon) - 1; // 月份需要减 1
const dVal = day || d;
if (yVal !== undefined && mVal !== undefined && dVal !== undefined) {
const hVal = hour || h || 0;
const minVal = minute || min || 0;
const secVal = second || sec || 0;
const msVal = millisecond || ms || 0;
let date;
if (timezone === 'utc') {
date = new Date(Date.UTC(yVal, mVal, dVal, hVal, minVal, secVal, msVal));
} else {
date = new Date(yVal, mVal, dVal, hVal, minVal, secVal, msVal);
}
return isValidDate(date) ? date : null;
}
}
// 5. 数组类型:[year, month, day, hour, minute, second, millisecond]
if (Array.isArray(dateInput)) {
const [year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0] = dateInput;
let date;
if (timezone === 'utc') {
date = new Date(Date.UTC(year, (month || 1) - 1, day || 1, hour, minute, second, millisecond));
} else {
date = new Date(year, (month || 1) - 1, day || 1, hour, minute, second, millisecond);
}
return isValidDate(date) ? date : null;
}
// 无效输入处理
if (strict) {
throw new Error(`Invalid date input: ${dateInput}`);
}
return defaultDate;
}
/**
* 检查 Date 对象是否有效
*/
function isValidDate(date) {
return date instanceof Date && !isNaN(date.getTime());
}
/**
* 时区归一化
*/
function normalizeTimezone(date, timezone) {
if (timezone === 'utc') {
// 返回 UTC 时间字符串对应的 Date(保持原值)
return new Date(date.toUTCString());
}
if (timezone === 'timestamp') {
// 直接返回时间戳数字
return date.getTime();
}
return date; // 'local' 不做转换
}
/**
* 尝试解析字符串(处理各种常见格式)
*/
function tryParseString(str) {
// 常见格式列表
const formats = [
// ISO 8601: 2024-01-15T10:30:00.000Z
(s) => new Date(s),
// YYYY-MM-DD
(s) => {
const match = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (match) return new Date(match[1], match[2] - 1, match[3]);
return null;
},
// YYYY/MM/DD
(s) => {
const match = s.match(/^(\d{4})\/(\d{2})\/(\d{2})$/);
if (match) return new Date(match[1], match[2] - 1, match[3]);
return null;
},
// DD/MM/YYYY 或 MM/DD/YYYY(需要指定)
(s) => {
const match = s.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
if (match) {
// 默认按 MM/DD/YYYY 处理
return new Date(match[3], match[1] - 1, match[2]);
}
return null;
},
// 中文格式:2024年01月15日
(s) => {
const match = s.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
if (match) return new Date(match[1], match[2] - 1, match[3]);
return null;
}
];
for (const parser of formats) {
const result = parser(str);
if (result && isValidDate(result)) {
return result;
}
}
return null;
}
2. 辅助格式化函数
/**
* 将日期格式化为指定字符串
* @param {Date|string|number} dateInput - 日期输入
* @param {string} format - 格式模板,如 'YYYY-MM-DD HH:mm:ss'
*/
function formatDate(dateInput, format = 'YYYY-MM-DD HH:mm:ss') {
const date = normalizeDate(dateInput);
if (!date) return '';
const pad = (n) => String(n).padStart(2, '0');
const replacements = {
'YYYY': date.getFullYear(),
'YY': String(date.getFullYear()).slice(-2),
'MM': pad(date.getMonth() + 1),
'M': date.getMonth() + 1,
'DD': pad(date.getDate()),
'D': date.getDate(),
'HH': pad(date.getHours()),
'H': date.getHours(),
'hh': pad(date.getHours() % 12 || 12),
'h': date.getHours() % 12 || 12,
'mm': pad(date.getMinutes()),
'm': date.getMinutes(),
'ss': pad(date.getSeconds()),
's': date.getSeconds(),
'SSS': String(date.getMilliseconds()).padStart(3, '0'),
'A': date.getHours() < 12 ? 'AM' : 'PM',
'a': date.getHours() < 12 ? 'am' : 'pm'
};
return format.replace(/YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g, (match) => replacements[match]);
}
/**
* 获取相对时间描述(如“3分钟前”)
*/
function timeAgo(dateInput, lang = 'zh') {
const date = normalizeDate(dateInput);
if (!date) return '';
const now = new Date();
const diff = (now - date) / 1000; // 秒差
const intervals = {
zh: [
{ limit: 60, unit: '秒前', divisor: 1 },
{ limit: 3600, unit: '分钟前', divisor: 60 },
{ limit: 86400, unit: '小时前', divisor: 3600 },
{ limit: 2592000, unit: '天前', divisor: 86400 },
{ limit: 31536000, unit: '个月前', divisor: 2592000 },
{ limit: Infinity, unit: '年前', divisor: 31536000 }
],
en: [
{ limit: 60, unit: 'seconds ago', divisor: 1 },
{ limit: 3600, unit: 'minutes ago', divisor: 60 },
{ limit: 86400, unit: 'hours ago', divisor: 3600 },
{ limit: 2592000, unit: 'days ago', divisor: 86400 },
{ limit: 31536000, unit: 'months ago', divisor: 2592000 },
{ limit: Infinity, unit: 'years ago', divisor: 31536000 }
]
};
const rules = intervals[lang] || intervals.zh;
for (const rule of rules) {
if (diff < rule.limit) {
const value = Math.floor(diff / rule.divisor);
return value <= 0 ? (lang === 'zh' ? '刚刚' : 'just now') : `${value} ${rule.unit}`;
}
}
return formatDate(date, lang === 'zh' ? 'YYYY-MM-DD' : 'YYYY-MM-DD');
}
3. 测试用例
// 测试归一化
console.log(normalizeDate('2024-01-15')); // Date: 2024-01-15
console.log(normalizeDate(1705305600000)); // 时间戳毫秒
console.log(normalizeDate(1705305600)); // 时间戳秒(自动识别)
console.log(normalizeDate({ year: 2024, month: 1, day: 15 })); // 对象
console.log(normalizeDate([2024, 1, 15, 10, 30])); // 数组
// 测试格式化
console.log(formatDate('2024-01-15', 'YYYY年MM月DD日')); // 2024年01月15日
console.log(formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss.SSS')); // 当前时间
console.log(formatDate(1705305600000, 'hh:mm A')); // 12:00 AM
// 测试相对时间
console.log(timeAgo(new Date(Date.now() - 120000))); // 2分钟前
console.log(timeAgo(new Date(Date.now() - 86400000))); // 1天前
核心设计要点
| 功能 | 实现方式 |
|---|---|
| 类型判断 | 依次检测 Date、number、string、object、array |
| 时间戳识别 | 根据位数(10位秒/13位毫秒)自动转换 |
| 字符串解析 | 支持 ISO、YYYY-MM-DD、中文格式等 |
| 时区处理 | local / utc / timestamp 三种模式 |
| 错误处理 | 严格模式抛错 / 宽松模式返回 null/默认值 |
| 格式模板 | 正则替换实现自定义格式 |