2025-10-13 15:08:54 +08:00
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
import 'dayjs/locale/zh-cn'
|
|
|
|
|
|
import utc from 'dayjs/plugin/utc'
|
|
|
|
|
|
import timezone from 'dayjs/plugin/timezone'
|
|
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
|
|
|
|
|
|
|
|
|
|
dayjs.extend(utc)
|
|
|
|
|
|
dayjs.extend(timezone)
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
|
dayjs.locale('zh-cn')
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_TZ = 'Asia/Shanghai'
|
|
|
|
|
|
dayjs.tz.setDefault(DEFAULT_TZ)
|
|
|
|
|
|
|
|
|
|
|
|
const NUMERIC_REGEX = /^-?\d+(?:\.\d+)?$/
|
|
|
|
|
|
|
|
|
|
|
|
const coerceDayjs = (value) => {
|
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
|
|
return dayjs(value).tz(DEFAULT_TZ)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const stringValue = String(value).trim()
|
|
|
|
|
|
if (!stringValue) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (NUMERIC_REGEX.test(stringValue)) {
|
|
|
|
|
|
const numeric = Number(stringValue)
|
|
|
|
|
|
if (Number.isNaN(numeric)) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 值小于 10^12 时视为秒级时间戳,否则视为毫秒
|
|
|
|
|
|
if (Math.abs(numeric) < 1e12) {
|
|
|
|
|
|
return dayjs.unix(numeric).tz(DEFAULT_TZ)
|
|
|
|
|
|
}
|
|
|
|
|
|
return dayjs(numeric).tz(DEFAULT_TZ)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 00:25:58 +08:00
|
|
|
|
// 解析 ISO 字符串(dayjs 会自动识别时区信息,如 Z 后缀表示 UTC)
|
2026-03-06 10:14:43 +08:00
|
|
|
|
// 需要先转换为 UTC 再设置时区,否则 .tz() 只会改变显示而不会正确转换
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const parsed = dayjs(stringValue)
|
|
|
|
|
|
if (!parsed.isValid()) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
2026-03-06 10:14:43 +08:00
|
|
|
|
// 先转换为 UTC(保留原始时间值),再转换到上海时区
|
|
|
|
|
|
return parsed.utc().tz(DEFAULT_TZ)
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const parseToShanghai = (value) => coerceDayjs(value)
|
|
|
|
|
|
|
|
|
|
|
|
export const formatDateTime = (value, format = 'YYYY-MM-DD HH:mm') => {
|
|
|
|
|
|
const parsed = coerceDayjs(value)
|
|
|
|
|
|
if (!parsed) return '-'
|
|
|
|
|
|
return parsed.format(format)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const formatFullDateTime = (value) => formatDateTime(value, 'YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
|
|
|
|
|
|
export const formatRelative = (value) => {
|
|
|
|
|
|
const parsed = coerceDayjs(value)
|
|
|
|
|
|
if (!parsed) return '-'
|
|
|
|
|
|
return parsed.fromNow()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const sortByDatetimeDesc = (items, accessor) => {
|
|
|
|
|
|
const copy = [...items]
|
|
|
|
|
|
copy.sort((a, b) => {
|
|
|
|
|
|
const first = coerceDayjs(accessor(a))
|
|
|
|
|
|
const second = coerceDayjs(accessor(b))
|
|
|
|
|
|
|
|
|
|
|
|
if (!first && !second) return 0
|
|
|
|
|
|
if (!first) return 1
|
|
|
|
|
|
if (!second) return -1
|
|
|
|
|
|
return second.valueOf() - first.valueOf()
|
|
|
|
|
|
})
|
|
|
|
|
|
return copy
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default dayjs
|