缅甸和斯里兰卡天天在用佛历,这两门语言的日历数据里一条都没有
本文目录
- 用另一套历法的地方,到底有多少?
- 制度上默认不用公历的地区只有三组
- 第二顺位才是真正会出事的地方
- 还有一批用法根本不在那张表里
- 2019年的几个对照数字
- 一门语言有几套历法,这个数字为什么不能信?
- 第一版数出来的结果全是错的
- 怎么一眼看出那一段是假的
- 重新数,把三种假数据剔掉
- 佛历圈这五个地方,数据差成什么样?
- 五门语言,一个满格四个空
- 泰语那8199字节里写了什么
- 缅甸和斯里兰卡是彻底的空白
- 那一格空了之后,页面上写的是什么
- 为什么老挝语的日本年号比它自己的佛历厚两百多倍?
- 236条对1条
- 哪些内容能批量灌,哪些不能
- 这不是有人乱填,是批量导入的结果
- 同样的形状在别的语言上也成立
- 回历那一格,谁填了谁没填?
- 填了的那一批
- 没填的那一批更值得注意
- 印尼语和马来语差了将近一倍
- 回历缺数据的后果比佛历严重得多
- 日本年号那一格为什么是全表最厚的?
- 474条纪年名意味着什么
- 今年春天那次改元逼出了一个额外版本
- 其他语言里的日本年号也不算薄
- 这件事对你的启发不在日本
- 两类非公历必须分开对待
- 只改年份的那一类
- 连月份都不一样的那一类
- 希伯来历算第三种情况
- 埃塞历那一格有个特别难看的细节
- 页面上哪几个地方会先出事?
- 第一个出事的是表单校验
- 第二个是结构化数据
- 第四个是导出文件和对账
- 第三个是排序和筛选
- 上线前这一格怎么查?
- 第一步:确认这个市场用不用非公历
- 第二步:查这门语言那一套历法填了没有
- 第三步:按两类分开定处理方式
- 查完写成一句话
- 第四步:把日期渲染和数据输出分开
- 哪些相邻的问题不归这一层管?
- 本地数字系统是另一件事
- 字符渲染是再往下一层
- 时区和夏令时不在这里
- 节庆日期不归这一层,也不归任何一层
- 翻译和审校碰不到这一格
- 常见问题解答
- 我的市场清单里要不要处理非公历?
- 怎么判断一门语言的历法数据到底填没填?
- 缺数据的时候页面上会显示什么?
- 佛历和回历缺数据的严重程度一样吗?
- 年份换算要不要用现成的库?
- 为什么这类问题上线前很少被发现?
- 结构化数据里的日期该用哪套历法?
- 权威参考资料
摘要:把公共区域数据里十六套非公历历法在五十门语言上的填充情况逐格数了一遍。第一,泰国官网写的年份比今年大543,而缅甸语、僧伽罗语、宗卡语的文件里佛历整段不存在,高棉语和老挝语各只有一条还标着未确认。第二,老挝语给日本年号填了236条,给自己的佛历填了1条。第三,缺了这一格不会报错,页面上出现的是BE、AH、ERA1这类英文缩写。
泰国的政府网站、发票、身份证上写的年份是四位数,比公历大543。今年公历2019年,那上面写的是2562。
这件事在做泰国市场的人里不算冷知识。真正的问题是它不止泰国一家,而绝大多数团队的处理方式是等出事再说。
保哥前阵子把公共区域数据里所有跟历法有关的字段翻了一遍,本来只想确认一下各门语言的历法支持情况,结果发现这一格的分布毫无道理可言:天天在用某套历法的国家,它的语言里那套历法可能一条数据都没有;而一辈子用不上某套历法的国家,它的语言里那套历法填得整整齐齐两百多条。
更麻烦的是,缺了这一格系统不会报错。它会安静地回落到最上层的通用值,而最上层的通用值是几个英文缩写。用户看到的年份前面会出现两三个拉丁字母。
下面这些数字是为了让这件事在上线前而不是上线后被发现。
用另一套历法的地方,到底有多少?
先把范围划清楚,不然容易把这件事想得太大或者太小。
制度上默认不用公历的地区只有三组
公共区域数据里有一张历法偏好表,逐个地区列出该地区按什么顺序优先使用哪几套历法。全表只有14条记录,其中第一顺位不是公历的只有三组。
泰国排第一的是佛历,伊朗和阿富汗排第一的是波斯历,沙特排第一的是回历中的一个特定算法版本。其余11组的第一顺位都是公历。
所以从制度上说,需要把非公历当默认的市场并不多。如果你的市场清单里没有这三组,这一格的优先级可以往后放。
但第一顺位不是全部。表里还列出了第二、第三顺位——以色列的第二顺位是希伯来历,埃及是科普特历,埃塞俄比亚是埃塞历,印度是印度国历,日本是年号纪年,韩国是檀纪,台湾地区是民国纪年,中国大陆和新加坡是农历。
沙特那一条还有个细节:它的第一顺位是回历里一个叫乌姆库拉的算法版本,而不是通用的回历。回历有好几种推算方式,彼此日期能差一天,选错版本在宗教场合是要出事的。
第二顺位才是真正会出事的地方
第一顺位是公历意味着系统默认渲染不会出错,但它不意味着那套历法在当地不重要。以色列的日历应用、节庆安排、政府文书里希伯来历随处可见。
做希伯来语市场的人对这一条会有实感:一周从周日开始这件事已经够折腾了,节庆日期还得按另一套历法算。
第二顺位的典型场景是促销和节庆。斋月、光明节、诺鲁孜节这些日子在公历上每年都在动,而当地用户是按另一套历法记住它们的。
你的活动排期表如果只有公历,那就意味着每年都要有人手工去查一次日期,而这个人多半在总部。
判断一个市场要不要重视第二顺位,有个简单办法:看当地的银行、政府和大型零售商的官网上有没有并列写两套年份。有,说明这件事在当地是日常。
还有一批用法根本不在那张表里
那张偏好表管的是操作系统和浏览器该默认显示哪套历法,它不覆盖所有实际用法。财年、宗教节庆、农事节气这些都不在里面。
比较典型的是农历。表里只给中国大陆、香港、澳门、新加坡和圣诞岛列了农历作为第二顺位,但农历在越南、韩国、马来西亚华人社区同样是活的。
另一个是印度国历。表里给印度列了它,可印度实际在用的地方历法有十几种,国历只是官方那一套。
所以这张表的正确用法是当下限:表里有的一定要处理,表里没有的还得问一句当地同事。
财年是另一个容易漏的。多个中东国家的财政年度不按公历1月起算,报表按月汇总的时候如果直接用公历月,跟当地会计口径对不上,这件事到年底才会暴露。
2019年的几个对照数字
| 历法 | 2019年对应的年份 | 主要使用地区 | 跟公历的关系 |
|---|---|---|---|
| 佛历 | 2562 | 泰国、缅甸、柬埔寨、老挝、斯里兰卡 | 年份加543,月日相同 |
| 民国纪年 | 108 | 台湾地区 | 年份减1911,月日相同 |
| 日本年号 | 令和元年 | 日本 | 年份按在位重置,月日相同 |
| 波斯历 | 1398 | 伊朗、阿富汗 | 月份与月长完全不同 |
| 回历 | 1441 | 中东、部分东南亚与非洲 | 纯阴历,一年少11天 |
| 埃塞历 | 2012 | 埃塞俄比亚、厄立特里亚 | 13个月,年份少7到8年 |
这张表里最容易被忽略的是最后一列。前三行的月和日跟公历完全一样,只有年份数字不同;后三行连月份都是另一套。
这个区别后面会反复用到,因为它决定了缺数据的后果差多远。
还有一个直觉陷阱:回历的年份1441看起来比佛历的2562小很多,容易让人以为它更接近公历。实际正相反,回历是这六套里跟公历差得最远的。
另外要注意年份边界。回历一年354天,它的新年在公历上每年往前挪11天;埃塞历新年固定在公历9月11日前后;波斯历新年是春分。跨年统计的时候这几条各自会造出不同的错位。
一门语言有几套历法,这个数字为什么不能信?
这是本篇最需要先说清楚的一件事,因为它决定了后面所有数字怎么读。
第一版数出来的结果全是错的
保哥第一版的做法很直接:打开每门语言的文件,数里面出现了几个历法定义段,非公历的算一套。数出来的结果是老挝语10套、马来语11套、越南语12套。
然后打开祖鲁语看了一眼。祖鲁语声称有两套非公历,佛历和农历。南非跟这两套都没关系,但数据在那儿。
把那一段内容打开一看,里面所有的值都是三个向上的箭头——那是继承标记,意思是这一格跟上级一样。祖鲁语的这两套历法数据,一个字都没有。
同样的问题还有哈萨克语。它唯一的一套非公历是科普特历,而科普特历是埃及的。打开一看,月份名写的是1到13的阿拉伯数字,而且每一条都标着未确认。
这已经是这类实测里第九次被同一种事情绊住了:某个指标看着能解释一切,打开原始记录一看是另一回事。能一次性解释所有小语种的漂亮结论,先拿一个大语言或者一个不相干的地区去证伪它。
怎么一眼看出那一段是假的
不用逐格数也能快速判断。第一个信号是段落的字节数:真正填过的历法段通常在几千字节以上,泰语的佛历段有8199字节;只有一两百字节的,里面装不下12个月份名。
第二个信号是月份名的内容。打开看,如果月份名写的是1到12的阿拉伯数字,那就是占位不是翻译。哈萨克语的科普特历就是这个样子。
第三个信号是那些向上的箭头。整段全是箭头的,等于零,祖鲁语的两套历法都属于这种。
三个信号任意命中一个就可以判定这一格不可用,不需要再往下细看。这套判断三十秒能做完。
这三个信号里最可靠的是第二个。数字占位这种写法只可能来自批量导入,没有任何一个真人会把月份名填成1到13。看到它基本可以判定这门语言的这一格从来没有本地人经手过。
重新数,把三种假数据剔掉
正确的数法要剔掉三类:值等于继承标记的、标着未确认的、标着暂定的。这两个概念的正式定义都写在区域数据规范总则里:后两类在生成发布数据时会被丢弃,第一类本来就是借的。
剔完之后的结果跟第一版差得很远:
| 语言 | 声称有几套非公历 | 真正有内容的 | 差多少 |
|---|---|---|---|
| 高棉语 | 1 | 0 | 全没了 |
| 祖鲁语 | 2 | 0 | 全没了 |
| 哈萨克语 | 1 | 0 | 全没了 |
| 格鲁吉亚语 | 3 | 0 | 全没了 |
| 库尔德语 | 1 | 0 | 全没了 |
| 老挝语 | 10 | 9 | 少1套 |
| 印尼语 | 8 | 7 | 少1套 |
| 阿姆哈拉语 | 5 | 3 | 少2套 |
| 越南语 | 12 | 11 | 少1套 |
五门语言从有变成了零。这五门里,高棉语丢掉的那一套正是柬埔寨在用的佛历。
这件事本身值得记一笔:数一门语言有没有某套历法,不能数段落有没有出现,要数段落里有几个格子真的填了东西。
关于继承标记这个符号本身的来历,以及它为什么2019年才第一次能被数出来,写在上一篇讲区域数据自有率的文章里,这里不重复。
还有一个副产品:剔完之后再看那些数字大的语言,可信度反而提高了。繁体中文的农历861格、日语的年号521格,这些都是真填的,一格箭头都没有。
佛历圈这五个地方,数据差成什么样?
佛历是这几套里对照最干净的,因为使用它的五个国家都在东南亚和南亚,宗教背景一致,用法也一致。
五门语言,一个满格四个空
| 语言 | 国家 | 佛历段字节数 | 真正填了几格 | 纪年名写法 |
|---|---|---|---|---|
| 泰语 | 泰国 | 8199 | 95 | 全称与缩写都是泰文 |
| 高棉语 | 柬埔寨 | 109 | 0 | 有一条高棉文缩写,但标着未确认 |
| 老挝语 | 老挝 | 109 | 0 | 有一条老挝文缩写,同样未确认 |
| 缅甸语 | 缅甸 | 0 | 0 | 整段不存在 |
| 僧伽罗语 | 斯里兰卡 | 0 | 0 | 整段不存在 |
| 宗卡语 | 不丹 | 0 | 0 | 整段不存在 |
泰语那一段有8199字节,里面除了纪年名,还单独定义了佛历的日期格式模板——因为泰语写年份的时候要在数字前面加纪年缩写,语序跟公历不一样。
高棉语和老挝语那两段各只有109字节,里面就一条纪年缩写,而且都带着未确认的标记。前面说过,这个标记意味着它到不了发布数据里。
换句话说,柬埔寨语和老挝语的佛历,有人填过,但那一条至今没有走完确认流程。填的那个人可能已经等了好几年。
要说明的是,这五门语言在整体数据量上并不悬殊:泰语6039格、老挝语5345格、僧伽罗语4653格、缅甸语4054格、高棉语3942格,同一个量级。差距集中在历法这一格上。
泰语那8199字节里写了什么
泰语的佛历段是这份数据里佛历填得最完整的一份,值得拆开看看一份填好的历法数据长什么样。
首先是纪年名,全称和缩写各一条,都是泰文。然后是完整的日期格式模板,从最长的那种带星期几的写法,到最短的纯数字写法,一共几档。
关键在模板这一部分。泰语写日期的时候纪年缩写要放在年份数字前面,而公历的模板里没有这个位置,所以必须单独定义。
这解释了为什么95格就够用:佛历的月和日跟公历一样,可以直接借用公历那一套,真正需要单独写的只有纪年名和几个模板。泰语在别的技术环节上的坑不少,历法这一格倒是这几门语言里做得最好的。
各套历法的字段结构在区域数据规范的日期部分里有完整说明,包括纪年、月份、格式模板各自该怎么写。要自己补数据的话,那份文档是唯一的依据。
缅甸和斯里兰卡是彻底的空白
缅甸语、僧伽罗语、宗卡语的文件里,佛历这个段落根本不存在。不是空的,是不存在。
这三个地方对佛历的使用强度不比泰国低。缅甸的传统节庆、斯里兰卡的卫塞节、不丹的宗教历法,都是日常。
做东南亚几个市场的语言层规划时,这一条可以直接写进风险清单:这三门语言在历法这一格上,你拿不到任何本地数据。
顺带说一句,缅甸语在别的格上并不弱——它的数字格式679格填了677格,几乎满格。所以这不是这门语言没人维护,是历法这一格没人碰。
不丹的宗卡语情况更特殊一点,它整个文件只有1446格,是这批语言里最薄的,只有英语的四分之一。这门语言在数据层面上基本处于起步阶段。
那一格空了之后,页面上写的是什么
最上层的通用数据里,佛历的纪年缩写是两个拉丁字母:BE。月份则直接借用公历的月份名。
所以缅甸语页面如果启用佛历渲染,年份会写成BE 2562这种形式,月份是缅甸文的,纪年标识是英文的。中间夹一个空格。
用户看到的是一句缅甸文里插了两个拉丁字母。它不影响理解,但它明确地告诉本地用户:这个站不是本地做的。
这类细节对转化的影响没法量化,但它跟地址栏里用本地字母还是拉丁转写是同一个性质的东西,属于一眼能看出来但没人会专门反馈的那一类。
补救成本其实很低。佛历的年份换算是加543这么一个固定数,纪年名替换成本地写法就是改一个字符串。真正的成本在没人知道要改。
为什么老挝语的日本年号比它自己的佛历厚两百多倍?
这是本批实测里最让人意外的一条,也是最能说明这一格是怎么长成现在这样的一条。
236条对1条
老挝语文件里的日本年号那一段,填了236格,全部是真数据。里面是从公元645年的大化开始,历朝历代所有年号的老挝文音译。
同一个文件里,老挝语自己的佛历那一段,只有1条,而且标着未确认。
236比1。老挝人不用日本年号,老挝人天天用佛历。
类似的还有中国农历那一段,老挝语填了160格。老挝人也不用农历。
把这两个数摆在一起的时候保哥愣了一下,先怀疑是自己的脚本数错了段。打开原文一看,那236条确实是老挝文写的日本年号,从大化、白雉一路排下来。
哪些内容能批量灌,哪些不能
把填得满的那几类内容列出来,共同点很清楚:日本年号是一份固定的历史清单,农历干支是六十个循环名,科普特历和埃塞历的月份名是固定的十三个词。
这些都是专有名词,数量有限,音译规则一旦定下来就可以一次生成。一个懂音译规则的人加一个脚本,一晚上能出几百条。
填不满的那几类则相反:本地历法的纪年名要看这门语言的实际书面习惯,日期模板要看这门语言的语序,复数形式要看这门语言的语法。
这些都得问人,而且得问对人。需要一次询问的字段填得满,需要一次判断的字段空着,这条规律在整份数据里到处成立。
这条规律反过来也能用:拿到一份陌生语言的本地化数据,先看哪几块填得满。如果满的全是清单型内容,那这份数据大概率没经过本地人审阅,只是跑过脚本。
这不是有人乱填,是批量导入的结果
把历史版本并排看就明白了。2012年那一版,老挝语总共只有413个字段,非公历历法只有1套。2013年那一版突然变成3734个字段、10套历法。
一年之间涨了九倍,这不是人一格一格填出来的。合理的解释是某次批量导入,把一批可以机械音译的内容一次性灌了进去。
年号名、农历干支名、科普特历月份名这类东西的共同点是:它们是专有名词,音译规则确定,可以批处理。而佛历的纪年名要看这门语言的实际习惯,批不了。
能批量处理的那部分被填满了,需要问一句本地人的那部分空着。这条规律在这一格上表现得比任何地方都极端。
同样的形状在别的语言上也成立
泰语的日本年号274格,比它自己的佛历95格还多。孟加拉语的希伯来历170格,而孟加拉跟希伯来历毫无关系。
最典型的是马来语:希伯来历167格,全部是真数据;而马来西亚在用的回历只有63格,另外还有23格明确写着跟上级一样。
马来西亚是穆斯林占多数的国家,回历是国家历法之一。做马来语和印尼语两个市场的人可以拿这一条去说服排期:这一格是缺的,而且缺得没道理。
顺带一提,印尼语的回历有110格,比马来语多。同一套历法、同一片区域、两门高度近似的语言,差了将近一倍。
这个现象还有一层含义:某门语言的某套历法数据厚,不能推断这门语言的本地化做得好,只能推断有人跑过一次批量任务。两者的相关性比直觉低得多。
回历那一格,谁填了谁没填?
回历是使用范围最广的一套非公历历法,横跨中东、南亚、东南亚和非洲,值得单独看。
填了的那一批
阿拉伯语117格、印尼语110格、土耳其语105格、乌尔都语104格、索马里语110格、阿塞拜疆语102格、乌兹别克语102格、马来语86格。
这一批的共同点是它们都定义了完整的12个月份名。回历的月份跟公历完全不同,所以这12个名字是必须的,不填就只能用英文转写。
阿拉伯语那一份还额外定义了完整的日期格式模板,跟泰语在佛历上的做法一样。阿拉伯语页面的方向问题本来就多,日期这一格倒是齐的。
要注意几门语言里带继承标记的比例:乌尔都语104格里有53格是借的,阿塞拜疆语102格里54格,乌兹别克语102格里49格。也就是说这几门语言真正自己填的只有一半。
另外值得一提的是索马里语。它的回历有110格,但其中89格是借的,真自有只有21格。光看总数会以为它填得跟阿拉伯语差不多,实际差了五倍。
土耳其语这一行有个值得一提的地方:它的科普特历84格,比自己的回历73格还多。土耳其跟科普特历没有任何关系,这又是一次批量导入留下的痕迹。
没填的那一批更值得注意
哈萨克语、吉尔吉斯语、豪萨语、斯瓦希里语,这四门语言的回历段整段不存在。
哈萨克斯坦和吉尔吉斯斯坦是穆斯林占多数的国家;豪萨语的主要使用区域是尼日利亚北部,那里同样如此;斯瓦希里语的沿海使用区历史上就是伊斯兰贸易圈。
四门语言都跟回历有实际关系,四门语言的数据都是零。
而前面提到过,这四门里哈萨克语唯一那套非公历是科普特历,内容是1到13的数字,全部未确认。这个组合已经不像是有人在维护,更像是某次导入留下的残余。
豪萨语这一行还有个背景:它整份文件里三分之二的格子都是借的,历法只是其中一块。这门语言的格式层整体成色都薄,不只是历法这一格。
印尼语和马来语差了将近一倍
印尼语的回历110格,马来语63格。这两门语言在语言学上高度近似,两国的穆斯林比例都很高,回历的用法也基本一致。
差异不来自语言本身,来自维护这两门语言数据的是两批不同的人,投入不同。
这一条对做这两个市场的人有直接影响:如果你打算用一套内容覆盖两边,格式层这一格是不能共用的,因为其中一边比另一边薄。
更细一点看,马来语那86格里有23格是借的,真自有只有63格;印尼语151格里41格是借的,真自有110格。两边借的比例也不一样。
这一条也说明了为什么不能拿一门语言的数据去推另一门。两门语言再近,维护它们的是两拨人,数据成色就是两回事。
回历缺数据的后果比佛历严重得多
回缺到通用层之后,纪年缩写是AH两个字母,月份则直接借公历的月份名。这就出问题了。
回历的第9个月是斋月,跟公历的9月毫无关系。如果月份名直接借公历,那么回历日期在页面上会显示成一个公历月份名配一个对不上的数字。
这不是难看的问题,是错的问题。前面那张表里最后一列的意义在这里体现出来:佛历缺数据只是纪年名难看,回历缺数据是整个日期读不对。
同一个道理适用于波斯历和埃塞历。做伊朗市场的人尤其要留意,因为波斯历是伊朗的第一顺位历法,不是备选。
日本年号那一格为什么是全表最厚的?
日语文件里的年号段有521格,其中474条是纪年名。这是整份数据里单套历法最厚的一格,比第二名多一倍还不止。
474条纪年名意味着什么
474这个数字来自日本年号制度本身:从公元645年到现在,一共换过两百多个年号,而每个年号还要有全称、缩写、单字母缩写几种写法。
最后几条是M、T、S、H、R,分别对应明治、大正、昭和、平成、令和的首字母。这几个单字母缩写在日本的表格和证件上很常见。
令和是今年5月1日启用的新年号,这一版数据里已经有了。
做日语内容的人应该知道,年号和公历在日本是并行的,政府文书、合同、证件用年号,商业场合两者都有。
这474条里还包含了一批很早期的年号,公元七世纪的都在。实际业务里用不到,但它们的存在说明这份数据是照着一份完整的历史清单填的,不是按需填的。
今年春天那次改元逼出了一个额外版本
年号跟其他所有历法有一个根本区别:它会因为一个人的更替而在没有任何数学规律的时间点上重置。
新年号是4月1日公布的,5月1日生效,中间只有一个月。这意味着全世界所有处理日语日期的软件,都要在一个月内更新一次数据。
公共区域数据这一侧的反应写在今年春天那一版的发布说明里,原话是:预计4月发布一个点版本,包含针对日本历法的进一步改动。
一个专门为了一套历法而加发的版本,这在这份数据的历史上不常见。它也从侧面说明,历法这一格一旦出错,影响面有多大。
值得对照的是,同一时期各家操作系统和办公软件也都发了补丁。一个国家换个年号,全球的软件供应链跟着动一遍,这种事在别的历法上不会发生。
其他语言里的日本年号也不算薄
前面提过老挝语给日本年号填了236格。这不是孤例:泰语274格、繁体中文795格、阿拉伯语237格、俄语237格、瑞典语240格、韩语250格。
把这些数字跟同一门语言自己那套本地历法比一比,很多都是反的。阿拉伯语的回历117格,日本年号237格,多了一倍。
解释还是那一条:年号是一份可以机械音译的固定清单,而回历的月份名要看当地的实际写法。
这也给了一个反向的判断法:如果一门语言的某套历法数据特别厚,先看看那套历法的内容是不是清单型的,别急着当成本地化做得好。
这件事对你的启发不在日本
日本这个案例的价值在于它把一件平时看不见的事放大了:历法数据不是常量,它会变。
年号是最极端的一种,但不是唯一一种。沙特2016年调整过财年基准,泰国的官方年份在不同文件上偶尔并列两套,回历各个算法版本之间的日期能差一天。
所以这一格不能查一次就当查完了。它属于要跟着依赖库版本一起复查的那一类。
顺带说一句,日语这一格填得厚跟日本的技术社群规模有关系。这也解释了为什么其他语言里的日本年号也填得不错——它有一个明确的、可批量音译的清单摆在那儿。
两类非公历必须分开对待
把前面几节的结论收一下,这一格真正可操作的判据只有一条。
只改年份的那一类
佛历、民国纪年、日本年号属于这一类。它们的月和日跟公历完全一致,区别只在年份数字和纪年标识上。
这一类缺数据的后果是可控的:纪年标识会显示成BE、R.O.C.这类英文缩写,月份日期都是对的。用户能看懂,只是觉得别扭。
补救成本也低。你只需要在自己的模板里把那个纪年标识替换成本地写法,几行代码的事,不需要动日期计算逻辑。
甚至可以更简单:这一类历法的年份换算是加减一个固定数,泰国加543,台湾地区减1911,自己算比调库还稳。
这一类还有个好处:即使你完全不处理,页面上显示的公历日期对本地用户来说也是可读的,因为这些地方公历同样通用。风险等级低。
连月份都不一样的那一类
回历、波斯历、埃塞历、希伯来历属于这一类。它们的月份数量、月份长度、年的起点跟公历全都不同。
这一类缺数据的后果是硬伤:月份名会借用公历的,导致一个明确错误的日期显示在页面上。而且没法用简单的加减修复。
补救只有两条路:要么找到一份可靠的本地月份名清单自己填进去,要么干脆不在这些市场上启用非公历显示,全部走公历。
后一条路听起来消极,但它比显示一个错的日期强。在这一格上,不做比做错便宜得多。
这一类里波斯历要额外小心,因为伊朗的第一顺位就是它,不是公历。也就是说在伊朗市场上,公历才是那个需要额外说明的东西,关系反过来了。
希伯来历算第三种情况
希伯来历的月份跟公历完全不同,按前面的分类属于第二类。但它还多一层麻烦:它有闰月,闰年的时候多出一个月,而且月份编号会变。
所以希伯来历的月份名条目数比别的历法多,以色列本地的希伯来语填了154格,其中84条是月份相关的,比回历的72条多。
数据里能看到闰月那个月份被写成两条,一条对应平年一条对应闰年。这种结构如果你自己填是想不到的。
结论是:这一类历法不要试图自己补数据,能找到本地填好的就用,找不到就别启用。希伯来语这门语言本身的坑已经够多了,历法这一格好在数据是齐的。
运行时具体挑哪套历法、挑不到的时候怎么退,写在ICU的历法服务文档里。自己实现闰月逻辑之前,先读一遍那份文档能省掉很多返工。
埃塞历那一格有个特别难看的细节
通用层里其他历法的纪年缩写好歹是有意义的字母——佛历是BE,回历是AH,波斯历是AP,印度国历是Saka。
埃塞历的通用值是ERA0和ERA1。这不是缩写,这是占位符,是程序里的变量名漏到了数据里。
阿姆哈拉语自己填了埃塞历的173格,所以埃塞俄比亚市场没事。但厄立特里亚的提格里尼亚语整段不存在,它的用户如果看到埃塞历,纪年名就是ERA1。
这一条可以当成整篇文章的缩影:缺数据的时候系统不会道歉,它会拿出一个内部标识符,然后正常渲染。
页面上哪几个地方会先出事?
把上面的东西落到实际页面上,出事的顺序是有规律的。
第一个出事的是表单校验
用户按本地习惯填出生日期,填的是佛历年份2530,你的年龄校验拿它减2019,算出一个负数,然后拒单。
这是最早被发现的一类,因为用户下不了单会来投诉。但投诉的措辞通常是网站不让我注册,不会是历法问题。
反过来的情况更隐蔽:用户按公历填,你的系统按佛历解析,算出这个人有五百多岁,某些风控规则会静默拦截。
解决办法不是猜,是在输入框旁边明确标出用哪套历法,并且在提交时做范围校验。这一步的成本远低于事后排查。
还有一种更少见但更贵的:优惠券的有效期。用户按本地历法理解截止日,你按公历判断,中间差出来的那几天全是客诉。
第二个是结构化数据
商品的上架时间、活动的起止时间、文章的发布时间,这些字段在结构化标记里必须是公历的标准格式。
如果你的日期渲染层被配置成按本地历法输出,而结构化数据又复用了同一个渲染函数,那么标记里会出现一个五百多年后的日期。
这类错误的表现是整段标记被判为无效,而不是某一个字段有问题。排查的时候很容易往标记结构上找,找半天。
判据很简单:给用户看的日期和给机器看的日期必须走两条不同的路径,前者可以本地化,后者永远是公历。
这一条跟锚文本报告里那些对不上的数字是同一类问题:给机器读的那一份有自己的口径,跟页面上给人看的那一份从来不共享,而没有任何一环会提醒你两者已经不一致了。
第四个是导出文件和对账
订单导出、财务对账、发票这些场景里的日期如果被本地化了,接收方多半读不对。尤其是跟本地供应商对接,对方发来的单据上写的可能就是本地历法。
这是双向的:你发出去的可能被对方按公历读,对方发来的可能被你按公历读。两个方向都会把整批数据的时间轴平移几百年。
做泰国供应链的人对这一条会有实感,因为泰国的商业单据上佛历年份很常见,而且不会特别标注。
处理原则跟结构化数据一样:凡是给机器读的日期一律公历,并且在字段名或者文件头上写清楚是公历。
还有一类是定时任务和报表。按自然月跑的任务如果用了本地历法的月边界,回历那种一年354天的历法会让某些月被跑两次、某些月被跳过,而日志里看不出异常。
第三个是排序和筛选
如果日期在数据库里存的是公历,展示的时候转成本地历法,那么按展示值排序会得到一个错误的顺序,尤其是跨年的时候。
回历这类一年只有354天的历法尤其容易出问题,因为它的年份边界跟公历的年份边界每年都在移动。
正确的做法是排序永远用底层的公历值,只在最后渲染那一步做转换。这条规则跟处理工具返回值的思路一致:转换只在最外层做一次。
顺带说一句,促销倒计时也归这一类。后端存公历,前端按本地历法渲染截止日期,用户看到的是五百多年后到期,这个活动的紧迫感就没了。
一个容易漏的地方是分页和归档页。按年月归档的列表如果年份是转换后的值,归档页的地址和标题会跟着变,而这些地址是被收录过的。
上线前这一格怎么查?
整个检查过程不超过十五分钟,四步。
第一步:确认这个市场用不用非公历
先查历法偏好表里有没有这个地区。有,看它的第一顺位是不是公历。是公历,这一格的优先级可以降低;不是,必须处理。
然后看第二顺位。有第二顺位的地区,虽然默认渲染不会出错,但节庆和活动排期需要单独考虑。
没出现在表里的地区,按纯公历处理。要注意历法也可以作为扩展子标签直接写进语言标签,写法由RFC 5646规定,别自己发明格式。
这一步只需要打开那张表看一眼,两分钟。
第二步:查这门语言那一套历法填了没有
打开这门语言的文件,搜索那套历法的名字。整段不存在的,直接判定为零。
段落存在的,要接着看两件事:里面有几个格子的值是三个向上的箭头,有几个格子带着未确认或暂定的标记。这两类都要剔掉。
剩下的数字才是真实的填充量。低于10格的,基本等同于没填。
这一步是整个检查里唯一需要动手数的地方,一门语言五分钟。
如果你不方便去翻原始文件,退而求其次的办法是在本地环境里用浏览器自带的日期格式化接口渲染一个日期,看输出里的纪年名是不是本地文字。是英文缩写,就说明回落了。
第三步:按两类分开定处理方式
只改年份的那一类,缺数据就在自己的模板里补一个纪年标识,年份换算用固定加减,不依赖库。
连月份都不一样的那一类,缺数据就先关掉非公历显示,走纯公历,并且在页面上明确标注这是公历日期。
两类都要在输入端把历法说清楚,这一条跟数据填不填无关,是产品设计问题。
做完这一步,就可以给这个市场写一句结论了。
还有一条通用做法:无论哪一类,都在页面上显示日期的地方标明用的是哪套历法。多写三个字,能省掉一大半的客诉。
查完写成一句话
最后把结论收成一句:这个市场默认用哪套历法,这门语言那一格填了多少,属于两类里的哪一类,以及页面上哪几个位置需要人工验。
这句话跟着市场进排期表,跟词典能力、字体能力、界面翻译完成度并列。它们分属不同的层,但都是上线前该有答案的。
要提醒的是,这一层的结论不该影响做不做这个市场。泰国市场值不值得做,跟泰语的佛历数据齐不齐是两个问题。
它影响的只是预留多少工时,以及要不要在第一版就上非公历显示。多数情况下第一版走纯公历、把非公历放进第二版,是更稳的排法。
这句话还有一个用处:跟本地化服务商谈的时候可以直接问,你们对这门语言的历法数据是自己补还是用公共数据。答不上来的,说明这一格他们没查过。
第四步:把日期渲染和数据输出分开
最后一步跟数据无关,是工程约定:确保给用户看的日期和写进结构化标记、写进接口返回、写进导出文件的日期,走的不是同一个函数。
这一条做到了,前面所有的坑最多影响显示,不会影响数据正确性。没做到,一个配置项就能让整站的日期数据全错。
值得检查的地方还有邮件模板和短信模板,它们经常是另一套渲染逻辑,配置项也另有一份。
保哥见过一次事故,站上日期全对,确认邮件里全是佛历年,因为邮件服务是另一个团队接的,那边照着文档把区域设置填成了泰语。
哪些相邻的问题不归这一层管?
这一格容易跟旁边几件事混起来,划一下界。
本地数字系统是另一件事
有些语言默认使用本族数字而不是拉丁数字。缅甸语的默认数字系统是缅甸数字,尼泊尔语是天城文数字,孟加拉语是孟加拉数字,波斯语是波斯扩展数字。
这跟历法完全无关,但它们经常一起出问题,因为都由同一个格式化调用决定。有意思的是高棉语和老挝语的默认数字系统是拉丁数字,虽然它们都有自己的一套数字。
所以同一片佛历圈里,缅甸语页面的价格默认会用缅甸数字,高棉语和老挝语默认用阿拉伯数字。这三门语言在这一格上的行为完全不同。
处理原则也不一样:数字系统影响的是可读性和可搜索性,历法影响的是正确性。两者别一起改,改完分不清是哪一个导致的变化。
这一格还跟收录有关。如果价格用本族数字渲染,那么页面上的价格字符串跟用户在搜索框里打的可能不是同一串字符,这属于关键词覆盖那一类问题而不是显示问题。
字符渲染是再往下一层
纪年名如果是本地文字写的,还得字体里有那些字形才画得出来。泰文的纪年缩写、埃塞俄比亚文的月份名,都不在常见的西文字体子集里。
这就跟网页字体的字形集取舍连上了:为了控制首屏体积裁掉的字符,可能正好是日期里要用的那几个。
排查顺序是先确认数据层输出的是哪几个字符,再确认字体里有没有。反过来查会很绕。
这两层的负责人通常不是同一批人,中间那道缝是问题最容易掉进去的地方。
一个具体的例子:泰文的纪年缩写里有一个点号加两个泰文字母,如果字体子集是按常用字裁的,这三个字符未必都在。
时区和夏令时不在这里
时区是另一套完全独立的数据,跟历法没有交集。一个日期用哪套历法显示,和它属于哪个时区的哪一天,是两个正交的问题。
把它们混在一起排查会得出很奇怪的结论,比如以为某个市场的日期偏了一天是历法问题,其实是时区问题。
分辨方法:偏一天的是时区,偏几百年的是历法,月份对不上的是历法里连月份都不一样的那一类。
这三种偏差的量级完全不同,一眼就能分开。
顺带说一句,伊朗直到2022年前都实行夏令时,而它同时又用波斯历,两件事叠在一起排查会很痛苦。分开查是唯一的办法。
节庆日期不归这一层,也不归任何一层
斋月哪天开始、卫塞节是哪一天、诺鲁孜节今年落在几号,这些不在区域数据里。这份数据管的是怎么把一个日期写出来,不管哪些日子是节日。
节假日数据是另一套东西,而且各国口径不一,很多国家的宗教节日还要等官方或者宗教机构临近才公布确切日期。
所以活动排期这件事没有一个可以查的公共数据源可用,只能靠当地同事或者本地服务商。这一点跟前面所有内容都不同。
把它写在这里是为了避免一种常见的误会:以为把历法数据配好了,节庆排期就自动对了。这两件事完全不搭界。
翻译和审校碰不到这一格
跟上一篇讲的那些格式字段一样,历法数据不出现在任何一份待翻译稿件上,它是系统渲染的产物。
所以再认真的母语审校流程也覆盖不到它,除非你专门让审校人员看渲染后的真实页面。
要覆盖它只有一个办法:在验收清单里单独列一条,让本地同事看真实页面上的日期,而不是看文档。
这一条加上之后,这类问题基本能在上线前一天被发现,而不是上线三个月后被客服转述。
这一条跟上一篇的结论是同一个:所有由系统渲染出来的字符串,都不在任何一份人工审校的覆盖范围里,需要单独建一条验收路径。
常见问题解答
我的市场清单里要不要处理非公历?
先查历法偏好表。第一顺位不是公历的只有三组:泰国是佛历,伊朗和阿富汗是波斯历,沙特是回历的一个特定算法版本。这三组必须处理。有第二顺位的地区,默认渲染不会出错,但节庆和活动排期要单独考虑,包括以色列、埃及、埃塞俄比亚、印度、日本、韩国、台湾地区,以及中国大陆和新加坡的农历。清单里完全没出现的地区,按纯公历处理就行。
怎么判断一门语言的历法数据到底填没填?
打开这门语言的原始文件,搜那套历法的名字。整段不存在的直接判零。段落存在的,要剔掉两类假数据:值是三个向上箭头的继承标记,以及带着未确认或暂定标记的格子——后者在生成发布数据时会被丢掉。剔完之后低于10格的基本等同于没填。实测里有五门语言按第一种数法有数据,剔完之后是零,其中包括柬埔寨的高棉语,丢掉的正好是它自己在用的佛历。
缺数据的时候页面上会显示什么?
会回落到最上层的通用值,而且不报任何错。佛历显示BE,回历显示AH,波斯历显示AP,印度国历显示Saka,民国纪年显示R.O.C.。最难看的是埃塞历,通用值是ERA0和ERA1,那是程序里的占位符不是缩写。月份则一律直接借用公历的月份名——对佛历这类只改年份的历法没关系,对回历这类连月份都不同的历法就是错的。
佛历和回历缺数据的严重程度一样吗?
差很多。佛历、民国纪年、日本年号的月和日跟公历完全一致,只有年份数字不同,缺数据只是纪年标识变成英文缩写,用户能看懂。回历、波斯历、埃塞历、希伯来历的月份数量和长度跟公历全不一样,缺数据之后月份名会借公历的,页面上出现的是一个明确错误的日期。前一类可以在自己模板里几行代码补上,后一类要么找到可靠的本地月份名清单,要么直接关掉非公历显示走纯公历。
年份换算要不要用现成的库?
只改年份的那一类不用,自己加减一个固定数更稳:泰国加543,台湾地区减1911。连月份都不一样的那一类必须用库,因为月长和闰规则不是简单算术,尤其回历还有好几个算法版本,彼此的日期能差一天。用库的时候记得锁版本,并且确认它内置的区域数据是哪一年的——日本年号这类会变的东西,旧版本里可能根本没有。
为什么这类问题上线前很少被发现?
因为它不出现在任何一份待翻译稿件上,是系统渲染的产物,所以翻译和审校流程碰不到它;它也不报错,页面正常、布局正常、状态码正常,所以监控发现不了。唯一能发现它的是让懂那门语言的人看渲染后的真实页面,并且专门盯日期那几个字符。把这一条写进验收清单,成本只有几分钟。
结构化数据里的日期该用哪套历法?
永远是公历的标准格式,没有例外。给用户看的日期可以本地化,给机器看的不行。这两者必须走两条不同的代码路径,如果复用了同一个渲染函数,一个配置项就能让整站的标记里出现五百多年后的日期,而表现是整段标记被判为无效,不是某个字段报错。同样的规则适用于接口返回值、导出文件和日志。
本文标题:《缅甸和斯里兰卡天天在用佛历,这两门语言的日历数据里一条都没有》
本文链接:https://zhangwenbao.com/minor-language-non-gregorian-calendar-data.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0