Luxon 把全角 2018 解析成 201,isValid 仍为 true
Luxon3.7.2的fullwide数字识别范围与解码范围不同,8和9可能被丢弃。限定输入格式后逐位归一化,避免把isValid当成原文未变的证明。
读取全角日期 2018-01-01,结果年份成了201,DateTime.isValid却仍为true。这种静默变化需要检查Luxon的numberingSystem,不能仅按“解析没报错”继续保存日期。
Luxon3.7.2的fullwide匹配器允许全角0到9,解码范围却只到7。8和9被识别为数字后,找不到解码分支,最终从累积文本中消失。2018因此可能被读成201,而201仍然是一个有效年份。
数字识别与数字解码没有对应上
同一文件中的hanidec也有边界问题:字符类将竖线写进了可匹配字符,解码表却不包含它,可能把 | 转成-1。两者都说明isValid只判断得到的日期对象,不保证它忠实对应输入字符。
这不是JavaScript安全整数精度问题,也不是输入法只改变了字体。需要比较原始字符与最终日期字段,不能用格式化后的另一个错误日期覆盖原文再检查。
日期字段只允许数字时,收紧归一化范围
若接口明确接受四位年、两位月日,以及ASCII或全角数字,可以先检查格式,只转换全角数字,再交给latn解析:
import { DateTime } from 'luxon';
function parseNumericDate(text) {
if (!/^[0-90-9]{4}-[0-90-9]{2}-[0-90-9]{2}$/.test(text)) {
throw new RangeError('Expected yyyy-MM-dd');
}
const ascii = text.replace(/[0-9]/g,
ch => String(ch.charCodeAt(0) - 0xFF10));
const result = DateTime.fromFormat(ascii, 'yyyy-MM-dd', {
numberingSystem: 'latn',
zone: 'UTC',
});
if (!result.isValid) throw new RangeError('Invalid calendar date');
return result;
}
这个示例只处理日历日期,UTC是它明确选用的展示和计算区域;真实业务使用其他时区时应保留原约定。不要将这段规则直接用于带时间、其他分隔符或汉字数字的自由文本。
输入应先按接口契约保证为字符串。它不会把所有Unicode字符统一转成ASCII,也不接受竖线充当汉字数字。NFKC还会归一化其他兼容字符,影响范围更大,不能为修复这两个数字就对所有业务文本全局调用。
保留能发现静默变化的检查
分别覆盖全角0到9,尤其是8和9,以及混合ASCII数字、非法分隔符和真实无效日期。检查结果中的年月日是否与明确归一化后的输入一致,而不只检查isValid。
现有数据若已按错误年份保存,先保留原始输入和受影响范围,再按业务流程修正;升级解析库不会自动找回曾经被丢弃的数字。正式3.7.2仍有这两套表的差异,后续修正需要核对具体发行物,不能用主分支补丁代替已安装版本的事实。


