Gregarius 是早期 PHP 開發的 RSS 聚合閱讀器。其日期格式化函數 `rss_locale_date()` 在 Windows 環境下處理中文 locales 時會出現編碼問題。本文分析根因並提供修正方案。
function rss_locale_date($fmt, $ts, $addTZOffset = true) {
if (isset($_SERVER["WINDIR"])) {
$fmt = str_replace("%e", "%#d", $fmt); // Windows 相容
}
if ($addTZOffset) {
return iconv("UTF-8", "utf-8", strftime($fmt, $ts + 3600 * getConfig('rss.config.tzoffset')));
}
return iconv("UTF-8", "utf-8", strftime($fmt, $ts));
}
function rss_locale_date($fmt, $ts, $addTZOffset = true) {
// Windows 相容:%e -> %#d
if (isset($_SERVER["WINDIR"])) {
$fmt = str_replace("%e", "%#d", $fmt);
}
$timestamp = $addTZOffset
? $ts + 3600 * getConfig('rss.config.tzoffset')
: $ts;
// 取得系統當前 locale 編碼
$localeInfo = setlocale(LC_TIME, '0');
$fromEncoding = 'UTF-8';
// 常見系統 locale 對應編碼
if (strpos($localeInfo, '950') !== false || strpos($localeInfo, 'big5') !== false) {
$fromEncoding = 'Big5';
} elseif (strpos($localeInfo, '936') !== false || strpos($localeInfo, 'gbk') !== false) {
$fromEncoding = 'GBK';
}
$formatted = strftime($fmt, $timestamp);
// 僅在非 UTF-8 時轉換
if ($fromEncoding !== 'UTF-8') {
$formatted = @iconv($fromEncoding, 'UTF-8//IGNORE', $formatted);
}
return $formatted;
}
推薦: 完全避開系統 locale 依賴,使用 ICU 庫提供穩定多語言格式化。
function rss_locale_date_intl($fmt, $ts, $addTZOffset = true, $locale = 'zh_TW') {
$timestamp = $addTZOffset
? $ts + 3600 * getConfig('rss.config.tzoffset')
: $ts;
// 將 strftime 格式轉換為 ICU 格式
$icuFmt = strftimeToIcu($fmt);
$formatter = new IntlDateFormatter(
$locale,
IntlDateFormatter::FULL,
IntlDateFormatter::NONE,
'Asia/Taipei', // 時區
IntlDateFormatter::GREGORIAN,
$icuFmt
);
return $formatter->format($timestamp);
}
function strftimeToIcu($strftimeFmt) {
$map = [
'%Y' => 'yyyy', '%y' => 'yy',
'%m' => 'MM', '%d' => 'dd', '%e' => 'd',
'%H' => 'HH', '%k' => 'H', '%I' => 'hh', '%l' => 'h',
'%M' => 'mm', '%S' => 'ss',
'%A' => 'EEEE', '%a' => 'EEE',
'%B' => 'MMMM', '%b' => 'MMM',
'%p' => 'a', '%P' => 'a',
'%z' => 'Z', '%Z' => 'z',
];
return strtr($strftimeFmt, $map);
}
function rss_locale_date_modern($fmt, $ts, $addTZOffset = true) {
$timestamp = $addTZOffset
? $ts + 3600 * getConfig('rss.config.tzoffset')
: $ts;
$dt = new DateTime('@' . $timestamp);
$dt->setTimezone(new DateTimeZone('Asia/Taipei'));
// 簡易 strftime 格式轉換(支援常用子集)
$replacements = [
'%Y' => $dt->format('Y'),
'%y' => $dt->format('y'),
'%m' => $dt->format('m'),
'%d' => $dt->format('d'),
'%e' => (int)$dt->format('d'), // 不補零
'%H' => $dt->format('H'),
'%I' => $dt->format('h'),
'%M' => $dt->format('i'),
'%S' => $dt->format('s'),
'%A' => $dt->format('l'),
'%a' => $dt->format('D'),
'%B' => $dt->format('F'),
'%b' => $dt->format('M'),
'%p' => $dt->format('A'),
'%z' => $dt->format('P'),
];
return strtr($fmt, $replacements);
}
<?php
// 模擬 Gregarius 調用
$ts = strtotime('2024-01-15 14:30:00');
$fmt = "%Y年%m月%d日 %A %H:%M";
echo "原始: " . rss_locale_date($fmt, $ts) . "\n";
echo "Intl: " . rss_locale_date_intl($fmt, $ts) . "\n";
echo "Modern: " . rss_locale_date_modern($fmt, $ts) . "\n";
// 預期輸出:2024年01月15日 星期一 14:30