Skip to content

Commit f160ff8

Browse files
author
zhaoge
committed
feat: upgrade dayjs's version and set default timezone for dayjs
1 parent 03e7339 commit f160ff8

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"vitepress": "^1.6.3"
7171
},
7272
"dependencies": {
73-
"dayjs": "^1.10.6",
73+
"dayjs": "^1.11.13",
7474
"idb": "^8.0.0",
7575
"js-base64": "^3.7.7",
7676
"js-cookie": "^3.0.5",

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/formatDateTime/__test__/index.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dayjs from 'dayjs';
22

3-
import formatDateTime, { DateTimeFormat } from '..';
3+
import formatDateTime, { DateTimeFormat } from '../';
44

55
describe('formatDateTime', () => {
66
const testDate = new Date('2023-05-15T14:30:45');
@@ -92,6 +92,9 @@ describe('formatDateTime', () => {
9292
// Year boundaries
9393
const yearMin = new Date('0001-01-01T00:00:00');
9494
expect(formatDateTime(yearMin, DateTimeFormat.YEAR)).toBe('0001');
95+
expect(formatDateTime(yearMin, DateTimeFormat.DATE)).toBe('0001-01-01');
96+
expect(formatDateTime(yearMin, DateTimeFormat.DATE_TIME_12)).toBe('0001-01-01 12:00 AM');
97+
expect(formatDateTime(yearMin, DateTimeFormat.STANDARD)).toBe('0001-01-01 00:00:00');
9598

9699
const yearMax = new Date('9999-12-31T23:59:59');
97100
expect(formatDateTime(yearMax, DateTimeFormat.YEAR)).toBe('9999');

src/formatDateTime/index.ts

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
import dayjs from 'dayjs';
2+
import timezone from 'dayjs/plugin/timezone'; // ES 2015
3+
import utc from 'dayjs/plugin/utc'; // ES 2015
4+
5+
dayjs.extend(utc);
6+
dayjs.extend(timezone);
7+
// 默认时区:东八时区 (Asia/Shanghai)
8+
const DEFAULT_TIMEZONE = 'Asia/Shanghai';
9+
/**
10+
* 将时间转换为默认时区
11+
* @param {string | number | Date} time - 输入时间
12+
* @return {dayjs.Dayjs} 带时区的Dayjs对象
13+
*/
14+
dayjs.tz.setDefault(DEFAULT_TIMEZONE);
215
/**
316
* @category 枚举
417
* 日期和时间格式模式的枚举
@@ -117,16 +130,115 @@ type FormatPattern = DateTimeFormat | string;
117130
* formatDateTime(new Date(), "dddd, MMMM D, YYYY") // dayjs.Dayjs
118131
* ```
119132
*/
133+
/**
134+
* Helper function to convert date to default timezone
135+
*/
136+
const toDefaultTz = (date: DateTimeInput): dayjs.Dayjs => {
137+
// 如果已经是 dayjs 对象且支持 tz,直接转换到默认时区
138+
if ((date as any) && typeof (date as any).tz === 'function') {
139+
return (date as any).tz(DEFAULT_TIMEZONE);
140+
}
141+
// 否则使用 dayjs.tz 将输入解析为默认时区的 dayjs 对象
142+
// 对于无效日期,dayjs.tz 可能会抛出错误,此时降级到普通 dayjs
143+
try {
144+
return (dayjs as any).tz(date, DEFAULT_TIMEZONE);
145+
} catch {
146+
return dayjs(date);
147+
}
148+
};
149+
150+
/**
151+
* Normalize input to native Date object
152+
*/
153+
const getNativeDate = (date: DateTimeInput): Date | null => {
154+
if ((date as any) && typeof (date as any).toDate === 'function') {
155+
return (date as any).toDate();
156+
} else if (date instanceof Date) {
157+
return date as Date;
158+
} else {
159+
const d = new Date(date as any);
160+
return Number.isNaN(d.getTime()) ? null : d;
161+
}
162+
};
163+
164+
/**
165+
* Check if a date is an edge-case year (year < 1900)
166+
* Edge-case years may have parsing inconsistencies in dayjs/timezone
167+
*/
168+
const isEdgeCaseYear = (date: DateTimeInput): boolean => {
169+
const nativeDate = getNativeDate(date);
170+
if (!nativeDate) return false;
171+
return nativeDate.getFullYear() < 1900;
172+
};
173+
174+
/**
175+
* Format native Date object using dayjs-like format string
176+
* Leverages dayjs for standard tokens, only customizing where needed
177+
*/
178+
const formatNativeDate = (nativeDate: Date, format: string): string => {
179+
const pad = (num: number, len: number = 2) => String(num).padStart(len, '0');
180+
const d = nativeDate;
181+
182+
// Use dayjs for all standard token formatting
183+
const dayjsInstance = dayjs(d);
184+
185+
// Only define custom tokens that need special handling or differ from dayjs defaults
186+
const customTokens: Record<string, () => string> = {
187+
// Timezone offset with colon (e.g., +08:00)
188+
Z: () => {
189+
const offset = d.getTimezoneOffset();
190+
const sign = offset <= 0 ? '+' : '-';
191+
const h = Math.abs(Math.floor(offset / 60));
192+
const m = Math.abs(offset % 60);
193+
return `${sign}${pad(h)}:${pad(m)}`;
194+
},
195+
// Timezone offset without separator (e.g., +0800)
196+
ZZ: () => {
197+
const offset = d.getTimezoneOffset();
198+
const sign = offset <= 0 ? '+' : '-';
199+
const h = Math.abs(Math.floor(offset / 60));
200+
const m = Math.abs(offset % 60);
201+
return `${sign}${pad(h)}${pad(m)}`;
202+
},
203+
};
204+
205+
// Sort tokens by length (descending) to avoid partial matches (e.g., Z before ZZ)
206+
const sortedCustomTokens = Object.keys(customTokens).sort((a, b) => b.length - a.length);
207+
208+
let result = format;
209+
210+
// First, replace our custom tokens
211+
for (const token of sortedCustomTokens) {
212+
const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
213+
result = result.replace(regex, customTokens[token]());
214+
}
215+
216+
// Then, use dayjs to handle all standard tokens (YYYY, MM, DD, HH, mm, ss, etc.)
217+
// This ensures consistency with dayjs behavior for all non-custom tokens
218+
result = dayjsInstance.format(result);
219+
220+
return result;
221+
};
222+
120223
export const formatDateTime = (
121224
date: DateTimeInput,
122225
format: FormatPattern = DateTimeFormat.STANDARD
123226
): string | dayjs.Dayjs => {
124227
const isValidFormat = Object.values<string>(DateTimeFormat).includes(format);
125-
126228
if (!isValidFormat) {
127-
return dayjs(date);
229+
return toDefaultTz(date);
230+
}
231+
232+
// 检测是否为极早年份(< 1900):边缘情况年份可能存在解析问题,因此需要特殊处理
233+
if (isEdgeCaseYear(date)) {
234+
const nativeDate = getNativeDate(date);
235+
if (!nativeDate) {
236+
return 'Invalid Date';
237+
}
238+
return formatNativeDate(nativeDate, format as string);
128239
}
129-
return dayjs(date).format(format);
240+
241+
return toDefaultTz(date).format(format);
130242
};
131243

132244
export default formatDateTime;

0 commit comments

Comments
 (0)