import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" const TIMEZONE = "Asia/Shanghai" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } function getShanghaiOffsetMs(): number { const formatter = new Intl.DateTimeFormat("en-US", { timeZone: TIMEZONE, timeZoneName: "short", }) const parts = formatter.formatToParts(new Date()) const tzPart = parts.find((p) => p.type === "timeZoneName") if (!tzPart) return 8 * 60 * 60 * 1000 const match = tzPart.value.match(/^([+-])(\d{2}):?(\d{2})$/) if (!match) return 8 * 60 * 60 * 1000 const sign = match[1] === "+" ? 1 : -1 const offsetHours = parseInt(match[2], 10) const offsetMinutes = parseInt(match[3], 10) return sign * (offsetHours * 60 + offsetMinutes) * 60 * 1000 } export function formatDateForDatetimeLocal(date: Date): string { const shanghaiOffsetMs = getShanghaiOffsetMs() const localTimeMs = date.getTime() + shanghaiOffsetMs const localDate = new Date(localTimeMs) const year = localDate.getUTCFullYear() const month = String(localDate.getUTCMonth() + 1).padStart(2, "0") const day = String(localDate.getUTCDate()).padStart(2, "0") const hours = String(localDate.getUTCHours()).padStart(2, "0") const minutes = String(localDate.getUTCMinutes()).padStart(2, "0") return `${year}-${month}-${day}T${hours}:${minutes}` } export function parseDateTimeLocalToUTC_v2(year: number, month: number, day: number, hours: number, minutes: number): Date { const localTimeMs = Date.UTC(year, month, day, hours, minutes) const shanghaiOffsetMs = getShanghaiOffsetMs() const utcMs = localTimeMs - shanghaiOffsetMs return new Date(utcMs) } export function parseDateTimeLocalToUTC(value: string): Date | null { if (!value) return null const [datePart, timePart] = value.split("T") if (!datePart) return null const [y, m, d] = datePart.split("-").map(Number) const [h = 0, min = 0] = timePart ? timePart.split(":").map(Number) : [0, 0] return parseDateTimeLocalToUTC_v2(y, m - 1, d, h, min) } export function nowInShanghai(): Date { const now = new Date() const utcStr = now.toLocaleString("en-US", { timeZone: "UTC" }) const utcDate = new Date(utcStr) const shanghaiOffset = getTimezoneOffset("Asia/Shanghai") const utcOffset = 0 return new Date(utcDate.getTime() + (shanghaiOffset - utcOffset)) } export function formatDateForDatetimeLocal(date: Date): string { const zoned = toZonedTime(date, TIMEZONE) const year = zoned.getFullYear() const month = String(zoned.getMonth() + 1).padStart(2, "0") const day = String(zoned.getDate()).padStart(2, "0") const hours = String(zoned.getHours()).padStart(2, "0") const minutes = String(zoned.getMinutes()).padStart(2, "0") return `${year}-${month}-${day}T${hours}:${minutes}` } function getTimezoneOffset(timezone: string): number { const formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone, timeZoneName: "longOffset", }) const parts = formatter.formatToParts(new Date()) const tzPart = parts.find((p) => p.type === "timeZoneName") if (!tzPart) return 0 const match = tzPart.value.match(/([+-]?\d{1,2}):?(\d{2})/) if (!match) return 0 const hours = parseInt(match[1], 10) const minutes = parseInt(match[2], 10) return (hours * 60 + minutes) * 60 * 1000 } export function parseDateTimeLocalToUTC(value: string): Date | null { if (!value) return null const zoned = fromZonedTime(value, TIMEZONE) return zoned } export function parseDateTimeLocalToUTC_v2(year: number, month: number, day: number, hours: number, minutes: number): Date { const shanghaiTime = new Date() shanghaiTime.setFullYear(year) shanghaiTime.setMonth(month) shanghaiTime.setDate(day) shanghaiTime.setHours(hours, minutes, 0, 0) const shanghaiOffsetMs = getShanghaiOffsetMs(shanghaiTime) const utcMs = shanghaiTime.getTime() - shanghaiOffsetMs return new Date(utcMs) } function getShanghaiOffsetMs(date: Date): number { const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "Asia/Shanghai", timeZoneName: "short", }) const parts = formatter.formatToParts(date) const tzPart = parts.find((p) => p.type === "timeZoneName") if (!tzPart) return 8 * 60 * 60 * 1000 const match = tzPart.value.match(/([+-])(\d{2}):?(\d{2})/) if (!match) return 8 * 60 * 60 * 1000 const sign = match[1] === "+" ? 1 : -1 const offsetHours = parseInt(match[2], 10) const offsetMinutes = parseInt(match[3], 10) return sign * (offsetHours * 60 + offsetMinutes) * 60 * 1000 }