125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
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
|
|
} |