Merge branch 'fork/ming4762/timezone-fix-20251030'

# Conflicts:
#	packages/stores/src/modules/timezone.ts
This commit is contained in:
Jin Mao 2025-10-31 09:08:08 +08:00
commit 2cb3dcf499
3 changed files with 185 additions and 23 deletions

View File

@ -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');
});
});
});

View File

@ -5,20 +5,22 @@ 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');
}
return date.tz().format(format);
} catch (error) {
console.error(`Error formatting date: ${error}`);
return time;
return String(time);
}
}
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;
};

View File

@ -1,9 +1,12 @@
import { ref, unref } from "vue";
import { ref, unref } from 'vue';
import { DEFAULT_TIME_ZONE_OPTIONS } from "@vben-core/preferences";
import { getTimezone, setDefaultTimezone } from "@vben-core/shared/utils";
import { DEFAULT_TIME_ZONE_OPTIONS } from '@vben-core/preferences';
import {
getCurrentTimezone,
setCurrentTimezone,
} from '@vben-core/shared/utils';
import { acceptHMRUpdate, defineStore } from "pinia";
import { acceptHMRUpdate, defineStore } from 'pinia';
interface TimezoneHandler {
getTimezone?: () => Promise<null | string | undefined>;
@ -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);
}
/**
@ -102,14 +103,16 @@ const useTimezoneStore = defineStore(
initTimezone().catch((error) => {
console.error('Failed to initialize timezone during store setup:', error);
});
function $reset() {
timezoneRef.value = getCurrentTimezone();
}
return {
timezone: timezoneRef,
setTimezone,
getTimezoneOptions,
$reset
$reset,
};
},
{