From 5a0fc98aa12f5d1e442b2703ad3e0a5038a2a8bc Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 12:59:34 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=94=B9=E5=86=99=20Temporal=20?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E4=B8=8E=E5=B7=AE=E9=87=8F=E5=B1=82=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - common.rs 模块头删「最小实现子集」,补 Duration 槽位并逐类型列出 prop 布局; reject_partial_object_with_calendar_or_time_zone 文档改中文语义句并移操作名入括注 - instant.rs round_instant_ns 文档改「语义在前 + 操作名括注」,补 increment/溢出边界 - duration.rs duration_compare 步骤 4 与相等短路注释删除测试名,写明日历单位必须依赖相对点 - difference.rs 改写差值设置解析两处文档(去箭头电报体与裸操作名)、 nudge/bubble 两处函数文档、进位边界范围校验说明,统一 Nudge 族为中文语义加括注 --- crates/oxide_builtins/src/temporal/common.rs | 33 ++++++++----- .../oxide_builtins/src/temporal/difference.rs | 48 +++++++++++++------ .../oxide_builtins/src/temporal/duration.rs | 5 +- crates/oxide_builtins/src/temporal/instant.rs | 9 +++- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/crates/oxide_builtins/src/temporal/common.rs b/crates/oxide_builtins/src/temporal/common.rs index 10eb7b89..59b0f9c4 100644 --- a/crates/oxide_builtins/src/temporal/common.rs +++ b/crates/oxide_builtins/src/temporal/common.rs @@ -1,12 +1,17 @@ -//! Temporal 命名空间的最小实现子集:Temporal.Now / Temporal.Instant / -//! Temporal.PlainDate / Temporal.PlainTime / Temporal.PlainDateTime / -//! Temporal.PlainMonthDay / Temporal.PlainYearMonth / Temporal.ZonedDateTime。 -//! 内部数据按对象类型存入 prop 槽: -//! Instant 存纪元纳秒(BigInt,prop 0)、PlainDate 存年/月/日/日历 ID(prop 0-3)、 -//! PlainTime 存午夜后纳秒(f64,prop 0)、PlainDateTime 存年/月/日/午夜后纳秒/日历 ID(prop 0-4)、 -//! PlainMonthDay 存月/日/参考年/日历 ID(prop 0-3)、 -//! PlainYearMonth 存年/月/参考日/日历 ID(prop 0-3)、 -//! ZonedDateTime 存纪元纳秒、时区 ID、日历 ID(prop 0-2)。 +//! Temporal 命名空间对象的存储约定:各类型实例的载荷按固定 prop 槽(对象 +//! 内置属性表的下标位置)写入,槽外不设隐藏字段。 +//! +//! 槽位布局: +//! - Temporal.Instant:prop 0 存纪元纳秒(BigInt)。 +//! - Temporal.Duration:prop 0-9 依次存年、月、周、日、时、分、秒、毫秒、 +//! 微秒、纳秒(均为 f64)。 +//! - Temporal.PlainDate:prop 0-3 存年、月、日、日历 ID。 +//! - Temporal.PlainTime:prop 0 存午夜后纳秒(f64)。 +//! - Temporal.PlainDateTime:prop 0-4 存年、月、日、午夜后纳秒、日历 ID。 +//! - Temporal.PlainMonthDay:prop 0-3 存月、日、参考年、日历 ID。 +//! - Temporal.PlainYearMonth:prop 0-3 存年、月、参考日、日历 ID。 +//! - Temporal.ZonedDateTime:prop 0-2 存纪元纳秒、时区 ID、日历 ID。 +//! //! 日历 ID 未显式给定时统一取 "iso8601"。 use num_traits::ToPrimitive; @@ -220,12 +225,14 @@ pub(crate) fn temporal_string_strict(vm: &mut H, value: JsValue) -> R Ok(to_string(primitive)) } -/// RejectObjectWithCalendarOrTimeZone:非对象 / Temporal 实例 / calendar、timeZone 非 undefined -/// 均返回 TypeError(with 的 partial 参数校验)。 +/// 校验 `with` 族方法的 partial 参数并排除日历/时区载体。 +/// +/// 非对象、已初始化的 Temporal 日期时间类实例,或 calendar、timeZone 任一 +/// 非 undefined 的对象都抛 TypeError(RejectObjectWithCalendarOrTimeZone 语义)。 /// /// # 边界与前提 -/// - Temporal 内部槽检查先于 calendar/timeZone 的 Get(IsPartialTemporalObject 步骤序)。 -/// - 参数校验通过后调用方才能读字段。 +/// - 内部类型标签检查先于 calendar、timeZone 属性的读取,两步的先后不可交换。 +/// - 只有本函数返回成功,调用方才能继续读取 partial 的字段。 pub(crate) fn reject_partial_object_with_calendar_or_time_zone( vm: &mut H, value: JsValue, ) -> Result<(), JsValue> { diff --git a/crates/oxide_builtins/src/temporal/difference.rs b/crates/oxide_builtins/src/temporal/difference.rs index 8c56b046..acfe5ae6 100644 --- a/crates/oxide_builtins/src/temporal/difference.rs +++ b/crates/oxide_builtins/src/temporal/difference.rs @@ -345,7 +345,8 @@ pub(crate) fn nudge_window( (r1, r2, start, end) } -/// NudgeToCalendarUnit:日历单位用纪元纳秒边界取整。 +/// 对年/月/周单位的差值取整:把候选窗口端点换算成纪元纳秒,按目标时刻在窗口 +/// 中的相对位置决定取整方向(NudgeToCalendarUnit 语义)。 #[allow(clippy::too_many_arguments)] fn nudge_to_calendar_unit( sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128), time1_ns: i128, @@ -402,7 +403,8 @@ fn nudge_to_calendar_unit( Ok((duration, nudged, did_expand)) } -/// BubbleRelativeDuration:舍入越过小单位边界时向更大单位进位(到 largest 为止)。 +/// 舍入结果越过小单位边界时向更大的单位逐级进位,并把被进位单位的低位分量 +/// 清零,最多进位到 largest 指定的单位(BubbleRelativeDuration 语义)。 fn bubble_relative_duration( sign: i128, mut values: [f64; 10], nudged_epoch: i128, date1: (i128, i128, i128), time1_ns: i128, largest: usize, start_unit: usize, @@ -442,7 +444,8 @@ fn bubble_relative_duration( } end_dur[4..10].fill(0.0); let end_date = add_date_duration(date1, &end_dur); - // Bubble 边界只用于比较,不做 ISO 范围校验(对齐 bugzilla 2036259)。 + // 进位候选边界只参与"是否进位"的比较,其对应的日期不会被写入结果, + // 因此这里不调用 ISO 日期范围校验(范围校验只在结果日期上执行)。 let end_epoch = days_from_civil(end_date.0, end_date.1, end_date.2) * DAY_NS + time1_ns; let reached_end = if sign > 0 { nudged_epoch >= end_epoch } else { nudged_epoch <= end_epoch }; if reached_end { @@ -498,11 +501,21 @@ fn plain_year_month_unit_index(value: &str) -> Option { } } -/// 解析差值选项(对齐 GetDifferenceSettings):读取顺序 largestUnit → -/// roundingIncrement → roundingMode → smallestUnit。date_only 时单位限定 -/// year/month/week/day,smallestUnit 缺省 "day"(含时间时缺省 "nanosecond")。 -/// default_largest 为 largestUnit 缺省/auto 时相对 smallestUnit 的取小上限 -/// (PDT/PD 传 3=day,ZDT 传 4=hour)。 +/// 解析 `until` / `since` 的差值选项,产出舍入所需的单位层级、增量与模式 +/// (GetDifferenceSettings 语义)。 +/// +/// # 步骤 +/// 1. 依次读取 largestUnit、roundingIncrement、roundingMode、smallestUnit; +/// 读取顺序固定,靠前的取值错误先于靠后的抛出。 +/// 2. date_only 时单位限定 year/month/week/day,否则允许到时/分/秒等时间单位。 +/// 3. smallestUnit 缺省 "day"(含时间单位时缺省 "nanosecond")。 +/// 4. largestUnit 缺省或 "auto" 时取 min(default_largest, smallestUnit)。 +/// +/// # 边界与前提 +/// - default_largest 是 largestUnit 缺省/auto 时相对 smallestUnit 的取小上限: +/// PlainDateTime/PlainDate 传 3(day),ZonedDateTime 传 4(hour)。 +/// - options 非对象抛 TypeError;单位名非法、roundingIncrement 超出 +/// 1..=10^9,或 smallestUnit 大于 largestUnit 时抛 RangeError。 pub(crate) fn parse_difference_settings( vm: &mut H, options_value: JsValue, date_only: bool, default_largest: usize, ) -> Result { @@ -591,10 +604,12 @@ pub(crate) fn parse_difference_settings( }) } -/// 解析 PlainYearMonth 差值选项(对齐 GetDifferenceSettings,unitGroup=date): -/// 单位表仅 year/month(week/day/时间单位不合法);smallestUnit 缺省 "month", -/// largestUnit 缺省/auto 为 LargerOfTwo(year, smallest) = year。 -/// 读取顺序 largestUnit → roundingIncrement → roundingMode → smallestUnit(字典序)。 +/// 解析 PlainYearMonth 的 `until` / `since` 差值选项,沿用 GetDifferenceSettings +/// 的 unitGroup=date 规则。 +/// +/// 单位表仅含 year/month(week/day 与时间单位均不合法);smallestUnit 缺省 +/// "month",largestUnit 缺省或 "auto" 时取 year。读取顺序为 largestUnit、 +/// roundingIncrement、roundingMode、smallestUnit。 pub(crate) fn parse_year_month_difference_settings( vm: &mut H, options_value: JsValue, ) -> Result { @@ -689,7 +704,8 @@ pub(crate) fn difference_core( /// # 步骤 /// 1. 日期差按 largestUnit 分解(时间单位时 days 并入时间)。 /// 2. 不要求舍入(smallest=nanosecond 且 increment=1)时直接按最大单位拆回。 -/// 3. smallest 为 day/时间单位走 NudgeToDayOrTime;为 year/month/week 走 NudgeToCalendarUnit。 +/// 3. smallestUnit 为 day 或时间单位时按均匀纳秒取整(NudgeToDayOrTime), +/// 为 year/month/week 时按纪元纳秒窗口取整(NudgeToCalendarUnit)。 /// 4. since 用 NegateRoundingMode 舍入并在最后整体取反。 /// /// # 边界与前提 @@ -784,7 +800,8 @@ pub(crate) fn nudge_iso_difference( } values } else if smallest_index >= 3 { - // NudgeToDayOrTime:合并天与时间为总纳秒后按单位取整(day 为均匀单位,同样走此路径)。 + // 把天与时间合并成总纳秒后按单位取整;day 可折算为固定的 24 小时纳秒, + // 因此与时间单位走同一条路径(NudgeToDayOrTime 语义)。 let total_ns = time_ns + date_days * DAY_NS; let quantum = if smallest_index == 3 { DAY_NS * increment @@ -820,7 +837,8 @@ pub(crate) fn nudge_iso_difference( } values } else { - // NudgeToCalendarUnit:year/month/week 用纪元纳秒窗口取整。 + // year/month/week 长度随日历变化,无法折算成固定纳秒:改用纪元纳秒窗口 + // 取整,按目标时刻落在候选窗口内的相对位置决定取整方向(NudgeToCalendarUnit 语义)。 let (mut values, nudged_epoch, did_expand) = match nudge_to_calendar_unit( sign, &date_values, diff --git a/crates/oxide_builtins/src/temporal/duration.rs b/crates/oxide_builtins/src/temporal/duration.rs index 6c886e76..9c1581ef 100644 --- a/crates/oxide_builtins/src/temporal/duration.rs +++ b/crates/oxide_builtins/src/temporal/duration.rs @@ -822,7 +822,7 @@ fn duration_apply_to_relative( /// 1. one/two 各自 duration_like_values 归一(读序 one → two → options)。 /// 2. options.relativeTo 经 duration_relative_to_date 归一(含 ZDT/Instant/bag 分支)。 /// 3. 无 relativeTo 且无日历单位:按 duration_time_nanoseconds 直接比大小(fast path)。 -/// 4. 无 relativeTo 但含日历单位:RangeError(calendar-possibly-required 语义)。 +/// 4. 无 relativeTo 但含日历单位:抛 RangeError,日历单位无法脱离具体相对点折算。 /// 5. 有 relativeTo:每个 duration 应用相对点得 (date, time),先比日期再比时间(字典序)。 /// /// # 边界与前提 @@ -833,7 +833,8 @@ pub fn duration_compare(vm: &mut H, args: &[u8]) -> NativeResult { let two_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let one = native_try!(duration_like_values(vm, one_val)); let two = native_try!(duration_like_values(vm, two_val)); - // 全分量相等直接返回 0(含日历单位时也无需 relativeTo,对齐 instances-identical)。 + // 十个分量逐一相等时直接返回 0:相等时长无论是否含日历单位,应用任意相对点结果都相同, + // 无需读取 options.relativeTo。 if one == two { return NativeResult::Ok(JsValue::int(0)); } diff --git a/crates/oxide_builtins/src/temporal/instant.rs b/crates/oxide_builtins/src/temporal/instant.rs index 7f3430c0..17811d7e 100644 --- a/crates/oxide_builtins/src/temporal/instant.rs +++ b/crates/oxide_builtins/src/temporal/instant.rs @@ -506,7 +506,14 @@ fn instant_round_unit(value: &str) -> Option<(i128, i128)> { } } -/// 按 Temporal 的 `RoundNumberToIncrementAsIfPositive` 语义舍入纳秒。 +/// 把纳秒值舍入到 increment 的整数倍。 +/// +/// 余数恒按非负处理,负值也按正向增长选取取整边界(RoundNumberToIncrementAsIfPositive +/// 语义);半值模式按余数与半增量的大小关系决定方向,halfEven 例外地看商的奇偶。 +/// +/// # 边界与前提 +/// - increment 必须为正,否则除零。 +/// - 舍入结果超出 i128 表示范围时返回 None。 pub(crate) fn round_instant_ns(value: i128, increment: i128, mode: InstantRoundingMode) -> Option { let quotient = value.div_euclid(increment); let remainder = value.rem_euclid(increment);