首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Gregarius RSS 閱讀器中文日期格式化修復:strftime 與 iconv 編碼處理

     編輯:PHP 綜合知識

Gregarius 中文日期亂碼問題根因與修復

Gregarius 是早期 PHP 開發的 RSS 聚合閱讀器。其日期格式化函數 `rss_locale_date()` 在 Windows 環境下處理中文 locales 時會出現編碼問題。本文分析根因並提供修正方案。

問題現象

  • Windows 下 `%e` 格式不支援(需用 `%#d`)
  • `strftime()` 回傳編碼取決於系統 locale,非 UTF-8 導致中文亂碼
  • `iconv("UTF-8", "utf-8", ...)` 寫法錯誤(大小寫不一致、無實際轉換)

原始問題代碼

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));
}

根因分析

  1. Windows strftime %e: 不支援 `%e`(空格補位日期),需用 `%#d`(不補零)
  2. iconv 參數錯誤: `"UTF-8"` → `"utf-8"` 大小寫不同但指向同編碼,實際無轉換作用
  3. 系統 locale 不匹配: 若系統 locale 為 `Chinese_Taiwan.950` (Big5),`strftime` 回傳 Big5,需轉 UTF-8

修正方案一:精確編碼轉換

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;
}

修正方案二:避開 strftime,使用 IntlDateFormatter (PHP 5.3+)

推薦: 完全避開系統 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);
}

方案三:現代化 DateTime + 手動格式化(零依賴、最穩定)

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

部署建議

  • 優先使用方案三:無擴展依賴、行為一致、效能最佳
  • 若需完整 locale 支援(多語言),安裝 `php-intl` 使用方案二
  • 生產環境強制設定系統 locale:`setlocale(LC_TIME, 'zh_TW.utf8')` 或 `zh_CN.utf8`
  • Docker 部署時在 Dockerfile 安裝 locale:`RUN apt-get install -y locales && locale-gen zh_TW.UTF-8`
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved