社交動態、評論系統、通知列表常需將絕對時間轉為相對人性化描述。本文提供生產級實現,支援多語言、快取友善、邊界情境處理。
['year', '年'],
MONTH => ['month', '個月'],
WEEK => ['week', '週'],
DAY => ['day', '天'],
HOUR => ['hour', '小時'],
MINUTE => ['minute', '分鐘'],
];
foreach ($units as $seconds => [$en, $zh]) {
if ($diff >= $seconds) {
$value = (int)($diff / $seconds);
return match ($lang) {
'en' => "$value $en" . ($value > 1 ? 's' : '') . ' ago',
'zh-TW', 'zh-CN' => "$value$zh前",
default => "$value $en ago",
};
}
}
return $lang === 'en' ? 'just now' : '剛剛';
}
function human_time_advanced(int $timestamp, string $lang = 'zh-TW'): string
{
$now = time();
$diff = $now - $timestamp;
if ($diff < 0) return date('Y-m-d H:i', $timestamp);
// 同一天內
if (date('Y-m-d', $timestamp) === date('Y-m-d', $now)) {
if ($diff < 60) return $lang === 'en' ? 'just now' : '剛剛';
if ($diff < 3600) return sprintf($lang === 'en' ? '%d minutes ago' : '%d分鐘前', $diff / 60);
return date('H:i', $timestamp); // 今天 14:30
}
// 昨天
$yesterday = strtotime('-1 day', strtotime(date('Y-m-d', $now)));
if (date('Y-m-d', $timestamp) === date('Y-m-d', $yesterday)) {
return ($lang === 'en' ? 'Yesterday ' : '昨天 ') . date('H:i', $timestamp);
}
// 前天
$dayBeforeYesterday = strtotime('-2 day', strtotime(date('Y-m-d', $now)));
if (date('Y-m-d', $timestamp) === date('Y-m-d', $dayBeforeYesterday)) {
return ($lang === 'en' ? 'Day before yesterday ' : '前天 ') . date('H:i', $timestamp);
}
// 今年內:M月D日
if (date('Y', $timestamp) === date('Y', $now)) {
return $lang === 'en'
? date('M j', $timestamp)
: date('m月d日', $timestamp);
}
// 更早:Y年M月D日
return $lang === 'en'
? date('Y-m-d', $timestamp)
: date('Y年m月d日', $timestamp);
}
後端僅輸出 ISO 8601 時間戳,前端用 Intl.RelativeTimeFormat 或 dayjs 即時刷新,避免快取導致時間靜止。
{
"created_at": "2024-01-15T14:30:00+08:00", // ISO 8601
"created_at_ts": 1705300200 // Unix 時間戳 (備用)
}
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/zh-tw';
dayjs.extend(relativeTime);
dayjs.locale('zh-tw');
export function useRelativeTime(timestamp: number) {
const time = ref(dayjs.unix(timestamp).fromNow()); // "3 分鐘前"
// 每 30 秒更新一次
const timer = setInterval(() => {
time.value = dayjs.unix(timestamp).fromNow();
}, 30000);
onUnmounted(() => clearInterval(timer));
return time;
}
cache_key = "post:$id:time:" . ($ts / 60))。Vary: Accept-Language 區分語言版本快取。| 輸入時間戳 | 當前時間 | 預期輸出 (zh-TW) |
|---|---|---|
| now - 30 | now | 剛剛 |
| now - 90 | now | 1分鐘前 |
| now - 3600 | now | 1小時前 |
| now - 7200 | now | 2小時前 |
| yesterday 14:30 | now | 昨天 14:30 |
| 2 days ago 09:00 | now | 前天 09:00 |
| 2024-01-01 | 2024-06-15 | 1月1日 |
| 2023-12-31 | 2024-01-02 | 2023年12月31日 |
| now + 3600 | now | 2024-06-15 15:30 (未來時間顯示絕對) |
carbonphp/carbon:Carbon::parse($ts)->diffForHumans() 內建多語言、測試完善。spatie/relative-time:輕量、專注相對時間。時間友善顯示看似簡單,實則涉及時區、語言、快取、前端即時更新等多維考量。建議後端統一輸出 ISO 8601 時間戳,前端負責格式化與刷新;若必須後端渲染,使用 Carbon 等成熟庫而非手寫邏輯,並採用時間桶快取策略平衡效能與準確度。