E.164是机器标准格式,与各国人类书写习惯不同;AI常混淆两者导致号码无法拨通或显示不规范,需对输出强制格式化。
电话号码的各国正确格式化
AI 生成的号码看起来很合理,能通过同一个模型写的正则校验,但就是打不通。几乎无一例外,原因是两种格式混用了——两者之间有一个数字看起来可选、实际上必填。
每个电话号码都有一种标准的机器格式和一种人类阅读的显示格式,两者不可互换。机器格式是 E.164:加号、国家区号、国内有效号码,不含空格、括号、连字符或点号。显示格式则是各国人民实际写在名片上的样子,是另一串字符,有时位数也不同。
AI 输出的失败产物是两种格式的混合体。你会得到 +44 (0)20 7946 0958——这在英国印刷物中是一种真实存在的惯例,括号里的零告诉国内呼叫者要拨这个号,告诉国际呼叫者要去掉它——然后把它传给一个要求严格 E.164 格式的电话 API,这个 API 会拒绝括号。或者你在给英国读者的发票上印上 +442079460958,读者必须自己数位数才能弄清楚长途区号到哪里为止。
E.164 实际规定了什么
国际电信联盟发布的 ITU-T Recommendation E.164(ITU-T E.164)定义了国际公用电信编号计划。对数据模型来说,重要的规则很短:
号码最多 15 位,含国家区号。全球没有统一的最小位数,短号码确实存在。
开头的 + 是符号,不是数字。它表示"后面是一个国际号码",实际拨出时会被呼叫者所在国的国际接入前缀替换——欧洲大部分地区是 00,北美编号计划是 011。
国家区号是一到三位,且不能以任何简单方式自定界。+1 是一位,+44 是两位,+353 是三位。你不能通过截取固定前缀来切分号码。
E.164 完全不含分隔符。存储的号码中任何空格、连字符或括号都意味着该字段存的是显示字符串。
长途前缀及其保留位置
这是错误数字最大的单一来源。许多国家使用国内长途前缀——国内拨号时在区号前多拨的一个数字——它不是国际号码的一部分。通常的规则是去掉它:
United Kingdom national: 020 7946 0958 E.164: +442079460958
Germany national: 030 12345678 E.164: +493012345678
Japan national: 03-1234-5678 E.164: +81312345678
Australia national: (02) 9374 4000 E.164: +61293744000
Russia national: 8 495 123-45-67 E.164: +74951234567
注意俄罗斯:长途前缀是 8,不是 0,它被国家代码 7 替换。一个假设"去掉开头的零"的转换程序会生成 +78495123456,这多了一位且无法路由。
然后有一个完全打破这个规则的例外:
Italy (Rome) national: 06 6982 1234 E.164: +390669821234
Italy (mobile) national: 320 123 4567 E.164: +393201234567
意大利在国际格式中保留开头的零。罗马的固定电话是 +39 06 ...,带着那个零,因为在意大利编号计划中那个零是有效号码的一部分,而不是长途前缀。去掉它——通用"去掉开头 0"步骤会做的事,也是模型通常会生成的——会使号码无效。科特迪瓦和其他少数计划也有类似行为。
编号计划由国家监管机构变更,变更后旧号码会失效。墨西哥取消了以前手机国家代码后面的那个额外数字;几个国家给区号增加了位数。任何硬编码的各国规则都应该视为需要向监管机构核实的事项,优先使用维护中的库。
固定位数的假设
模型为号码写的校验器通常比号码本身更糟。反复出现的形式是 /^\d{10}$/ 或 /^\+?[1-9]\d{7,14}$/,其中第一个把北美编号计划编码成了好像它是普适的一样。
真实的国内有效号码不是固定位数的,在几个国家甚至在同一个国家内部都不是固定位数。德国区号从两位(柏林是 30,汉堡是 40)到小城镇的五位不等,用户号码填满剩余部分,所以德国号码总长度相差好几位。奥地利号码类似。一个固定位数的正则表达式会以用户无法解决的方式拒绝有效号码,因为他们没有自己号码的其他形式可以输入。
你唯一能安全应用在全球的位数规则是 E.164 的最多 15 位。所有更窄的规则都需要按国家的元数据,这正是 Google 的 libphonenumber 存在的意义:它保存每个地区的位数和前缀规则,并随监管机构变更而更新。它的四种输出格式——E164、INTERNATIONAL、NATIONAL 和 RFC3966——就是人们说"格式化"时指的四种东西。最后一个是 RFC 3966 定义的 tel: URI,这才是 HTML 链接里应该放的东西。
各国显示惯例
显示格式就是分组,分组方式是各国不同的。同 11 位数字在每个国家写法不同:
US (212) 555-0123 or 212-555-0123
UK 020 7946 0958 (area code and subscriber, space-separated)
France 01 42 68 53 00 (five pairs, always)
Germany 030 12345678 (area code, space, subscriber unbroken)
Japan 03-1234-5678 (hyphens)
China 138 0013 8000 (mobile: 3-4-4)
法国是最清楚的例子,其惯例与号码结构完全无关:法国号码总是写成五组两位数字,不管区号在哪里结束,一个法国读者看到 0142685300 会读得很慢并且心存疑虑。上面的英国和美国例子使用了监管机构为虚构和文档保留的号段,在需要不可能打通真人的示例号码时值得了解。
始终将 E.164 存储在一个字段中。不是国家列加本地号码列两列:E.164 已经包含了国家代码,两列意味着两种可能不一致的方式。
在渲染时根据存储的 E.164 加上查看者的地区信息进行格式化,使用带有各地区数据的库。绝不存储显示字符串。
不要让模型规范化号码。让它提取它看到的数字和它认为这些数字所属的国家,然后在代码中做转换。长途前缀规则正是那种模型会自信地、但在批量处理中又不一致地应用的各国例外。
接受任何输入。让用户输入空格、括号、点和开头的零;自己去掉它们并告诉他们你解析出了什么。因为标点符号拒绝一个号码和因为姓氏只有两个字母就拒绝一个真实的姓氏是同一类错误。
Formatting an Address Correctly for a Given Country
Validating Postal Code Formats Across Countries
Why AI-Generated Forms Break on Two-Character Surnames