diff --git a/packages/@core/base/shared/src/utils/__tests__/date.test.ts b/packages/@core/base/shared/src/utils/__tests__/date.test.ts new file mode 100644 index 00000000..46a27c58 --- /dev/null +++ b/packages/@core/base/shared/src/utils/__tests__/date.test.ts @@ -0,0 +1,143 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + formatDate, + formatDateTime, + getCurrentTimezone, + getSystemTimezone, + isDate, + isDayjsObject, + setCurrentTimezone, +} from '../date'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +describe('dateUtils', () => { + const sampleISO = '2024-10-30T12:34:56Z'; + const sampleTimestamp = Date.parse(sampleISO); + + beforeEach(() => { + // 重置时区 + dayjs.tz.setDefault(); + setCurrentTimezone(); // 重置为系统默认 + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + // =============================== + // formatDate + // =============================== + describe('formatDate', () => { + it('should format a valid ISO date string', () => { + const formatted = formatDate(sampleISO, 'YYYY/MM/DD'); + expect(formatted).toMatch(/2024\/10\/30/); + }); + + it('should format a timestamp correctly', () => { + const formatted = formatDate(sampleTimestamp); + expect(formatted).toMatch(/2024-10-30/); + }); + + it('should format a Date object', () => { + const formatted = formatDate(new Date(sampleISO)); + expect(formatted).toMatch(/2024-10-30/); + }); + + it('should format a dayjs object', () => { + const formatted = formatDate(dayjs(sampleISO)); + expect(formatted).toMatch(/2024-10-30/); + }); + + it('should return original input if date is invalid', () => { + const invalid = 'not-a-date'; + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const formatted = formatDate(invalid); + expect(formatted).toBe(invalid); + expect(spy).toHaveBeenCalledOnce(); + }); + + it('should apply given format', () => { + const formatted = formatDate(sampleISO, 'YYYY-MM-DD HH:mm'); + expect(formatted).toMatch(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/); + }); + }); + + // =============================== + // formatDateTime + // =============================== + describe('formatDateTime', () => { + it('should format date into full datetime', () => { + const result = formatDateTime(sampleISO); + expect(result).toMatch(/2024-10-30 \d{2}:\d{2}:\d{2}/); + }); + }); + + // =============================== + // isDate + // =============================== + describe('isDate', () => { + it('should return true for Date instances', () => { + expect(isDate(new Date())).toBe(true); + }); + + it('should return false for non-Date values', () => { + expect(isDate('2024-10-30')).toBe(false); + expect(isDate(null)).toBe(false); + expect(isDate(undefined)).toBe(false); + }); + }); + + // =============================== + // isDayjsObject + // =============================== + describe('isDayjsObject', () => { + it('should return true for dayjs objects', () => { + expect(isDayjsObject(dayjs())).toBe(true); + }); + + it('should return false for other values', () => { + expect(isDayjsObject(new Date())).toBe(false); + expect(isDayjsObject('string')).toBe(false); + }); + }); + + // =============================== + // getSystemTimezone + // =============================== + describe('getSystemTimezone', () => { + it('should return a valid IANA timezone string', () => { + const tz = getSystemTimezone(); + expect(typeof tz).toBe('string'); + expect(tz).toMatch(/^[A-Z]+\/[A-Z_]+/i); + }); + }); + + // =============================== + // setCurrentTimezone / getCurrentTimezone + // =============================== + describe('setCurrentTimezone & getCurrentTimezone', () => { + it('should set and retrieve the current timezone', () => { + setCurrentTimezone('Asia/Shanghai'); + expect(getCurrentTimezone()).toBe('Asia/Shanghai'); + }); + + it('should reset to system timezone when called with no args', () => { + const guessed = getSystemTimezone(); + setCurrentTimezone(); + expect(getCurrentTimezone()).toBe(guessed); + }); + + it('should update dayjs default timezone', () => { + setCurrentTimezone('America/New_York'); + const d = dayjs('2024-01-01T00:00:00Z'); + // 校验时区转换生效(小时变化) + expect(d.tz().format('HH')).not.toBe('00'); + }); + }); +}); diff --git a/packages/@core/base/shared/src/utils/date.ts b/packages/@core/base/shared/src/utils/date.ts index 4b80308b..84d03d26 100644 --- a/packages/@core/base/shared/src/utils/date.ts +++ b/packages/@core/base/shared/src/utils/date.ts @@ -5,9 +5,11 @@ import utc from 'dayjs/plugin/utc'; dayjs.extend(utc); dayjs.extend(timezone); -export function formatDate(time: number | string, format = 'YYYY-MM-DD') { +type FormatDate = Date | dayjs.Dayjs | number | string; + +export function formatDate(time: FormatDate, format = 'YYYY-MM-DD') { try { - const date = dayjs(time); + const date = dayjs.isDayjs(time) ? time : dayjs(time); if (!date.isValid()) { throw new Error('Invalid date'); } @@ -18,7 +20,7 @@ export function formatDate(time: number | string, format = 'YYYY-MM-DD') { } } -export function formatDateTime(time: number | string) { +export function formatDateTime(time: FormatDate) { return formatDate(time, 'YYYY-MM-DD HH:mm:ss'); } @@ -30,18 +32,32 @@ export function isDayjsObject(value: any): value is dayjs.Dayjs { return dayjs.isDayjs(value); } -/** - * 设置默认时区 - * @param timezone - */ -export const setDefaultTimezone = (timezone?: string) => { - timezone ? dayjs.tz.setDefault(timezone) : dayjs.tz.setDefault(); -}; - /** * 获取当前时区 * @returns 当前时区 */ -export const getTimezone = () => { +export const getSystemTimezone = () => { return dayjs.tz.guess(); }; + +/** + * 自定义设置的时区 + */ +let currentTimezone = getSystemTimezone(); + +/** + * 设置默认时区 + * @param timezone + */ +export const setCurrentTimezone = (timezone?: string) => { + currentTimezone = timezone || getSystemTimezone(); + dayjs.tz.setDefault(currentTimezone); +}; + +/** + * 获取设置的时区 + * @returns 设置的时区 + */ +export const getCurrentTimezone = () => { + return currentTimezone; +}; diff --git a/packages/stores/src/modules/timezone.ts b/packages/stores/src/modules/timezone.ts index c0b8873e..24180c60 100644 --- a/packages/stores/src/modules/timezone.ts +++ b/packages/stores/src/modules/timezone.ts @@ -1,7 +1,10 @@ import { ref, unref } from 'vue'; import { DEFAULT_TIME_ZONE_OPTIONS } from '@vben-core/preferences'; -import { getTimezone, setDefaultTimezone } from '@vben-core/shared/utils'; +import { + getCurrentTimezone, + setCurrentTimezone, +} from '@vben-core/shared/utils'; import { acceptHMRUpdate, defineStore } from 'pinia'; @@ -59,9 +62,7 @@ const getTimezoneHandler = () => { const useTimezoneStore = defineStore( 'core-timezone', () => { - const timezoneRef = ref( - getTimezone() || new Intl.DateTimeFormat().resolvedOptions().timeZone, - ); + const timezoneRef = ref(getCurrentTimezone()); /** * 初始化时区 @@ -74,7 +75,7 @@ const useTimezoneStore = defineStore( timezoneRef.value = timezone; } // 设置dayjs默认时区 - setDefaultTimezone(unref(timezoneRef)); + setCurrentTimezone(unref(timezoneRef)); } /** @@ -87,7 +88,7 @@ const useTimezoneStore = defineStore( await timezoneHandler.setTimezone?.(timezone); timezoneRef.value = timezone; // 设置dayjs默认时区 - setDefaultTimezone(timezone); + setCurrentTimezone(timezone); } /** @@ -103,10 +104,15 @@ const useTimezoneStore = defineStore( console.error('Failed to initialize timezone during store setup:', error); }); + function $reset() { + timezoneRef.value = getCurrentTimezone(); + } + return { timezone: timezoneRef, setTimezone, getTimezoneOptions, + $reset, }; }, {