學習PHP中國際化地數字格式處理

不知道大家有沒有了解過,對於數字格式來說,西方國家會以三位爲一個進位,使用逗號來分隔。比如,12345678,用標準的格式來表示的話就是 12,345,678 。不過我們中文其實並不會有這樣的分隔符,另外像某些地區則是以空格爲分隔的,這個我們馬上通過代碼就可以看到。其實在之前的文章中我們就已經接觸過一點這方面的知識,學習PHP中的國際化功能來查看貨幣及日期信息,今天就來詳細的學習一遍。至於爲什麼要格式化數字、貨幣這些內容呢?我們將在文章講解中逐一說明。

數字標準格式

首先還是看我們開頭介紹的標準數字格式。

$localeArr = ['en_US', 'zh_CN', 'ja_JP', 'de_DE', 'fr_FR', 'ar-IQ', 'ru_RU'];

foreach ($localeArr as $locale) {
    $fmt = new NumberFormatter($locale, NumberFormatter::DECIMAL);
    echo $locale . ':', $fmt->format(1234567.891234567890000), PHP_EOL;
}
// en_US:1,234,567.891
// zh_CN:1,234,567.891
// ja_JP:1,234,567.891
// de_DE:1.234.567,891
// fr_FR:1 234 567,891
// ar-IQ:١٬٢٣٤٬٥٦٧٫٨٩١
// ru_RU:1 234 567,891

我們先指定了許多的國家地區編碼,然後循環它們,使用 NumberFormatter 對象來對他們進行實例化。第二個參數就是要實例化的格式類型,這裏我們指定的是數字類型。然後使用 format() 方法就可以對指定的數字進行格式化地輸出了。可以看到,德國是使用 . 來分隔進位,使用逗號來做爲小數點。而法國和俄羅斯則是使用空格來表示進位,逗號表示小數點。其它國家則是沿用標準的英式表示。

對於很多財務及銀行項目來說,標準數字格式非常有用。往往我們接觸到比較多的是在匯款時要填寫的普通數字、中文大寫,而一些面向企業和涉外的公司財務也需要這種標準格式的數字來進行存根的記錄。既然說到財務了,我們再看看貨幣格式的展示。

貨幣格式

foreach ($localeArr as $locale) {
    $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
    echo $locale . ':', $fmt->format(1234567.891234567890000), PHP_EOL;
    echo $locale . ':', $fmt->formatCurrency(1234567.891234567890000, 'RUR'), PHP_EOL;
}
// en_US:$1,234,567.89
// en_US:RUR 1,234,567.89
// zh_CN:¥1,234,567.89
// zh_CN:RUR 1,234,567.89
// ja_JP:¥1,234,568
// ja_JP:RUR 1,234,567.89
// de_DE:1.234.567,89 €
// de_DE:1.234.567,89 RUR
// fr_FR:1 234 567,89 €
// fr_FR:1 234 567,89 RUR
// ar-IQ:١٬٢٣٤٬٥٦٨ د.ع.
// ar-IQ:١٬٢٣٤٬٥٦٧٫٨٩ RUR
// ru_RU:1 234 567,89 ₽
// ru_RU:1 234 567,89 р.

在這段代碼中,我們使用了兩種模式的輸出。第一個是指定 NumberFormatter 的第二個參數爲 CURRENCY ,也就是指定格式化爲貨幣格式。其實就是爲標準格式的數字前後增加了對應地區的代幣符號。比如我們中國和日本通用的 ¥ ,一般是放在金額的前面,而歐洲的則使用 € 歐元標識放在金額的後面。

另一種形式就是 formatCurrency() 這個方法可以指定一個貨幣類型,如果不是這個類型的區域設置的話,就直接輸出這個貨幣字符。在測試代碼中,我們給定的是俄羅斯的老盧布,其它區域中會直接輸出 RUR ,而在區域設置爲俄羅斯時,輸出的就是標準的老盧布符號(現在使用的是新盧布,符號是 ₽ ,老盧布就是 р.)。

詳細的地區格式化樣式

是不是感覺已經很高大上了?不不不,上面兩種格式只是開胃菜,真正好玩的現在馬上端給你。

$fmt = new NumberFormatter('zh_CN', NumberFormatter::PERCENT);
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 123 456 789 %

$fmt = new NumberFormatter('zh_CN', NumberFormatter::SCIENTIFIC);
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1,2345678912345679E6

$fmt = new NumberFormatter('zh_CN', NumberFormatter::SPELLOUT);
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 一百二十三萬四千五百六十七點八九一二三四五六七九

$fmt = new NumberFormatter('zh_CN', NumberFormatter::SPELLOUT);
echo $fmt->format(1234502.891234567890000), PHP_EOL; // 一百二十三萬四千五百〇二點八九一二三四五六七九

$fmt = new NumberFormatter('zh_CN', NumberFormatter::ORDINAL);
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 第1,234,568

$fmt = new NumberFormatter('zh_CN', NumberFormatter::DURATION);
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1,234,568

PERCENT 不多說了,百分比,就是增加了一個百分號,而且不是以標準格式輸出的,會以空格進行進位分隔。SCIENTIFIC 就是我們常見的科學計數法,測試代碼中的結果就是 1.xx 的 10 的 6 次方的意思。

SPELLOUT 就比較厲害了,按當前區域語言的拼寫規則。沒錯,直接轉換成了我們的中文表示。如果需要再轉換成中文的大寫,直接字符替換就可以了,這個絕對是這次文章的重大發現。之前在一家公司面試的時候就有人問過如何將數字轉換成中文表示,因爲很多的財務系統都需要這樣的功能。不管是做帳還是處理髮票,中文大寫或小寫都是系統自動輸出的。當時還寫了半天算法,如果大家自己寫算法的時候除了需要注意單位外,零的表示也是非常重要的一點,有興趣的朋友可以自己嘗試一下。不過下回如果面試的時候有人問這個問題,那我直接就會甩出 NumberFormatter::SPELLOUT 這個神器了。

ORDINAL 是排序的表示,在中文中其實就是在前面增加了一個 第 字。DURATION 是基於持續時間規則的格式。這兩種都會拋棄掉小數點。

格式化規則設置

雖說已經有這麼多的規則格式供我們使用了,但大家的業務總是千奇百怪的,我們能不能定義自己的格式規則呢?既然這麼寫了,那當然是可以的啦。

var_dump($fmt->getPattern()); // string(8) "#,##0.##"
$fmt->setPattern("#0.# kg");
var_dump($fmt->getPattern()); // string(6) "0.# kg"
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1234567.9 kg

看出來了嗎?我們使用 setPattern() 方法來定義了一個帶 kg 的格式規則,很顯示,我們是需要一個表示重量的格式。然後僅保留一位小數點,不需要分隔符號。這樣再次使用 format() 方法的時候就會按照我們指定的格式來進行格式化了。

屬性操作

當然,除了直接設置規則格式外,我們還可以指定一些屬性值來改變當前的格式效果。

$fmt = new NumberFormatter( 'zh_CN', NumberFormatter::DECIMAL );
echo "Digits: ".$fmt->getAttribute(NumberFormatter::MAX_FRACTION_DIGITS), PHP_EOL; // Digits: 3
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1,234,567.891

$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);
echo "Digits: ".$fmt->getAttribute(NumberFormatter::MAX_FRACTION_DIGITS), PHP_EOL; // Digits: 2
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1,234,567.89

這段代碼中,我們通過 setAttribute() 來設置 MAX_FRACTION_DIGITS 的值,用於改變最大保留的小數點位數。當然,不僅限於這一個屬性,還有很多別的可以修改的屬性,大家可以自行查閱官方手冊。

分隔符號設置

同樣,我們可以直接修改格式化中的分隔符、小數點等使用的符號。直接使用 setSymbol() 方法就可以。

var_dump($fmt->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL)); // string(1) ","
$fmt->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, "*");
var_dump($fmt->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL)); // string(1) "*"
echo $fmt->format(1234567.891234567890000), PHP_EOL; // 1*234*567.891

與地區格式化關聯的文本屬性設置

我們還可以直接設置與地區格式化相關的一些文本信息,比如下面代碼中使用 setTextAttribute() 修改了負號的表示。我們還可以使用這個方法修改間隔字符,貨幣編碼等內容,大家可以自己對照官方文檔測試學習。

var_dump($fmt->getTextAttribute(NumberFormatter::NEGATIVE_PREFIX)); // string(1) "-"
echo $fmt->format(-1234567.891234567890000), PHP_EOL;
$fmt->setTextAttribute(NumberFormatter::NEGATIVE_PREFIX, "負號 ");
var_dump($fmt->getTextAttribute(NumberFormatter::NEGATIVE_PREFIX)); // string(7) "負號 "
echo $fmt->format(-1234567.891234567890000), PHP_EOL; // 負號 1,234,567.891

獲取地區信息

這兩個方法就是簡單地獲取當前的地區信息了,之前在其它的文章中我們也講過,VALID_LOCALE 是表示有效區域,ACTUAL_LOCALE 表示的是實際區域。

var_dump($fmt->getLocale(Locale::VALID_LOCALE)); // string(10) "zh_Hans_CN"
var_dump($fmt->getLocale(Locale::ACTUAL_LOCALE)); // string(10) "zh_Hans_CN"

字符轉換爲數字、貨幣格式

我們能夠將數字進行格式化地輸出,輸出之後的內容因爲增加了分隔符之類的內容,所以都會轉成字符串,那麼,我們能不能把已經格式化過的標準數字字符再轉回數字類型呢?

$fmt = new NumberFormatter( 'zh_CN', NumberFormatter::DECIMAL );
$num = "1,234,567.891";
echo $fmt->parse($num)."\n"; // 1234567.891
echo $fmt->parse($num, NumberFormatter::TYPE_INT32)."\n"; // 1234567


$fmt = new NumberFormatter( 'zh_CN', NumberFormatter::CURRENCY );
echo $fmt->parseCurrency('¥1,234,567.89', $currency), PHP_EOL; // 1234567.89
var_dump($currency); // string(3) "CNY"

兩個方法,第一個是 parse() 方法,將標準格式的數字字符串轉回指定類型的數字,可以指定爲 TYPE_INT32 、TYPE_INT64 、TYPE_DOUBLE 、TYPE_CURRENCY 等類型。另外一個方法是 parseCurrency() 方法,從名字就可以看出,它是將貨幣格式轉回數字,並且,很重要的一點是,它的第二個引用參數,可以將貨幣符號的通用編碼也返回回來,比如測試代碼中返回的 CNY 代表的就是我們使用的人民幣。

錯誤信息

最後我們來看看 NumberFormatter 中的錯誤信息如何獲取。

echo $fmt->parseCurrency('1,234,567.89', $currency), PHP_EOL;
var_dump($fmt->getErrorCode()); // int(9)
var_dump(intl_is_failure($fmt->getErrorCode())); // bool(true)
var_dump($fmt->getErrorMessage()); // string(36) "Number parsing failed: U_PARSE_ERROR"

在這裏我們使用非標準的貨幣字符串來使用 parseCurrency() 進行轉換,parseCurrency() 必須接收的是帶貨幣符號的內容,所以這裏就產生了錯誤。我們使用 getErrorCode() 可以獲取到錯誤碼,使用 getErrorMessage() 可以獲取到錯誤信息。另外是一個 intl_is_failure() 函數,用於根據錯誤碼判斷是否產生了區域語言問題的錯誤。

總結

又是大開眼界的一次學習旅程,中文小寫格式的轉換真的是之前完全不知道的,而貨幣的互相轉換我覺得也完全可以應用到一些採集程序中,比如電商頁面價格的採集分析。總之,還是感覺到收穫滿滿的。另外,這一套 NumberFormatter 對象也是提供了面向過程的函數式使用方法的,比如 numfmt_create() ,記住是 numfmt_ 開頭的函數哦,不要和 number_format() 相關的函數搞混了。

測試代碼:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202011/source/4.學習PHP中國際化地數字格式處理.php

參考文檔:

https://www.php.net/manual/zh/class.numberformatter.php

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章