两个午夜之间为什么只有 23 小时?用 JavaScript 验证日历天数
阅读鸿蒙计算器模板时,我看到日期工具有两条很适合验收的规则。相邻日期相差 1 天,同一个日期相差 0 天。这个需求里,用户选择的是日历上的年月日,结果应当只取决于这两个日期。若控件把选择结果交给业务层时先转换为本地午夜,再把两个时间戳相减,碰到跨夏令时的日期就可能得到小数。这里用独立 JavaScript 样本演示这个边界,不涉及模板的实际运行结果。
用两对相邻日期复现
在采用夏令时的时区,以纽约为例,2026 年 3 月 8 日到 3 月 9 日的两个本地午夜只相隔 23 小时。2026 年 11 月 1 日到 11 月 2 日则相隔 25 小时。两对输入在日历上都只相差 1 天。对于要求按绝对时长计费的需求,23 和 25 都是有效结果。对于日期选择器,这些小时数会把设备时区混进本来只需要处理年月日的计算。
取整也不可靠。向下取整会把 23 小时判成 0 天,向上取整会把 25 小时判成 2 天。这里将两个选中的日期分别映射到 UTC 零点,仅把这个数值用作日期编号,再对编号求差。传入的两个字符串已经代表日期,算法不会把某个真实事件的时间转成 UTC 日期。
一个可直接运行的实现
输入限定为四位年份的 YYYY-MM-DD,年份范围为 0001 至 9999。JavaScript 会把部分越界年月日滚动到下一月,构造后必须对照原年月日。使用 setUTCFullYear 也避开了 Date.UTC 将 0 至 99 映射到 1900 至 1999 的规则。
const MS_PER_DAY = 86_400_000;
function calendarDayNumber(value) {
if (typeof value !== 'string' || value.length !== 10 ||
!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
throw new TypeError('Expected YYYY-MM-DD');
}
const [year, month, day] = value.split('-').map(Number);
const date = new Date(0);
date.setUTCFullYear(year, month - 1, day);
if (year === 0 || date.getUTCFullYear() !== year ||
date.getUTCMonth() + 1 !== month || date.getUTCDate() !== day) {
throw new RangeError('Invalid calendar date');
}
return date.getTime() / MS_PER_DAY;
}
function calendarDistance(first, second) {
return Math.abs(calendarDayNumber(second) - calendarDayNumber(first));
}
验收时看输入语义
我用 Node.js 验证了同一天、跨夏令时、跨年、闰年、反向输入、两位数年份和非法日期。对日期间隔控件而言,反向选择仍返回正数,同一天返回 0。若产品还要做“含起止日共几天”,在已验证的结果上由业务规则明确加 1,并单测同一天返回 1。若做的是倒计时,保留两端时间戳并按时长计算,这样跨夏令时的 23 或 25 小时才能保留下来。
一个能区分两类实现的回归方式,是在相同日期输入下切换测试进程的时区。设备处在北京或纽约时,日历日期输入的期望值应相同。随后单独运行以当地午夜构造的时长对照,验证春季为 23 小时,秋季为 25 小时。这样既覆盖了数字,还覆盖了用户选择日期时想表达的含义。
参考资料为鸿蒙开发实践中心《计算器应用模板》,以及 ECMAScript 规范的 Date 对象、Date.UTC 和 Date.prototype.setUTCFullYear 章节。
- 点赞
- 收藏
- 关注作者
评论(0)