docs: 改写 Temporal 共享与差量层注释

- 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 族为中文语义加括注
This commit is contained in:
2026-09-17 12:59:34 +00:00
parent d22c5dfcdb
commit 5a0fc98aa1
4 changed files with 64 additions and 31 deletions
+20 -13
View File
@@ -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<H: VmHost>(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<H: VmHost>(
vm: &mut H, value: JsValue,
) -> Result<(), JsValue> {
@@ -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<usize> {
}
}
/// 解析差值选项(对齐 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<H: VmHost>(
vm: &mut H, options_value: JsValue, date_only: bool, default_largest: usize,
) -> Result<DifferenceSettings, JsValue> {
@@ -591,10 +604,12 @@ pub(crate) fn parse_difference_settings<H: VmHost>(
})
}
/// 解析 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<H: VmHost>(
vm: &mut H, options_value: JsValue,
) -> Result<DifferenceSettings, JsValue> {
@@ -689,7 +704,8 @@ pub(crate) fn difference_core<H: VmHost>(
/// # 步骤
/// 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<H: VmHost>(
}
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<H: VmHost>(
}
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,
@@ -822,7 +822,7 @@ fn duration_apply_to_relative<H: VmHost>(
/// 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<H: VmHost>(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));
}
@@ -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<i128> {
let quotient = value.div_euclid(increment);
let remainder = value.rem_euclid(increment);