import { isDate } from "./is.ts" /** * @description Check whether a date is outdated (in the past). */ export const temporalIsOutdated = (target: unknown): boolean => { return isDate(target) && new Date(target).getTime() < Date.now() } /** * @description Format a timestamp to a YYYY-MM-DD string. * * @example * ``` * // Expect: "1970-01-01" * const example1 = temporalFormatToYYYYMMDD(0) * // Expect: "2000/01/02" * const example2 = temporalFormatToYYYYMMDD(946771200000, "/") * ``` */ export const temporalFormatToYYYYMMDD = (timestamp: number, separator = "-"): string => { const date = new Date(timestamp) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, "0") const day = String(date.getDate()).padStart(2, "0") return `${year}${separator}${month}${separator}${day}` } /** * @description Format a timestamp to a hh:mm:ss string. * * @example * ``` * // Expect: "00:00:00" * const example1 = temporalFormatTohhmmss(0) * // Expect: "12:34:56" * const example2 = temporalFormatTohhmmss(45296000) * ``` */ export const temporalFormatTohhmmss = (timestamp: number, separator = ":"): string => { const date = new Date(timestamp) const hours = String(date.getHours()).padStart(2, "0") const minutes = String(date.getMinutes()).padStart(2, "0") const seconds = String(date.getSeconds()).padStart(2, "0") return `${hours}${separator}${minutes}${separator}${seconds}` } /** * @description Format a timestamp to a YYYY-MM-DD hh:mm:ss string. * * @example * ``` * // Expect: "1970-01-01 00:00:00" * const example1 = temporalFormatToYYYYMMDDhhmmss(0) * // Expect: "2000-01-02 00:00:00" * const example2 = temporalFormatToYYYYMMDDhhmmss(946771200000) * ``` */ export const temporalFormatToYYYYMMDDhhmmss = (timestamp: number): string => { return `${temporalFormatToYYYYMMDD(timestamp)} ${temporalFormatTohhmmss(timestamp)}` } /** * @description Format a timestamp to a relative time string (e.g., "刚刚", "5 分钟前"). * * @example * ``` * // Expect: "刚刚" * const originalNow = Date.now * Date.now = () => 3_600_000 * const example1 = temporalFormatToRelativeTime(3_600_000) * // Expect: "1 小时前" * const example2 = temporalFormatToRelativeTime(0) * Date.now = originalNow * ``` */ export const temporalFormatToRelativeTime = (timestamp: number): string => { const now = Date.now() const diffInSeconds = Math.floor((now - timestamp) / 1_000) if (diffInSeconds < 60) { return "刚刚" } else if (diffInSeconds < 3_600) { const minutes = Math.floor(diffInSeconds / 60) return `${minutes} 分钟前` } else if (diffInSeconds < 86_400) { const hours = Math.floor(diffInSeconds / 3_600) return `${hours} 小时前` } else if (diffInSeconds < 172_800) { return "昨天" } else if (diffInSeconds < 604_800) { return "一周内" } else { // 格式化为 YYYY-MM-DD hh:ss return temporalFormatToYYYYMMDDhhmmss(timestamp) } } export interface TemporalFormatToWechatRelativeTimeOptions { timestamp: number alwaysShowTime?: boolean | undefined } /** * @description Format a timestamp to a WeChat-style relative time string. * * @example * ``` * // Expect: "70/01/01" * const example1 = temporalFormatToWechatRelativeTime({ timestamp: 0 }) * // Expect: "70/01/01 00:00" * const example2 = temporalFormatToWechatRelativeTime({ timestamp: 0, alwaysShowTime: true }) * ``` */ export const temporalFormatToWechatRelativeTime = ( options: TemporalFormatToWechatRelativeTimeOptions, ): string => { const { timestamp, alwaysShowTime = false } = options const now = new Date() const targetDate = new Date(timestamp) const padZero = (num: number): string => num.toString().padStart(2, "0") const isSameDay = (date1: Date, date2: Date): boolean => { return ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ) } const isYesterday = (date1: Date, date2: Date): boolean => { const yesterday = new Date(date1) yesterday.setDate(date1.getDate() - 1) return isSameDay(yesterday, date2) } const getWeekDayName = (date: Date): string => { const weekdays = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"] return weekdays[date.getDay()]! } const isSameWeek = (date1: Date, date2: Date): boolean => { const startOfWeek = (date: Date): Date => { const result = new Date(date) const day = result.getDay() const diff = result.getDate() - day + (day === 0 ? -6 : 1) result.setDate(diff) return new Date(result.getFullYear(), result.getMonth(), result.getDate()) } return startOfWeek(date1).getTime() === startOfWeek(date2).getTime() } const date = `${padZero(targetDate.getFullYear() % 100)}/${padZero(targetDate.getMonth() + 1)}/${padZero(targetDate.getDate())}` const time = `${padZero(targetDate.getHours())}:${padZero(targetDate.getMinutes())}` if (isSameDay(now, targetDate)) { return time } else if (isYesterday(now, targetDate)) { return alwaysShowTime ? `昨天 ${time}` : "昨天" } else if (isSameWeek(now, targetDate)) { return alwaysShowTime ? `${getWeekDayName(targetDate)} ${time}` : getWeekDayName(targetDate) } else { return alwaysShowTime ? `${date} ${time}` : date } } type InternalUnitsDict = Record const INTERNAL_UNITS_DICT: InternalUnitsDict = { year: "年", month: "月", week: "周", day: "天", hour: "小时", minute: "分钟", second: "秒", millisecond: "毫秒", } type InternalUnitsMilliDict = Record const INTERNAL_UNITS_MILLI_DICT: InternalUnitsMilliDict = { year: 31_557_600_000, month: 2_629_800_000, week: 604_800_000, day: 86_400_000, hour: 3_600_000, minute: 60_000, second: 1_000, millisecond: 1, } /** * @description Convert a timestamp to a human-readable relative time string in Chinese. * * @example * ``` * // Expect: "刚刚" * const originalNow = Date.now * Date.now = () => 3_600_000 * const example1 = temporalHumanize(3_600_000) * // Expect: "1 小时前" * const example2 = temporalHumanize(0) * Date.now = originalNow * ``` */ export const temporalHumanize = (timestamp: number): string => { let res = "" const milliseconds = Date.now() - timestamp for (const key in INTERNAL_UNITS_MILLI_DICT) { if (milliseconds >= INTERNAL_UNITS_MILLI_DICT[key]!) { res = `${Math.floor(milliseconds / INTERNAL_UNITS_MILLI_DICT[key]!)} ${INTERNAL_UNITS_DICT[key]}前` break } } return res !== "" ? res : "刚刚" }