DEBUG.WIKI
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器

Luxon 3.7.2 无效 Interval 调用 isEmpty 报 valueOf 错误

Interval对象存在不代表起止时间有效。先分离invalid、empty与正常区间,保留原输入错误;不要用交换端点或全局异常兜底掩盖valueOf异常。

Luxon 已经返回一个 isValid === false 的 Interval,再调用 isEmpty(),却抛出 TypeError: Cannot read properties of undefined (reading 'valueOf')。在 3.7.2 中,isEmpty 直接读取内部起止时间,没有先检查区间是否有效。

例如结束时间早于开始时间:

import { Interval } from 'luxon';

const interval = Interval.fromISO(
  '2024-06-01T12:00Z/2024-06-01T06:00Z',
);

在默认不因无效输入立即抛错的设置下,这里得到的是无效区间对象。它不是 null,也不是一个长度为零的正常区间。检查 if (interval) 只确认对象存在,挡不住后面的 isEmpty 异常。

无效区间与空区间需要不同的处理

3.7.2 的无效对象可以没有内部 start、end;isEmpty 却直接对它们调用 valueOf。与此不同,同一版本的 contains、isAfter 等方法有 isValid 防护,不能因其中一个方法返回 false,就假定其他方法也都安全。

输入错误可能来自结束时间早于开始时间、时间文本无法解析,或某个端点本来就是无效 DateTime。保留 invalidReason 和必要的 invalidExplanation,沿原始输入修正,别把失败统一显示成“没有数据”。这些说明可能包含输入值,对外日志仍应按应用的数据规则脱敏。

需要区分三种状态的入口,可以先检查有效性,再调用 isEmpty。以下函数接收已经创建好的 Luxon Interval:

function classifyInterval(interval) {
  if (!interval.isValid) {
    return { kind: 'invalid', reason: interval.invalidReason };
  }
  if (interval.isEmpty()) return { kind: 'empty' };
  return { kind: 'range' };
}

调用方对 invalid 应保留原有输入错误或业务拒绝路径;empty 才表示有效且起止相同的区间。如果业务确实只需要“有效且为空”的布尔值,可以使用 interval.isValid && interval.isEmpty(),但仍要决定无效输入如何报告,不能让 false 隐去错误。

不要为了绕过异常自动交换起止时间

倒置区间可能表示表单字段填反,也可能表示上游时间或时区解析错误。自动交换会改变预约、计费和过滤范围;在没有明确产品规则时,应拒绝该输入并修正产生它的位置。

同样,不要捕获所有 TypeError 后统一返回 true 或 false。那会掩盖真正传入 null、错误对象以及函数内部的其他编程问题。若参数本身就是 null,应先按接口契约处理;它与默认参数无法接住显式 null一样,需要区分字段缺省与无效值,不能指望一个默认对象覆盖所有情况。

Settings.throwOnInvalid = true 会让相关无效区间在创建时就抛出 InvalidIntervalError,检查点因而更早。是否采用这种全局策略,应按应用现有错误处理决定;不必为一个 isEmpty 调用突然改变所有 Luxon 使用者的行为。

截至 2026 年 10 月 24 日,正式 3.7.2 的 isEmpty 仍没有有效性检查。入口防护应覆盖正常范围、有效零长度、结束早于开始和不可解析输入;合法零长度返回 empty,无效输入进入 invalid 或原有创建错误路径,而不是再发生 valueOf 的 TypeError。