码农老张Zy 发表于 2021-8-11 08:42:42

学习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:1234567,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:1234567,89 // fr_FR:1234567,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,568PERCENT 不多说了,百分比,就是增长了一个百分号,而且不是以标准格式输出的,会以空格进行进位分隔。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: 3echo $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: 2echo $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.891echo $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.89var_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
页: [1]
查看完整版本: 学习PHP中国际化地数字格式处理惩罚