Merge branch 'fork/ming4762/timezone-fix-20251030'
# Conflicts: # packages/stores/src/modules/timezone.ts
This commit is contained in:
commit
2cb3dcf499
143
packages/@core/base/shared/src/utils/__tests__/date.test.ts
Normal file
143
packages/@core/base/shared/src/utils/__tests__/date.test.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -5,20 +5,22 @@ import utc from 'dayjs/plugin/utc';
|
|||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
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 {
|
try {
|
||||||
const date = dayjs(time);
|
const date = dayjs.isDayjs(time) ? time : dayjs(time);
|
||||||
if (!date.isValid()) {
|
if (!date.isValid()) {
|
||||||
throw new Error('Invalid date');
|
throw new Error('Invalid date');
|
||||||
}
|
}
|
||||||
return date.tz().format(format);
|
return date.tz().format(format);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error formatting date: ${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');
|
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);
|
return dayjs.isDayjs(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置默认时区
|
|
||||||
* @param timezone
|
|
||||||
*/
|
|
||||||
export const setDefaultTimezone = (timezone?: string) => {
|
|
||||||
timezone ? dayjs.tz.setDefault(timezone) : dayjs.tz.setDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前时区
|
* 获取当前时区
|
||||||
* @returns 当前时区
|
* @returns 当前时区
|
||||||
*/
|
*/
|
||||||
export const getTimezone = () => {
|
export const getSystemTimezone = () => {
|
||||||
return dayjs.tz.guess();
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { ref, unref } from "vue";
|
import { ref, unref } from 'vue';
|
||||||
|
|
||||||
import { DEFAULT_TIME_ZONE_OPTIONS } from "@vben-core/preferences";
|
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";
|
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||||
|
|
||||||
interface TimezoneHandler {
|
interface TimezoneHandler {
|
||||||
getTimezone?: () => Promise<null | string | undefined>;
|
getTimezone?: () => Promise<null | string | undefined>;
|
||||||
@ -59,9 +62,7 @@ const getTimezoneHandler = () => {
|
|||||||
const useTimezoneStore = defineStore(
|
const useTimezoneStore = defineStore(
|
||||||
'core-timezone',
|
'core-timezone',
|
||||||
() => {
|
() => {
|
||||||
const timezoneRef = ref(
|
const timezoneRef = ref(getCurrentTimezone());
|
||||||
getTimezone() || new Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化时区
|
* 初始化时区
|
||||||
@ -74,7 +75,7 @@ const useTimezoneStore = defineStore(
|
|||||||
timezoneRef.value = timezone;
|
timezoneRef.value = timezone;
|
||||||
}
|
}
|
||||||
// 设置dayjs默认时区
|
// 设置dayjs默认时区
|
||||||
setDefaultTimezone(unref(timezoneRef));
|
setCurrentTimezone(unref(timezoneRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +88,7 @@ const useTimezoneStore = defineStore(
|
|||||||
await timezoneHandler.setTimezone?.(timezone);
|
await timezoneHandler.setTimezone?.(timezone);
|
||||||
timezoneRef.value = timezone;
|
timezoneRef.value = timezone;
|
||||||
// 设置dayjs默认时区
|
// 设置dayjs默认时区
|
||||||
setDefaultTimezone(timezone);
|
setCurrentTimezone(timezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,14 +103,16 @@ const useTimezoneStore = defineStore(
|
|||||||
initTimezone().catch((error) => {
|
initTimezone().catch((error) => {
|
||||||
console.error('Failed to initialize timezone during store setup:', error);
|
console.error('Failed to initialize timezone during store setup:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
function $reset() {
|
function $reset() {
|
||||||
|
timezoneRef.value = getCurrentTimezone();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timezone: timezoneRef,
|
timezone: timezoneRef,
|
||||||
setTimezone,
|
setTimezone,
|
||||||
getTimezoneOptions,
|
getTimezoneOptions,
|
||||||
$reset
|
$reset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user