From 260a3e8b04f90a16cb8bf614b70c790fabfa8f0f Mon Sep 17 00:00:00 2001 From: zhaoge <> Date: Thu, 12 Feb 2026 11:21:51 +0800 Subject: [PATCH] feat: upgrade dayjs's version and set default timezone for dayjs --- package.json | 2 +- pnpm-lock.yaml | 2 +- src/formatDateTime/__test__/index.test.ts | 5 +- src/formatDateTime/index.ts | 110 +++++++++++++++++++++- 4 files changed, 113 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 15bca65..af2fd22 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "vitepress": "^1.6.3" }, "dependencies": { - "dayjs": "^1.10.6", + "dayjs": "^1.11.13", "idb": "^8.0.0", "js-base64": "^3.7.7", "js-cookie": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 727b9fa..2b7ad72 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ specifiers: commitlint: ^19.5.0 conventional-changelog-cli: ^2.0.31 cz-conventional-changelog: ^3.3.0 - dayjs: ^1.10.6 + dayjs: ^1.11.13 fake-indexeddb: ^4 gh-pages: ^6.3.0 husky: ^9.1.6 diff --git a/src/formatDateTime/__test__/index.test.ts b/src/formatDateTime/__test__/index.test.ts index df60ce7..4749f2e 100644 --- a/src/formatDateTime/__test__/index.test.ts +++ b/src/formatDateTime/__test__/index.test.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs'; -import formatDateTime, { DateTimeFormat } from '..'; +import formatDateTime, { DateTimeFormat } from '../'; describe('formatDateTime', () => { const testDate = new Date('2023-05-15T14:30:45'); @@ -92,6 +92,9 @@ describe('formatDateTime', () => { // Year boundaries const yearMin = new Date('0001-01-01T00:00:00'); expect(formatDateTime(yearMin, DateTimeFormat.YEAR)).toBe('0001'); + expect(formatDateTime(yearMin, DateTimeFormat.DATE)).toBe('0001-01-01'); + expect(formatDateTime(yearMin, DateTimeFormat.DATE_TIME_12)).toBe('0001-01-01 12:00 AM'); + expect(formatDateTime(yearMin, DateTimeFormat.STANDARD)).toBe('0001-01-01 00:00:00'); const yearMax = new Date('9999-12-31T23:59:59'); expect(formatDateTime(yearMax, DateTimeFormat.YEAR)).toBe('9999'); diff --git a/src/formatDateTime/index.ts b/src/formatDateTime/index.ts index cad6c60..362995b 100644 --- a/src/formatDateTime/index.ts +++ b/src/formatDateTime/index.ts @@ -1,4 +1,17 @@ import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; // ES 2015 +import utc from 'dayjs/plugin/utc'; // ES 2015 + +dayjs.extend(utc); +dayjs.extend(timezone); +// 默认时区:东八时区 (Asia/Shanghai) +const DEFAULT_TIMEZONE = 'Asia/Shanghai'; +/** + * 将时间转换为默认时区 + * @param {string | number | Date} time - 输入时间 + * @return {dayjs.Dayjs} 带时区的Dayjs对象 + */ +dayjs.tz.setDefault(DEFAULT_TIMEZONE); /** * @category 枚举 * 日期和时间格式模式的枚举 @@ -117,16 +130,107 @@ type FormatPattern = DateTimeFormat | string; * formatDateTime(new Date(), "dddd, MMMM D, YYYY") // dayjs.Dayjs * ``` */ +/** + * Helper function to convert date to default timezone + */ +const toDefaultTz = (date: DateTimeInput): dayjs.Dayjs => { + // 如果已经是 dayjs 对象且支持 tz,直接转换到默认时区 + if ((date as any) && typeof (date as any).tz === 'function') { + return (date as any).tz(DEFAULT_TIMEZONE); + } + // 否则使用 dayjs.tz 将输入解析为默认时区的 dayjs 对象 + // 对于无效日期,dayjs.tz 可能会抛出错误,此时降级到普通 dayjs + try { + return (dayjs as any).tz(date, DEFAULT_TIMEZONE); + } catch { + return dayjs(date); + } +}; + +/** + * Normalize input to native Date object + */ +const getNativeDate = (date: DateTimeInput): Date | null => { + if ((date as any) && typeof (date as any).toDate === 'function') { + return (date as any).toDate(); + } else if (date instanceof Date) { + return date as Date; + } else { + const d = new Date(date as any); + return Number.isNaN(d.getTime()) ? null : d; + } +}; + +/** + * Check if a date is an edge-case year (year < 1900) + * Edge-case years may have parsing inconsistencies in dayjs/timezone + */ +const isEdgeCaseYear = (date: DateTimeInput): boolean => { + const nativeDate = getNativeDate(date); + if (!nativeDate) return false; + return nativeDate.getFullYear() < 1900; +}; + +/** + * Format native Date object using dayjs-like format string + * Leverages dayjs for standard tokens, only customizing where needed + */ +const formatNativeDate = (nativeDate: Date, format: string): string => { + const pad = (num: number, len = 2) => String(num).padStart(len, '0'); + const d = nativeDate; + // 使用dayjs进行标准格式化 + const dayjsInstance = dayjs(d); + // 只维护自定义tokens,主要用于处理时区格式,因为原生Date对象不支持dayjs的时区功能 + const customTokens: Record string> = { + // 时区 Z 替换成 +08:00 格式 + Z: () => { + const offset = d.getTimezoneOffset(); + const sign = offset <= 0 ? '+' : '-'; + const h = Math.abs(Math.floor(offset / 60)); + const m = Math.abs(offset % 60); + return `${sign}${pad(h)}:${pad(m)}`; + }, + // 时区 ZZ 替换成 +0800 格式 + ZZ: () => { + const offset = d.getTimezoneOffset(); + const sign = offset <= 0 ? '+' : '-'; + const h = Math.abs(Math.floor(offset / 60)); + const m = Math.abs(offset % 60); + return `${sign}${pad(h)}${pad(m)}`; + }, + }; + + // 通过长度将 tokens 排序,避免 Z 早于 ZZ 这种情况 + const sortedCustomTokens = Object.keys(customTokens).sort((a, b) => b.length - a.length); + let result = format; + for (const token of sortedCustomTokens) { + const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); + result = result.replace(regex, customTokens[token]()); + } + result = dayjsInstance.format(result); + + return result; +}; + export const formatDateTime = ( date: DateTimeInput, format: FormatPattern = DateTimeFormat.STANDARD ): string | dayjs.Dayjs => { const isValidFormat = Object.values(DateTimeFormat).includes(format); - if (!isValidFormat) { - return dayjs(date); + return toDefaultTz(date); } - return dayjs(date).format(format); + + // 检测是否为极早年份(< 1900):边缘情况年份可能存在解析问题,因此需要特殊处理 + if (isEdgeCaseYear(date)) { + const nativeDate = getNativeDate(date); + if (!nativeDate) { + return 'Invalid Date'; + } + return formatNativeDate(nativeDate, format as string); + } + + return toDefaultTz(date).format(format); }; export default formatDateTime;