社交動態、評論系統、文章列表常需顯示「3分鐘前」、「2天前」等相對時間。本文整理 4 種函數實作與 1 種類實作,涵蓋基本版、陣列映射版、精確計算版、多語系擴展版,滿足不同精度需求。
<?php
function time_tran($the_time) {
$now_time = strtotime(date('Y-m-d H:i:s')); // 當前時間戳
$show_time = strtotime($the_time); // 目標時間戳
$diff = $now_time - $show_time; // 秒差
if ($diff < 0) return $the_time; // 未來時間直接返回原字串
if ($diff < 60) {
return $diff . '秒前';
} elseif ($diff < 3600) {
return floor($diff / 60) . '分鐘前';
} elseif ($diff < 86400) {
return floor($diff / 3600) . '小時前';
} elseif ($diff < 259200) { // 3 天內
return floor($diff / 86400) . '天前';
} else {
return $the_time; // 超過 3 天顯示完整時間
}
}
// 使用
echo time_tran('2024-01-15 10:30:00'); // 輸出: 2小時前 (視當前時間)
<?php
function format_date($timestamp) {
$diff = time() - $timestamp;
if ($diff < 0) return date('Y-m-d H:i:s', $timestamp);
$intervals = [
31536000 => '年',
2592000 => '個月',
604800 => '星期',
86400 => '天',
3600 => '小時',
60 => '分鐘',
1 => '秒'
];
foreach ($intervals as $seconds => $unit) {
$count = floor($diff / $seconds);
if ($count > 0) {
return $count . $unit . '前';
}
}
return '剛剛';
}
// 使用 (傳入時間戳)
echo format_date(strtotime('-2 hours')); // 2小時前
<?php
function formatTime($date) {
$timer = strtotime($date);
$diff = $_SERVER['REQUEST_TIME'] - $timer; // 使用請求開始時間,效能較佳
if ($diff < 0) return $date;
$day = floor($diff / 86400);
$remainder = $diff % 86400;
if ($day > 0) {
return $day . '天前';
}
$hour = floor($remainder / 3600);
$remainder %= 3600;
if ($hour > 0) return $hour . '小時前';
$min = floor($remainder / 60);
$sec = $remainder % 60;
if ($min > 0) return $min . '分鐘前';
if ($sec > 0) return $sec . '秒前';
return '剛剛';
}
<?php
class RelativeTime {
private $locale = 'zh_TW';
private $thresholds = [
'just_now' => 5, // 5 秒內顯示「剛剛」
'minute' => 60,
'hour' => 3600,
'day' => 86400,
'week' => 604800,
'month' => 2592000,
'year' => 31536000,
];
private $labels = [
'zh_TW' => [
'just_now' => '剛剛',
'seconds_ago' => '{count}秒前',
'minutes_ago' => '{count}分鐘前',
'hours_ago' => '{count}小時前',
'days_ago' => '{count}天前',
'weeks_ago' => '{count}週前',
'months_ago' => '{count}個月前',
'years_ago' => '{count}年前',
'future' => '{count}秒後',
],
'en' => [
'just_now' => 'just now',
'seconds_ago' => '{count} seconds ago',
'minutes_ago' => '{count} minutes ago',
'hours_ago' => '{count} hours ago',
'days_ago' => '{count} days ago',
'weeks_ago' => '{count} weeks ago',
'months_ago' => '{count} months ago',
'years_ago' => '{count} years ago',
'future' => 'in {count} seconds',
]
];
public function __construct($locale = 'zh_TW') {
$this->locale = $locale;
}
public function setThreshold($key, $seconds) {
if (isset($this->thresholds[$key])) {
$this->thresholds[$key] = $key;
}
}
public function format($datetime, $now = null) {
$now = $now ?: $_SERVER['REQUEST_TIME'];
$target = is_numeric($datetime) ? $datetime : strtotime($datetime);
$diff = $now - $target;
$labels = $this->labels[$this->locale] ?? $this->labels['zh_TW'];
$t = $this->thresholds;
if (abs($diff) < $t['just_now']) {
return $labels['just_now'];
}
$is_future = $diff < 0;
$diff = abs($diff);
if ($diff < $t['minute']) {
$count = $diff;
$template = $is_future ? $labels['future'] : $labels['seconds_ago'];
} elseif ($diff < $t['hour']) {
$count = floor($diff / 60);
$template = $is_future ? $labels['future'] : $labels['minutes_ago'];
} elseif ($diff < $t['day']) {
$count = floor($diff / 3600);
$template = $is_future ? $labels['future'] : $labels['hours_ago'];
} elseif ($diff < $t['week']) {
$count = floor($diff / 86400);
$template = $is_future ? $labels['future'] : $labels['days_ago'];
} elseif ($diff < $t['month']) {
$count = floor($diff / 604800);
$template = $is_future ? $labels['future'] : $labels['weeks_ago'];
} elseif ($diff < $t['year']) {
$count = floor($diff / 2592000);
$template = $is_future ? $labels['future'] : $labels['months_ago'];
} else {
$count = floor($diff / 31536000);
$template = $is_future ? $labels['future'] : $labels['years_ago'];
}
return str_replace('{count}', $count, $template);
}
}
// 使用範例
$rt = new RelativeTime('zh_TW');
echo $rt->format('-30 minutes'); // 30分鐘前
echo $rt->format('+2 hours'); // 2小時後
echo $rt->format('2024-01-01'); // 幾天前/幾個月前
echo $rt->format(time()); // 剛剛
$rt_en = new RelativeTime('en');
echo $rt_en->format('-1 day'); // 1 day ago
<?php
/**
* 發布時間計算類
* @author Solon Ring
*/
class MyGetTime {
private $createtime;
private $gettime;
public function __construct($createtime, $gettime) {
$this->createtime = $createtime;
$this->gettime = $gettime;
}
private function diff() { return $this->createtime - $this->gettime; }
public function getYears() { return $this->diff() / 31536000; }
public function getMonths() { return $this->diff() / 2592000; }
public function getWeeks() { return $this->diff() / 604800; }
public function getDays() { return $this->diff() / 86400; }
public function getHours() { return $this->diff() / 3600; }
public function getMinutes() { return $this->diff() / 60; }
public function getSeconds() { return $this->diff(); }
public function index() {
if ($this->getYears() > 1) {
return $this->getYears() > 2
? date('Y-m-d', $this->gettime) // 超過 2 年顯示日期
: intval($this->getYears()) . ' 年前';
}
if ($this->getMonths() > 1) return intval($this->getMonths()) . ' 月前';
if ($this->getDays() > 1) return intval($this->getDays()) . ' 天前';
if ($this->getHours() > 1) return intval($this->getHours()) . ' 小時前';
if ($this->getMinutes() > 1) return intval($this->getMinutes()) . ' 分鐘前';
if ($this->getSeconds() > 1) return intval($this->getSeconds()) . ' 秒前';
return '剛剛';
}
}
// 使用
$a = new MyGetTime(time(), strtotime('-25 month'));
echo $a->index(); // 2 年前 (或顯示日期)
| 實作版本 | 程式碼行數 | 擴展性 | 多語系 | 適用場景 |
|---|---|---|---|---|
| 基本判斷版 | ~20 | 低 | 否 | 簡單專案、快速原型 |
| 陣列映射版 | ~15 | 高 | 易擴展 | 一般專案、推薦 |
| 精確分級版 | ~25 | 中 | 否 | 需精確邊界控制 |
| RelativeTime 類 | ~60 | 最高 | 內建支援 | 國際化專案、長期維護 |
| MyGetTime 類 | ~35 | 中 | 否 | 舊專案相容 |
<!-- HTML -->
<script>
// 前端即時更新 (每分鐘更新一次)
document.querySelectorAll('.relative-time').forEach(el => {
const ts = parseInt(el.dataset.timestamp);
const update = () => {
const diff = Math.floor(Date.now()/1000) - ts;
// 這裡可複用 PHP 同邏輯或使用 moment.js/dayjs
el.textContent = formatRelative(diff);
el.title = el.dateTime; // 滑鼠懸停顯示完整時間
};
update();
setInterval(update, 60000);
});
</script>
最佳實踐:資料庫儲存
TIMESTAMP或DATETIME(UTC),前端傳遞 ISO 8601 格式,PHP/JS 統一使用時間戳運算,避免時區混淆。