Perl 13:规范化服务名,也要拒绝假的闰日
仅靠 \d{4}-\d\d-\d\d 可以放行 2025-02-29。它长得像 UTC 时间,但 2025 不是闰年。格式合法与日期存在是两项检查;目前输入强制尾部 Z,所以即使日期有效也不表示日志时间由可信时钟生成。
read_records 用 Time::Local::timegm_modern 按 UTC 校验年、月、日、时、分、秒,年限制为 1970–2099;内部异常在行边界收束为 line N: invalid record。fixtures/bad-date.tsv 含 2025-02-29T00:00:00Z,CLI STDOUT 空、STDERR line 1: invalid record、退出码 2。对应的真实闰日 2024-02-29T00:00:00Z 则通过,绝不凭“正则看起来匹配”判日期正确。本机 Time::Local 是 1.30,属已安装核心模块;模块装载列表见 examples/perl-language/VERSIONS.md,没有发生第三方包隔离安装。
同一次规范化还处理 ASCII 服务名:输入 API 被接受,parse 输出 api,原始消息 queued 两侧空格不动。golden/normalized.txt 把输出字节固定下来,golden/report-mixed.txt 把三种等级计数固定下来。当前没有实现时区换算,也不将服务名做任意 Unicode 大小写折叠;它只对契约允许的 ASCII 服务标识统一大小写。新增语义校验是 13 的增量;00 的早期示例只对时间字面形式做断言,不能用它推断后来已验收的日期规则。
字段规范化不能越过标识符契约
旧入口约定 ASCII 服务名小写,新版原定课程入口明确保留服务名,并只对消息做 NFC。API 与 api 是否属于同一服务是数据域规则,不能从 Unicode 规范化推导。消息 e 加组合重音与单码点 é 可规范成相同字符序列,但 raw_message 保留规范化前文本;NFC 不改变原始时间,也不生成新的 UTC 观测。
新版时间语法使用 [0-9] 而不是 \d,明确只接受 ASCII 数字。Perl 正则在 Unicode 条件下的十进制数字类可能包含其他码点;形状检查如果比后续数值解析允许的集合更宽,就会让校验边界难解释。解析器同时检查字段数、ASCII 范围和日历,三个检查分别对应不同反例。
timegm_modern 的成功不证明业务上的时间可信、先后顺序正确或服务器时钟同步。课程只判定指定年范围内这个 UTC 日期和时分秒可表达。不能以一个闰日样本推广到支持所有历史历法或时区规则,版本与输入语法已经限定了实验范围。
[PATTERN] 每个规范化步骤标明目标字段、变换和保留证据。把格式校验、日期存在与业务身份合并,会让工具在读入时不可逆地改变含义。
练习:把有效例子的 2024-02-29 改成 2025-02-28,应如何验证?先看日期通过,再看旧 parse 输出仍是 Z 且 service=api;新版则保留输入服务名。一次成功不能代替坏日期的拒绝测试。第二题:NFC 是否应把服务名统一大小写?不应,这超出它处理的规范化问题和本字段契约。
| 变换 | 旧入口 | 新入口 |
|---|---|---|
| UTC 日历检查 | 执行 | 执行 |
| 服务名小写 | 执行 | 不执行 |
| 消息 NFC | 不执行 | 执行并留 raw_message |
来源:Perl 5.38.2 官方 pod/perlfunc.pod(eval、die)、pod/perlre.pod(字符类),核心模块 Time::Local 1.30 的 timegm_modern/日历范围检查;金标在历史与补全证据目录。






