學習PHP中的國際化日期格式化操作

對於國際化功能來說,日期相關的格式化操作也是一塊重頭戲,畢竟不同的時區,不同的國家對於日期的表示方式都會有些不同。今天我們主要來學習的就是國際化地表示日期相關的信息內容。

日期格式化

首先就是最直接的格式化能力。

$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL,
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN  );
echo "en_US 格式化結果爲: ".$fmt->format(time()), PHP_EOL;
// en_US 格式化結果爲: Friday, November 20, 2020 at 4:45:06 PM Pacific Standard Time

$fmt = new IntlDateFormatter( "de-DE" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN  );
echo "de_DE 格式化結果爲: ".$fmt->format(IntlCalendar::createInstance()), PHP_EOL;
// de_DE 格式化結果爲: Freitag, 20. November 2020 um 16:45:06 Nordamerikanische Westküsten-Normalzeit

$fmt = new IntlDateFormatter( "zh-CN" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'Asia/Shanghai',IntlDateFormatter::GREGORIAN  );
echo "zh-CN 格式化結果爲: ".$fmt->format(time()), PHP_EOL;
// zh-CN 格式化結果爲: 2020年11月21日星期六 中國標準時間 上午8:45:06

IntlDateFormatter 對象就是國際化組件中對於日期格式化的操作類。它的構造參數很多,不過其實非常簡單,第一個參數是國家區域設置,第二和第三個參數分別是日期和日間的顯示格式,這個我們下段代碼將演示。第四個參數是時區設置,第五個參數是時間規範,這裏指定的是格里高利時間。

使用 format() 方法就可以對時間戳或者日曆對象進行日期時間的格式化。它只能接收這兩種類型的參數並進行格式化。它會根據 IntlDateFormatter 對象所設置的各種參數進行輸出,比如輸出的語言是英語、德語、中文等,輸出的時間是按時區(中國8點,美國下午4點)。

對於日期和時間的顯示格式,我們可以使用幾個 IntlDateFormatter 類的常量來表示,主要有 FULL 、 SHORT 、MEDIUM、 LONG 這些類型。

$fmt = new IntlDateFormatter( "zh-CN" ,IntlDateFormatter::SHORT, IntlDateFormatter::LONG, 
    'Asia/Shanghai',IntlDateFormatter::GREGORIAN  );
echo "zh-CN 格式化結果爲: ".$fmt->format(time()), PHP_EOL;
// zh-CN 格式化結果爲: 2020/11/21 GMT+8 上午8:45:06

另外,構造函數的第六個參數是可以指定格式化的格式規則的。

$fmt = new IntlDateFormatter( "zh-CN" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'Asia/Shanghai',IntlDateFormatter::GREGORIAN, 'yyyy/MM/dd' );
echo "zh-CN 格式化結果爲: ".$fmt->format(time()), PHP_EOL;
// zh-CN 格式化結果爲: 2020/11/21

根據指定對象格式化日期

上文中的 format() 方法我們看到只能使用時間戳和日曆對象類型。其實還有另一種更強大的格式化方法,它就是 formatObject() 方法。從名字可以推斷出,它是根據指定的對象來格式化日期數據。

$cal = IntlCalendar::createInstance(new DateTimeZone('Asia/Shanghai'));
echo IntlDateFormatter::formatObject($cal),PHP_EOL;
// Nov 21, 2020, 8:45:06 AM
echo IntlDateFormatter::formatObject($cal, IntlDateFormatter::FULL),PHP_EOL;
// Saturday, November 21, 2020 at 8:45:06 AM China Standard Time
echo IntlDateFormatter::formatObject($cal, IntlDateFormatter::NONE, IntlDateFormatter::FULL),PHP_EOL;
// 20201121 08:45 AM
echo IntlDateFormatter::formatObject($cal, IntlDateFormatter::FULL, 'zh-CN'),PHP_EOL;
// 2020年11月21日星期六 中國標準時間 上午8:45:06
echo IntlDateFormatter::formatObject($cal, "d 'of' MMMM y", 'zh-CN'), PHP_EOL;
// 21 of 十一月 2020

最常用的依然是對日曆對象的格式化,可以看到 formatObject() 方法的參數更多一些,它也可以直接指定日期和時間的格式形式以及相關的語言設置。另外,它還可以指定豐富的輸出規則,比如我們最後一段代碼輸出的是當天在這個月中是第幾天。在 PHP中的國際化日曆類 這篇文章中,我們也使用過這個方法來進行測試,自定義的語法規則非常多,大家可以自己查閱 ICU 相關的文檔。

除了對於日曆類的格式化之外,formatObject() 方法還可以對 DateTime 對象進行日期格式化地輸出。

$dt = new DateTime();
echo IntlDateFormatter::formatObject($dt),PHP_EOL;
// Nov 21, 2020, 8:45:06 AM

不過需要注意的是,從官方文檔的 Note 來看,formatObject() 的速度非常慢,在 PHP5 下面與 format() 方法有 10 倍左右的差距,在 PHP7 下也有 3 倍左右的差距。所以說,如果不是有特別的需求的話,儘量還是不要使用 formatObject() 這個方法來格式化日期時間。

反解析日期字符串

和之前我們在 學習PHP中國際化地數字格式處理 中講過的一樣,我們可以將對象或者時間戳格式化爲標準的字符串格式顯示,那麼能不能將這種標準的字符串格式數據再反轉回來呢?

$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL,
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN  );
$arr = $fmt->localtime($fmt->format(time()));
print_r($arr);
// Array
// (
//     [tm_sec] => 1
//     [tm_min] => 59
//     [tm_hour] => 16
//     [tm_year] => 120
//     [tm_mday] => 20
//     [tm_wday] => 5
//     [tm_yday] => 325
//     [tm_mon] => 10
//     [tm_isdst] => 0
// )

echo $fmt->parse("Thursday, November 19, 2020 at 5:05:41 PM Pacific Standard Time"), PHP_EOL;
// 1605834341

localtime() 方法就是用於解析給定的標準日期內容的,根據 IntlDateFormatter 初始化時的規則,將字符串的內容反向輸出爲一個數組,其中包含了年、月、日、時、分、秒等信息。而 parse() 方法則是直接將給定的內容轉換爲對應的時間戳。

$fmt = new IntlDateFormatter( "zh-CN" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'Asia/Shanghai',IntlDateFormatter::GREGORIAN );
$arr = $fmt->localtime("2020年11月20日星期五 中國標準時間 上午8:54:08");
print_r($arr);
// Array
// (
//     [tm_sec] => 8
//     [tm_min] => 54
//     [tm_hour] => 8
//     [tm_year] => 120
//     [tm_mday] => 20
//     [tm_wday] => 5
//     [tm_yday] => 325
//     [tm_mon] => 10
//     [tm_isdst] => 0
// )

echo $fmt->parse("2020年11月20日星期五 中國標準時間 上午8:54:08"), PHP_EOL;
// 1605833648

不管是中英文都是良好支持的。

相關屬性獲取及設置

日曆類型信息

對於日曆類型來說,只有兩種類型的日曆,GREGORIAN 和 TRADITIONAL,分別對應的是格里高利和傳統日曆。在構造參數中我們可以通過第五個參數指定,也可以在對象使用的過程中使用 setCalendar() 方法來設置。getCalendar() 方法用於獲取當前設置的日期類型信息。

$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN );
echo $fmt->getCalendar(), PHP_EOL; // 1
$fmt->setCalendar(IntlDateFormatter::TRADITIONAL);
echo $fmt->getCalendar(), PHP_EOL; // 0

日期和時間類型

// 日期類型獲取及設置
$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::FULL, IntlDateFormatter::FULL, 
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN );
echo $fmt->getDateType(), PHP_EOL; // 0
$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::SHORT, IntlDateFormatter::FULL, 
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN );
echo $fmt->getDateType(), PHP_EOL; // 3

// 時間類型獲取及設置
echo $fmt->getTimeType(), PHP_EOL; // 0
$fmt = new IntlDateFormatter( "en_US" ,IntlDateFormatter::SHORT, IntlDateFormatter::MEDIUM, 
    'America/Los_Angeles',IntlDateFormatter::GREGORIAN );
echo $fmt->getTimeType(), PHP_EOL; // 2

對於日期和時間類型來說,我們只能通過構造函數的參數進行指定,獲取到的也是對應常量的值。

區域語言信息

echo $fmt->getLocale(), PHP_EOL; // en
echo $fmt->getLocale(Locale::VALID_LOCALE), PHP_EOL; // en_US

這個就不多做解釋了,之前的文章中都有,似乎國際化相關組件的類中都會包含這兩個方法。

格式規則獲取及設置

我們可以在構造函數的第六個參數中指定格式化的規則,同時也可以對對象進行動態的設置。

echo $fmt->getPattern(), PHP_EOL; // M/d/yy, h:mm:ss a
$fmt->setPattern('yyyyMMdd hh:mm:ss z');
echo $fmt->getPattern(), PHP_EOL; // yyyyMMdd hh:mm:ss z
echo $fmt->format(time()), PHP_EOL; // 20201120 04:59:01 PST

使用 setPattern() 設置格式規則之後,再次進行 formar() 就是以新的格式規則進行格式化了。

時區設置

首先我們來看一個 getTimezoneId() 方法。它是直接獲取時區內容的,也就是一個字符串。

echo $fmt->getTimezoneId(), PHP_EOL; // America/Los_Angeles
// $fmt->setTimeZoneId('CN'); // PHP7 已刪除
// echo $fmt->getTimezoneId(), PHP_EOL;

不過在 PHP7 中已經刪除了 setTimezoneId() 方法,現在推薦是使用 setTimezone() 方法來設置時區信息,我們馬上來看看。

var_dump($fmt->getTimezone());
// object(IntlTimeZone)#4 (4) {
//     ["valid"]=>
//     bool(true)
//     ["id"]=>
//     string(19) "America/Los_Angeles"
//     ["rawOffset"]=>
//     int(-28800000)
//     ["currentOffset"]=>
//     int(-28800000)
//   }

$fmt->setTimeZone('Asia/Shanghai');
var_dump($fmt->getTimezone());
// object(IntlTimeZone)#4 (4) {
//     ["valid"]=>
//     bool(true)
//     ["id"]=>
//     string(13) "Asia/Shanghai"
//     ["rawOffset"]=>
//     int(28800000)
//     ["currentOffset"]=>
//     int(28800000)
//   }

$fmt->setTimeZone('GMT+00:30');
var_dump($fmt->getTimezone());
// object(IntlTimeZone)#4 (4) {
//     ["valid"]=>
//     bool(true)
//     ["id"]=>
//     string(9) "GMT+00:30"
//     ["rawOffset"]=>
//     int(1800000)
//     ["currentOffset"]=>
//     int(1800000)
//   }

與 getTimezoneId() 方法不同的是,getTimezone() 方法返回的是一個 IntlTimeZone 對象,關於這個對象的內容官方文檔不全,很多方法參數都沒有寫,我也不好猜測,所以不會寫這個對象的文章,大家可以自己查閱相關的資料。不過對於簡單的設置時區來說,setTimezone() 方法可以直接使用字符串做爲參數。比如我們在上面的代碼分別將美國洛杉磯的時區修改爲中國上海以及GMT+00:30這兩種時區。對應地,如果我們再 format() 輸出時間的話,就是以當前時區的標準時間爲準進行輸出了。

獲取日曆對象

本身在格式化數據的時候,我們就與日曆對象打了很多交道,當然通過 IntlDateFormatter 對象我們也是可以獲得日曆信息的。

$cal = $fmt->getCalendarObject();
var_dump(
    $cal->getType(),
    $cal->getTimeZone(),
    $cal->getLocale(Locale::VALID_LOCALE)
);
// string(9) "gregorian"
// object(IntlTimeZone)#3 (4) {
//   ["valid"]=>
//   bool(true)
//   ["id"]=>
//   string(9) "GMT+00:30"
//   ["rawOffset"]=>
//   int(1800000)
//   ["currentOffset"]=>
//   int(1800000)
// }
// string(5) "en_US"

寬容能力

最後我們再看一下寬容能力,其實也就是一種嚴格模式的操作。比如我們如果定義一個錯誤的時間,IntlDateFormatter 中的操作並不會報錯,因爲它默認是寬容處理的。

$fmt->setPattern('dd/mm/yyyy');
var_dump($fmt->isLenient()); // bool(true)
echo $fmt->parse('35/13/1955'), PHP_EOL;
// -470449020

很明顯,這個日期是一個錯誤的日期。通過 isLenient() 方法我們可以獲取當前是否是寬容處理的狀態。我們現在將寬容處理的能力取消掉,再看看會是什麼結果。

$fmt->setLenient(FALSE);
echo $fmt->parse('35/13/1955'), PHP_EOL;
// 

echo $fmt->getErrorCode(), PHP_EOL; // 9
echo $fmt->getErrorMessage(), PHP_EOL; // Date parsing failed: U_PARSE_ERROR

parse() 方法沒有任何輸出了。同時通過 getErrorCode() 和 getErrorMessage() 也看到了錯誤信息。這就是 IntlDateFormatter 對象中寬容處理的主要能力。

總結

今天學習的內容比較多和零散,不過主要都是 IntlDateFormatter 這個對象的內容。數字和日期格式是國際化相關功能中最主要的功能,也能夠隨時應用到我們的日常業務開發中,大家可以多多地學習瞭解相關的知識。

測試代碼:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202011/source/8.學習PHP中的國際化日期格式化操作.php

參考文檔:

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

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