两个午夜之间为什么只有 23 小时?用 JavaScript 验证日历天数

举报
yd_225778114 发表于 2026/11/12 10:52:41 2026/11/12
【摘要】 日期选择器只提交年月日时,按本地午夜计算会遇到夏令时边界。用两地时区的对照测试和可运行 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 章节。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。