Effect Duration.Input 升级:DurationObject 支持 Temporal 风格对象输入的实现解析
2026/9/13 10:42:38 网站建设 项目流程

Effect Duration.Input 升级:DurationObject 支持 Temporal 风格对象输入的实现解析

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

本文基于 t3code 仓库内嵌的 effect-smol 仓库(位于.repos/effect-smol/)中一份真实的 changeset 变更说明展开,系统讲解Duration输入模型新增DurationObject这一 Temporal 风格对象输入的动机、类型定义、源码级解码实现与测试验证。读完本篇,你可以掌握 EffectDuration.Input的完整输入形态,理解对象式时长(如{ hours: 1, minutes: 30 })如何被精确换算、舍入与规范化,并能在自己的 Effect 项目里正确选用fromInputfromInputUnsafe两个转换入口。

变更背景:一份 changeset 说明了什么

本次讲解的核心文档是 t3code 仓库内 effect-smol 仓库的变更说明文件 duration-temporal-object-input.md,其原文内容如下:

--- "effect": patch --- Add `DurationObject` to `Duration.Input` to support Temporal-style object input. Durations can now be created from objects with named unit properties like `{ hours: 1, minutes: 30 }`, similar to `Temporal.Duration.from()`. Supported fields: `weeks`, `days`, `hours`, `minutes`, `seconds`, `millis`, `micros`, `nanos`.

从这份 changeset 可以直接读出三个关键事实:

  1. 变更级别为 patch:frontmatter 中"effect": patch表明这是对effect包的兼容性增强,不破坏既有 API;
  2. 目标是扩展Duration.Input联合类型:新增成员DurationObject,让"带命名字段的时间单位对象"成为合法输入,语义上对标 ECMAScript Temporal 提案中的Temporal.Duration.from()
  3. 字段集合:changeset 列出了weeksdayshoursminutessecondsmillismicrosnanos八类命名单位。

需要注意的一个细节:changeset 的措辞早于最终实现,其中亚毫秒单位写作millis/micros/nanos,而当前仓库源码中实际落地的接口字段名是millisecondsmicrosecondsnanoseconds(见下文类型定义)。本文一律以当前仓库源码为准。

这份变更目前处于 effect-smol 的 4.0.0 预发布(pre)流程中——pre.json 显示"mode": "pre", "tag": "rc",说明DurationObject相关能力随 4.0.0 rc 版本线发布,类型声明中也标注了@since 4.0.0

DurationObject 类型定义与完整 Input 联合类型

DurationObject的完整定义位于 Duration.ts:

export interface DurationObject { readonly weeks?: number | undefined readonly days?: number | undefined readonly hours?: number | undefined readonly minutes?: number | undefined readonly seconds?: number | undefined readonly milliseconds?: number | undefined readonly microseconds?: number | undefined readonly nanoseconds?: number | undefined }

所有字段均为可选且相互叠加(additive):任意子集组合都合法,{ seconds: 1 }{ days: 1, hours: 2 }或仅{ nanoseconds: 500 }都能构造出有效时长。接口注释明确写道 "Compatible with Temporal.Duration-like objects"(见 Duration.ts L182-L212),即设计目标是让持有 Temporal 风格时长对象的代码可以直接传入 Effect API。

DurationObject作为新成员被并入Duration.Input联合类型(Duration.ts L172-L180),完整的输入形态如下:

输入形态TypeScript 类型语义
已有时长Duration原样返回
毫秒数number按毫秒解释
纳秒数bigint按纳秒解释
高精度二元组readonly [seconds: number, nanos: number]秒 + 纳秒,对齐hrtime风格
时长字符串`${number} ${Unit}`"10 seconds",单位见Unit类型
无穷字符串"Infinity"/"-Infinity"正/负无穷
时长对象(本次新增)DurationObject命名单位字段叠加

其中Unit类型(Duration.ts L129-L145)支持单复数混写,如nano/nanosmicro/microsmilli/millis直至week/weeks

解码实现:fromInputUnsafe 的对象分支

所有输入形态最终由fromInputUnsafe统一解码。其对象分支(Duration.ts L287-L318)是本次变更的核心,源码逻辑可归纳为四层:

第一层:Duration 实例短路。若对象上带有 Duration 的 TypeId("~effect/time/Duration"),直接返回原实例,不做任何换算:

if (TypeId in input) return input as Duration

第二层:二元组分支。数组输入按[seconds, nanos]解释,带完整的边界处理:长度不为 2 或非数字字段时走invalid抛错;两个分量含NaN时返回zero;任一分量为-Infinity返回负无穷,为Infinity返回正无穷;否则以roundTiesAwayFromZero(input[0] * 1_000_000_000 + input[1])归一化为纳秒。

第三层:DurationObject 字段叠加(本次新增)。各命名单位先被折算到毫秒整数轴上(Duration.ts L305-L317):

const obj = input as DurationObject let millis = 0 // we can use truthy checks here, because 0 can be ignored if (obj.weeks) millis += obj.weeks * 604_800_000 // 1 周 = 7 * 86_400_000 if (obj.days) millis += obj.days * 86_400_000 // 1 天 = 24 * 3_600_000 if (obj.hours) millis += obj.hours * 3_600_000 if (obj.minutes) millis += obj.minutes * 60_000 if (obj.seconds) millis += obj.seconds * 1_000 if (obj.milliseconds) millis += obj.milliseconds if (!obj.microseconds && !obj.nanoseconds) return make(millis) return make(roundTiesAwayFromZero( millis * 1_000_000 + (obj.microseconds ?? 0) * 1_000 + (obj.nanoseconds ?? 0) ))

这里有三个值得注意的实现细节:

  • truthy 检查而非!= null:源码注释说明0值可以安全忽略,因为0 * 单位对累加结果无影响,代码因此保持简洁;
  • 快速路径:当输入不含亚毫秒字段(microseconds/nanoseconds)时,直接以纯毫秒值调用make(millis),避免一次大整数乘法与舍入;
  • 亚毫秒精度路径:一旦存在microsecondsnanoseconds,整体换算到纳秒轴——毫秒部分乘以1_000_000、微秒部分乘以1_000、纳秒直接相加——再交给roundTiesAwayFromZero四舍五入到最近纳秒(ties away from zero,平局远离零方向舍入)

第四层:非法输入兜底。走到分支末尾仍未匹配任何形态的输入会落入invalid(input),抛出Invalid Input: ...错误(Duration.ts L323-L325)。

roundTiesAwayFromZero本身定义在 Duration.ts L38-L39:

const roundTiesAwayFromZero = (input: number): bigint => BigInt(input < 0 ? Math.ceil(input - 0.5) : Math.floor(input + 0.5))

即对正数向下加 0.5 取整、对负数向上加 0.5 取整,保证±0.5平局时统一向绝对值增大方向舍入。这一规则与Input类型文档中 "Finite fractional values that are normalized to nanoseconds are rounded to the nearest nanosecond, with ties away from zero" 的声明一致。

类型文档中的行为示例

DurationObject接口的 jsdoc 内嵌了三个行为示例(Duration.ts L190-L198):

import { Duration } from "effect" Duration.fromInputUnsafe({ seconds: 30 }) // => Duration.seconds(30) Duration.fromInputUnsafe({ days: 1 }) // => Duration.days(1) Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) // => Duration.nanos(1_000_000_500n)

第三个示例值得品味:{ seconds: 1, nanoseconds: 500 }的结果是Nanos 形态1_000_000_500n)而非 Millis 形态——由于存在亚毫秒成分,整条换算被提升到纳秒轴,最终时长保留了500n纳秒的尾部精度。这说明对象的输出形态由输入是否含亚毫秒字段决定,而不是固定输出毫秒。

安全入口 fromInput 与错误边界

fromInputUnsafe的语义是"输入可信、非法即抛错"。当输入来源不可信(如用户配置、远端请求体)时,应使用fromInput,它通过Option.liftThrowable将抛错转换为Option(Duration.ts L343-L345):

export const fromInput: (u: Input) => Option.Option<Duration> = Option.liftThrowable( fromInputUnsafe )

文档给出的示例行为:

Duration.fromInput(1000) // => Option.some(Duration.seconds(1)) Duration.fromInput("invalid" as any) // => Option.none()

从源码结构看,fromInputDurationObject输入同样适用:一个字段名拼写错误(例如误用 changeset 早期措辞里的millis)的对象,虽然不会抛错(未知字段被忽略、合法字段照常累加),但会导致时长静默变短——这是对象式输入相比字符串输入更需要配套 Schema 校验的原因。在 Effect 生态中,这类边界通常由调用方在Schema层完成字段白名单校验后,再交给fromInput做兜底转换。

测试验证

effect-smol 的测试文件 Duration.test.ts 中包含针对对象输入的断言,例如:

deepStrictEqual(Duration.fromInputUnsafe({ seconds: 30 }), Duration.seconds(30))

(Duration.test.ts L76),以及多字段叠加的场景(Duration.test.ts L108):

Duration.fromInputUnsafe({ days: 1, hours: 2, minutes: 30, seconds: 15 })

测试覆盖了"单一字段与等价构造器一致"和"多字段叠加"两条主线,与上文源码中millis累加逻辑一一对应。

变更溯源与相关配套改动

从 CHANGELOG.md 可以确认该变更的合并信息:

  • PR #1696(commit5a84853,贡献者 @krzkaczor):"AddDurationObjecttoDuration.Inputto support Temporal-style object input",正文与 changeset 文件逐字一致,即本文开头的字段清单就是该 PR 的发布说明;
  • 同一条变更线还包含PR #1701(commit21d5d5e):"allow assigning Temporal types to DateTime & Duration input"——即在同一批 4.0.0 rc 变更中,DateTimeDuration的输入类型同步放宽了对 Temporal 类型赋值的接受度。两者组合后,Effect 的时间 API 在类型层面形成了一套与 Temporal 提案对齐的输入契约。

在项目中如何使用与适用前提

以当前仓库源码为准,在 effect-smol 4.0.0 rc 之后的版本中,任何接受Duration.Input的 Effect API(延迟、超时、TTL、调度间隔等)都可以直接传入命名单位对象:

import { Duration, Effect } from "effect" // Temporal 风格对象输入 const backoff = Duration.fromInputUnsafe({ minutes: 5, seconds: 30 }) // 等价于 5 分 30 秒的毫秒时长 // 含亚毫秒精度时输出纳秒形态 const precise = Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) // => Duration.nanos(1_000_000_500n) // 不可信输入走安全路径 const parsed = Duration.fromInput({ hours: 1, milliseconds: 250 })

适用前提与限制需要明确:

  1. 版本前提DurationObject与扩展后的Input联合类型均标注@since 4.0.0(Duration.ts L170),且 effect-smol 当前处于rc预发布模式(pre.json),尚未到稳定版的项目应在升级前留意 rc 阶段可能存在的接口微调;
  2. 字段名以源码为准:亚毫秒字段名为milliseconds/microseconds/nanoseconds,changeset 文本中的millis/micros/nanos是早期措辞;
  3. 未知字段被静默忽略:解码只读取白名单字段,拼写错误不会报错,建议在上游用 Schema 或Struct校验字段集合;
  4. 叠加语义:所有字段为正负相加,负数分量合法并参与远离零方向的舍入。

参考文件索引

文件作用
duration-temporal-object-input.md本次变更的 changeset 原文(patch 级别说明)
Duration.tsDurationObjectInput类型与fromInputUnsafe/fromInput实现
Duration.test.ts对象输入的测试断言
CHANGELOG.mdPR #1696/#1701 的合并记录
pre.jsoneffect-smol 当前处于 pre/rc 发布模式的配置

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询