strftime():中文,俄语和匈牙利语编码错误

时间:2018-11-29 16:05:51

标签: php encoding utf-8 chinese-locale

我想做的事情很简单:我想用中文(或俄语)打印日期(时间戳)。

对于我正在使用的所有语言

setlocale(LC_TIME, 'hu_HU.utf8', 'hu_HU.UTF-8', 'hu_HU', 'hr');
$date = strftime('%a %e %b %Y, %H:%M');

$date = utf8_encode($date);

即使没有utf8_encode(),这也会返回UTF-8字符串。一切都好。现在,当我对'zh_CN.utf8'语言环境(或'zh_CN.UTF-8''zh_CN''zh'做完全相同的操作时,这不会返回正确的日期。有或没有utf8_encode()都会返回

'2018å¹?mæ?#dæ?'

我不会说中文,但这显然是错误的。我发现它应该返回类似'年'的内容。该字符具有UTF-8十六进制编码E5 B9 B4,但是当我查看返回的String时,十六进制值错误。 (在2018年之后)有C3 A5 C2 B9 3F 6D C3 A6 ...

当我使用mb_detect_encoding()检查返回的String的编码时,它总是返回UTF-8。我期待如此,因为我使用的是'zh_CN.utf8'语言环境,该语言环境将编码设置为UTF-8。

环顾了一段时间后,我遇到了this answer of Peter。他建议在'%Y年%m月%e日'函数中使用格式strftime()。当我使用它时,我得到的结果和以前一样。

这使我想到编码错误。但这是真的吗?编码错误吗?如何将结果转换为正确的编码?

我在俄语上遇到的问题更少。

1 个答案:

答案 0 :(得分:0)

解决方案

我花了几个小时才找到正确的编码。 strftime()传递UTF-8字符串。有关详细信息,请查看此答案的底部。我最后得到了一个formatTime()函数,该函数以正确的编码(对我来说UTF-8)为我提供了正确的时间。

function formatTime($format, $language = null, $timestamp = null){
    switch($language){
        case 'chinese':
            $locale = setlocale(LC_TIME, 'zh_CN.utf8', 'zh_CN.UTF-8', 'zh_CN', 'zh');
            break;
        case 'hungarian':
            $locale = setlocale(LC_TIME, 'hu_HU.utf8', 'hu_HU.UTF-8', 'hu_HU', 'hr');
            break;
        case 'russian':
            $locale = setlocale(LC_TIME, 'ru_RU.utf8', 'ru_RU.UTF-8', 'ru_RU', 'ru');
            break;
        case 'german':
            $locale = setlocale(LC_TIME, 'de_DE.utf8', 'de_DE.UTF-8', 'de_DE', 'de');
            break;
        case 'french':
            $locale = setlocale(LC_TIME, 'fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR', 'fr');
            break;
        case 'polish':
            $locale = setlocale(LC_TIME, 'pl_PL.utf8', 'pl_PL.UTF-8', 'pl_PL', 'pl');
            break;
        case 'turkish':
            $locale = setlocale(LC_TIME, 'tr_TR.utf8', 'tr_TR.UTF-8', 'tr_TR', 'tr');
            break;
        case 'english':
            $locale = setlocale(LC_TIME, 'en_GB.utf8', 'en_GB.UTF-8', 'en_GB', 'en');
            break;
        // ...
        default: break;
    }

    if(!is_numeric($timestamp)){
        $datetime = strftime($format);
    }
    else{
        $datetime = strftime($format, $timestamp);
    }

    $current_locale = strtolower(setlocale(LC_TIME, 0));

    if(($pos = strpos("utf", $current_locale)) === false || strpos("8", $current_locale, $pos) === false){
        // UTF-8 locale is not used, the encodings are found out with the code shown below
        $locale_default_encodings = array(
            "german" => "ISO-8859-1",
            "french" => "ISO-8859-1",
            "polish" => "ISO-8859-2",
            "turkish" => "ISO-8859-9",
            // Testing hungarian results in "Windows-1252", but php.net recommends to 
            // use ISO-8859-2, in fact Windows-1252 is based on ISO-8859-2 so it should 
            // (hopefully) work with both (*)
            "hungarian" => "ISO-8859-2", 
            "chinese" => "CP936",
            "russian" => "KOI8-R"
        );
        $target_encoding = mb_internal_encoding(); // or "UTF-8" or whatever

        if(isset($locale_default_encodings[$language])){
            $datetime = mb_convert_encoding(
                $datetime, 
                $target_encoding, 
                $locale_default_encodings[$language]
            );
        }
        else{
            // try to avoid this case
            $datetime = mb_convert_encoding($datetime, $target_encoding);
        }
    }

    setlocale(LC_TIME, $locale);

    return $datetime;
}

(*):http://php.net/manual/de/function.strftime.php#94399

漫漫长路

我检查了特定语言的strftime("%B")结果。这是完整的月份名称。我检查了翻译的语言,然后查看UTF-8的十六进制值来查找翻译的不同字母。

现在,我正在遍历php支持的所有编码。我将strftime()给出的结果从当前的迭代编码转换为UTF-8。现在,我可以将转换为strftime()的{​​{1}}的结果与手动转换的十六进制值(也就是UTF-8的十六进制值)进行比较。如果它们匹配,则UTF-8的结果具有当前插入编码的编码。

我选择十六进制值是因为它们在定义上是相同的,并且不依赖于内部编码,因为它们是ASCII字符串(甚至是php中的数字)。

这给了我以下输出,代码发布在下面:

strftime()

请注意,此html以UTF-8编码。 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <h1>Detecting the font encoding of <code>strftime()</code> </h1> <h2>hungarian</h2> <p> <code>strftime()</code> for March for language hungarian. Expected hex: <code>6fc5be756a616b</code>, converted expected hex to string: <code>ožujak</code> </p> <table> <tr> <td>initial return value</td> <td>oߵjak</td> <td>6f9e756a616b</td> </tr> <tr> <td colspan='3'>Encodings that deliver the correct result:</td> </tr> <tr style='background: green;'> <td>Windows-1252</td> <td>ožujak</td> <td>6fc5be756a616b</td> </tr> </table> <h2>chinese</h2> <p> <code>strftime()</code> for December for language chinese. Expected hex: <code>e58d81e4ba8ce69c88</code>, converted expected hex to string: <code>十二月</code> </p> <table> <tr> <td>initial return value</td> <td>ʮ׾Ղ</td> <td>caaeb6fed4c2</td> </tr> <tr> <td colspan='3'>Encodings that deliver the correct result:</td> </tr> <tr style='background: green;'> <td>EUC-CN</td> <td>十二月</td> <td>e58d81e4ba8ce69c88</td> </tr> <tr style='background: green;'> <td>CP936</td> <td>十二月</td> <td>e58d81e4ba8ce69c88</td> </tr> <tr style='background: green;'> <td>GB18030</td> <td>十二月</td> <td>e58d81e4ba8ce69c88</td> </tr> </table> <h2>russian</h2> <p> <code>strftime()</code> for December for language russian. Expected hex: <code>d0b4d095d099d0aed090d09fd0ad</code>, converted expected hex to string: <code>дЕЙЮАПЭ</code> </p> <table> <tr> <td>initial return value</td> <td>ť롡td> <td>c4e5eae0e1f0fc</td> </tr> <tr> <td colspan='3'>Encodings that deliver the correct result:</td> </tr> <tr style='background: green;'> <td>KOI8-R</td> <td>дЕЙЮАПЭ</td> <td>d0b4d095d099d0aed090d09fd0ad</td> </tr> <tr style='background: green;'> <td>KOI8-U</td> <td>дЕЙЮАПЭ</td> <td>d0b4d095d099d0aed090d09fd0ad</td> </tr> </table> </body> </html>函数给出的结果仍然是错误的!注释中指出,这与浏览器或编辑器的编码无关。

strftime()