|
@@ -0,0 +1,73 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 统一时区工具
|
|
|
|
|
+ * 项目时区:Asia/Shanghai (UTC+8)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 为什么不用 toISOString().slice(0,10)?
|
|
|
|
|
+ * toISOString() 始终返回 UTC 时间,凌晨 0:00-7:59 在 UTC+8 时区
|
|
|
|
|
+ * 会是前一天的日期,导致每日重置等逻辑出现一天偏差。
|
|
|
|
|
+ *
|
|
|
|
|
+ * Intl.DateTimeFormat 配合 timeZone 参数能正确获取指定时区的日期。
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+export const TIMEZONE = 'Asia/Shanghai' as const;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取当前日期字符串(Asia/Shanghai 时区)
|
|
|
|
|
+ * @returns YYYY-MM-DD 格式,如 "2026-05-30"
|
|
|
|
|
+ */
|
|
|
|
|
+export function getTodayStr(): string {
|
|
|
|
|
+ const parts = new Intl.DateTimeFormat('zh-CN', {
|
|
|
|
|
+ timeZone: TIMEZONE,
|
|
|
|
|
+ year: 'numeric',
|
|
|
|
|
+ month: '2-digit',
|
|
|
|
|
+ day: '2-digit',
|
|
|
|
|
+ }).formatToParts(new Date());
|
|
|
|
|
+
|
|
|
|
|
+ const year = parts.find(p => p.type === 'year')!.value;
|
|
|
|
|
+ const month = parts.find(p => p.type === 'month')!.value;
|
|
|
|
|
+ const day = parts.find(p => p.type === 'day')!.value;
|
|
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取当前紧凑日期字符串(Asia/Shanghai 时区)
|
|
|
|
|
+ * @returns YYYYMMDD 格式,如 "20260530"
|
|
|
|
|
+ */
|
|
|
|
|
+export function getTodayStrCompact(): string {
|
|
|
|
|
+ return getTodayStr().replace(/-/g, '');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取指定日期的 ISO 字符串(保持 UTC 格式,用于 API 传输)
|
|
|
|
|
+ * @param date - Date 对象,不传则用当前时间
|
|
|
|
|
+ * @returns ISO 8601 格式
|
|
|
|
|
+ */
|
|
|
|
|
+export function toISOString(date?: Date): string {
|
|
|
|
|
+ return (date || new Date()).toISOString();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 格式化日期为 Asia/Shanghai 时区的 ISO 风格字符串
|
|
|
|
|
+ * @param date - Date 对象
|
|
|
|
|
+ * @returns 如 "2026-05-30T12:00:00+08:00"
|
|
|
|
|
+ */
|
|
|
|
|
+export function toTZISOString(date: Date): string {
|
|
|
|
|
+ const parts = new Intl.DateTimeFormat('zh-CN', {
|
|
|
|
|
+ timeZone: TIMEZONE,
|
|
|
|
|
+ year: 'numeric',
|
|
|
|
|
+ month: '2-digit',
|
|
|
|
|
+ day: '2-digit',
|
|
|
|
|
+ hour: '2-digit',
|
|
|
|
|
+ minute: '2-digit',
|
|
|
|
|
+ second: '2-digit',
|
|
|
|
|
+ hour12: false,
|
|
|
|
|
+ }).formatToParts(date);
|
|
|
|
|
+
|
|
|
|
|
+ const y = parts.find(p => p.type === 'year')!.value;
|
|
|
|
|
+ const mo = parts.find(p => p.type === 'month')!.value;
|
|
|
|
|
+ const d = parts.find(p => p.type === 'day')!.value;
|
|
|
|
|
+ const h = parts.find(p => p.type === 'hour')!.value;
|
|
|
|
|
+ const mi = parts.find(p => p.type === 'minute')!.value;
|
|
|
|
|
+ const s = parts.find(p => p.type === 'second')!.value;
|
|
|
|
|
+ return `${y}-${mo}-${d}T${h}:${mi}:${s}+08:00`;
|
|
|
|
|
+}
|