function normalizeTimestamp(ts) { let value = parseInt(ts, 10) if (!value) value = Date.now() if (String(value).length === 10) value *= 1000 return value } function padUnit(value, width) { const text = String(value) return width > 1 ? text.padStart(width, '0') : text } export function formatTimestamp(ts, pattern = 'yyyy-mm-dd') { const date = new Date(normalizeTimestamp(ts)) const tokens = { 'y+': String(date.getFullYear()), 'm+': String(date.getMonth() + 1), 'd+': String(date.getDate()), 'h+': String(date.getHours()), 'M+': String(date.getMinutes()), 's+': String(date.getSeconds()) } let output = pattern for (const token of Object.keys(tokens)) { const match = new RegExp(`(${token})`).exec(output) if (!match) continue const width = match[1].length output = output.replace(match[1], padUnit(tokens[token], width)) } return output } export function createRelativeTimeFormatter(i18n) { const translate = (key, fallback) => { try { return i18n ? i18n.t(key) : fallback } catch (_) { return fallback } } return { translate } } export function socialRelativeTime(ts, pattern = 'yyyy-mm-dd', i18n) { const { translate } = createRelativeTimeFormatter(i18n) const seconds = Math.floor((Date.now() - normalizeTimestamp(ts)) / 1000) if (seconds < 300) return translate('find.just-now', '刚刚') if (seconds < 3600) return `${Math.floor(seconds / 60)}${translate('find.minutes-ago', '分钟前')}` if (seconds < 86400) return `${Math.floor(seconds / 3600)}${translate('find.hours-ago', '小时前')}` if (seconds < 2592000) return `${Math.floor(seconds / 86400)}${translate('find.days-ago', '天前')}` if (pattern === false) { if (seconds < 365 * 86400) { return `${Math.floor(seconds / (86400 * 30))}${translate('find.months-ago', '个月前')}` } return `${Math.floor(seconds / (86400 * 365))}${translate('find.years-ago', '年前')}` } return formatTimestamp(ts, pattern) } export function chatBubbleTime(ts, i18n) { const { translate } = createRelativeTimeFormatter(i18n) const seconds = Math.floor((Date.now() - normalizeTimestamp(ts)) / 1000) if (seconds < 86400) return formatTimestamp(ts, 'hh:MM') const weekdays = (() => { try { const list = i18n.t('find.weekdays', { returnObjects: true }) if (Array.isArray(list)) return list } catch (_) {} return ['日', '一', '二', '三', '四', '五', '六'] })() const locale = i18n ? i18n.locale : 'zh-Hans' const chineseStyle = locale.startsWith('zh') || locale === 'ja' const clock = formatTimestamp(ts, 'hh:MM') const when = new Date(normalizeTimestamp(ts)) const dayGap = new Date().getDate() - when.getDate() if (seconds < 86400 * 7) { if (dayGap === 1) return `${translate('find.yesterday', '昨天')} ${clock}` if (dayGap === 2) return `${translate('find.dayBeforeYesterday', '前天')} ${clock}` const weekLabel = weekdays[when.getDay()] return chineseStyle ? `${translate('find.week', '星期')}${weekLabel} ${clock}` : `${weekLabel} ${clock}` } if (seconds >= 86400 * 7) return formatTimestamp(ts, 'mm-dd hh:MM') return formatTimestamp(ts, 'yyyy-mm-dd hh:MM') }