阅读 118

学习PHP中的国际化日期格式化操作

学习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 这个对象的内容。数字和日期格式是国际化相关功能中最主要的功能,也能够随时应用到我们的日常业务开发中,大家可以多多地学习了解相关的知识。

文章分类
后端
文章标签