mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 04:01:11 +08:00
refactor: temporal 与 json 长参数收进结构体
This commit is contained in:
@@ -86,6 +86,21 @@ pub(crate) struct InternalDuration {
|
||||
pub(crate) time_ns: i128,
|
||||
}
|
||||
|
||||
/// ISO 日期时间:日期三元(年/月/日)加时间纳秒(不含日分量)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct IsoDateTime {
|
||||
pub(crate) date: (i128, i128, i128),
|
||||
pub(crate) time_ns: i128,
|
||||
}
|
||||
|
||||
/// 取整规格:增量、单位与舍入模式(吸收取整三参)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct RoundingSpec {
|
||||
pub(crate) increment: i128,
|
||||
pub(crate) unit: usize,
|
||||
pub(crate) mode: InstantRoundingMode,
|
||||
}
|
||||
|
||||
impl InternalDuration {
|
||||
/// 转十分量数组:时间部分按向零截断分解为时/分/秒/毫秒/微秒/纳秒(与时长同号)。
|
||||
pub(crate) fn to_values(&self) -> [f64; 10] {
|
||||
@@ -394,11 +409,11 @@ pub(crate) struct NudgeCalendarResult {
|
||||
///
|
||||
/// `epoch_of_date` 是窗口端点日期的纪元换算闭包:plain 面传墙钟换算(行为逐字节
|
||||
/// 不变),zoned 面传 GetEpochNanosecondsFor(compatible) 换算。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn nudge_to_calendar_unit(
|
||||
sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128), increment: i128,
|
||||
unit: usize, mode: InstantRoundingMode, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>,
|
||||
sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128),
|
||||
spec: RoundingSpec, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>,
|
||||
) -> Result<NudgeCalendarResult, ()> {
|
||||
let RoundingSpec { increment, unit, mode } = spec;
|
||||
let epoch_of = |dur: &[f64; 10]| -> Result<i128, ()> {
|
||||
if date_duration_sign(dur) == 0 {
|
||||
return Ok(origin_epoch);
|
||||
@@ -744,12 +759,10 @@ pub(crate) fn parse_year_month_difference_settings<H: VmHost>(
|
||||
/// 的舍入模式并在最后整体取反(不调换两端,调换会改变 0.5 边界所在的年长)。
|
||||
/// no_rounding = 单位索引空间中"最细单位"的索引:该单位且 increment=1 时
|
||||
/// 舍入为恒等变换,跳过 nudge(边界处避免窗口端点的无效范围抛错)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_core<H: VmHost>(
|
||||
vm: &mut H, start: (i128, i128, i128), start_time_ns: i128, end: (i128, i128, i128), end_time_ns: i128,
|
||||
settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
) -> NativeResult {
|
||||
let values = match nudge_iso_difference(vm, start, start_time_ns, end, end_time_ns, settings, since, no_rounding) {
|
||||
let values = match nudge_iso_difference(vm, start, end, settings, since, no_rounding) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
@@ -769,10 +782,8 @@ pub(crate) fn difference_core<H: VmHost>(
|
||||
/// - Duration.round 的日历单位路径与本函数共用:round 把 duration 应用 relativeTo 后
|
||||
/// 以同样的 start/end 差输入本函数,输出即 balance 后的舍入结果。
|
||||
/// - 时间分量为带符号 i128;start/end 日与时间各自独立,符号由差值推导。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
vm: &mut H, start: (i128, i128, i128), start_time_ns: i128, end: (i128, i128, i128), end_time_ns: i128,
|
||||
settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
) -> Result<[f64; 10], JsValue> {
|
||||
let DifferenceSettings {
|
||||
largest_index,
|
||||
@@ -794,17 +805,17 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
|
||||
// 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。
|
||||
// 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。
|
||||
let origin_epoch = days_from_civil(start.0, start.1, start.2) * DAY_NS + start_time_ns;
|
||||
let dest_epoch = days_from_civil(end.0, end.1, end.2) * DAY_NS + end_time_ns;
|
||||
let origin_epoch = days_from_civil(start.date.0, start.date.1, start.date.2) * DAY_NS + start.time_ns;
|
||||
let dest_epoch = days_from_civil(end.date.0, end.date.1, end.date.2) * DAY_NS + end.time_ns;
|
||||
|
||||
let date1 = start;
|
||||
let time1_ns = start_time_ns;
|
||||
let date1 = start.date;
|
||||
let time1_ns = start.time_ns;
|
||||
// plain 面纪元换算闭包:墙钟(日 × 24h + 时间),行为与旧内联算法逐字节一致。
|
||||
let epoch_of_date = |date: (i128, i128, i128)| -> Result<i128, ()> {
|
||||
Ok(days_from_civil(date.0, date.1, date.2) * DAY_NS + time1_ns)
|
||||
};
|
||||
let mut date2 = end;
|
||||
let mut time_ns = end_time_ns - time1_ns;
|
||||
let mut date2 = end.date;
|
||||
let mut time_ns = end.time_ns - time1_ns;
|
||||
let time_sign = if time_ns > 0 {
|
||||
1_i128
|
||||
} else if time_ns < 0 {
|
||||
@@ -906,9 +917,11 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
origin_epoch,
|
||||
dest_epoch,
|
||||
date1,
|
||||
increment,
|
||||
smallest_index,
|
||||
mode,
|
||||
RoundingSpec {
|
||||
increment,
|
||||
unit: smallest_index,
|
||||
mode,
|
||||
},
|
||||
&epoch_of_date,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
|
||||
@@ -15,14 +15,14 @@ use super::common::{
|
||||
use super::difference::{
|
||||
add_date_duration, add_days_iso, balance_instant_difference, compare_iso_date, date_duration_sign, date_until_iso,
|
||||
nudge_iso_difference, nudge_window, plain_date_time_unit_index, round_instant_difference, DifferenceSettings,
|
||||
InternalDuration, DAY_NS, MAX_ISO_DAY,
|
||||
IsoDateTime, InternalDuration, DAY_NS, MAX_ISO_DAY,
|
||||
};
|
||||
use super::instant::{instant_rounding_mode, InstantRoundingMode};
|
||||
use super::time_zone::zone_offset_seconds;
|
||||
use super::zoned_date_time::{
|
||||
add_zoned_datetime, difference_zoned_datetime_with_rounding_vm, difference_zoned_datetime_with_total_vm,
|
||||
disambiguate_possible_epoch_nanoseconds, extract_time_zone_annotation, get_possible_epoch_nanoseconds,
|
||||
map_add_zoned_error, parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts,
|
||||
map_add_zoned_error, parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts, ZonedPair,
|
||||
};
|
||||
use super::{make_duration, parse_plain_date_time_string};
|
||||
|
||||
@@ -708,9 +708,13 @@ pub fn duration_total<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithTotal(门与单位无关)。
|
||||
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date {
|
||||
let target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
|
||||
let total = native_try!(difference_zoned_datetime_with_total_vm(
|
||||
vm, *epoch_ns, target, time_zone, calendar, unit_index
|
||||
));
|
||||
let pair = ZonedPair {
|
||||
ns1: *epoch_ns,
|
||||
ns2: target,
|
||||
time_zone,
|
||||
calendar,
|
||||
};
|
||||
let total = native_try!(difference_zoned_datetime_with_total_vm(vm, pair, unit_index));
|
||||
return NativeResult::Ok(JsValue::float(total));
|
||||
}
|
||||
|
||||
@@ -1369,17 +1373,19 @@ pub fn duration_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithRounding(门与单位无关)。
|
||||
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date {
|
||||
let target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
|
||||
let internal = native_try!(difference_zoned_datetime_with_rounding_vm(
|
||||
vm,
|
||||
*epoch_ns,
|
||||
target,
|
||||
let pair = ZonedPair {
|
||||
ns1: *epoch_ns,
|
||||
ns2: target,
|
||||
time_zone,
|
||||
calendar,
|
||||
largest,
|
||||
increment,
|
||||
};
|
||||
let settings = DifferenceSettings {
|
||||
largest_index: largest,
|
||||
smallest_index,
|
||||
increment,
|
||||
mode,
|
||||
));
|
||||
};
|
||||
let internal = native_try!(difference_zoned_datetime_with_rounding_vm(vm, pair, settings));
|
||||
// largestUnit 为日期类时降为 hour。
|
||||
let largest = if largest <= 3 { 4 } else { largest };
|
||||
let result = internal_duration_to_values(&internal, largest);
|
||||
@@ -1460,7 +1466,15 @@ pub fn duration_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
increment,
|
||||
mode,
|
||||
};
|
||||
let result = match nudge_iso_difference(vm, rel, 0, end, target_time, settings, false, 9) {
|
||||
let start = IsoDateTime {
|
||||
date: rel,
|
||||
time_ns: 0,
|
||||
};
|
||||
let end_dt = IsoDateTime {
|
||||
date: end,
|
||||
time_ns: target_time,
|
||||
};
|
||||
let result = match nudge_iso_difference(vm, start, end_dt, settings, false, 9) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::common::{
|
||||
is_ctor_call, parse_plain_date_string, plain_date_time_object_parts, receiver_obj, temporal_calendar_id_strict,
|
||||
temporal_number_component, temporal_overflow, zoned_date_time_plain_parts,
|
||||
};
|
||||
use super::difference::{difference_core, parse_difference_settings};
|
||||
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
||||
use super::duration::duration_like_values;
|
||||
use super::make_plain_date;
|
||||
|
||||
@@ -623,10 +623,14 @@ pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
0,
|
||||
(i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
0,
|
||||
IsoDateTime {
|
||||
date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
false,
|
||||
9,
|
||||
@@ -651,10 +655,14 @@ pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
0,
|
||||
(i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
0,
|
||||
IsoDateTime {
|
||||
date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
true,
|
||||
9,
|
||||
|
||||
@@ -14,7 +14,7 @@ use super::{
|
||||
parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, receiver_obj, round_instant_ns,
|
||||
temporal_calendar_id_strict, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, valid_iso_date, valid_plain_date_time_range, valid_plain_time, zoned_date_time_plain_parts,
|
||||
FractionalSecondDigitsInput,
|
||||
FractionalSecondDigitsInput, IsoDateTime,
|
||||
};
|
||||
// ───────────────────── PlainDateTime 基础方法 ─────────────────────
|
||||
|
||||
@@ -589,10 +589,14 @@ fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(sy), i128::from(sm), i128::from(sd)),
|
||||
st as i128,
|
||||
(i128::from(oy), i128::from(om), i128::from(od)),
|
||||
ot as i128,
|
||||
IsoDateTime {
|
||||
date: (i128::from(sy), i128::from(sm), i128::from(sd)),
|
||||
time_ns: st as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(oy), i128::from(om), i128::from(od)),
|
||||
time_ns: ot as i128,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
make_plain_time, parse_any_offset_seconds, parse_difference_settings, parse_fractional_second_digits,
|
||||
parse_iso_date, receiver_obj, reject_partial_object_with_calendar_or_time_zone, round_instant_ns,
|
||||
temporal_number_component, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, valid_offset_fraction, zoned_date_time_plain_parts, FractionalSecondDigitsInput,
|
||||
temporal_overflow, valid_offset_fraction, zoned_date_time_plain_parts, FractionalSecondDigitsInput, IsoDateTime,
|
||||
};
|
||||
|
||||
/// 时间串的日期歧义判定:形如 YYYY-MM / MMDD / YYYYMM / MM-DD 且作为日期合法 → 歧义。
|
||||
@@ -701,7 +701,20 @@ fn plain_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -> Nat
|
||||
Ok(settings) => settings,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
difference_core(vm, (0, 0, 0), receiver_ns as i128, (0, 0, 0), other_ns as i128, settings, since, 9)
|
||||
difference_core(
|
||||
vm,
|
||||
IsoDateTime {
|
||||
date: (0, 0, 0),
|
||||
time_ns: receiver_ns as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (0, 0, 0),
|
||||
time_ns: other_ns as i128,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainTime.prototype.until(other, options)`。
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::{
|
||||
read_iso2, receiver_obj, reject_partial_object_with_calendar_or_time_zone, temporal_constructor_calendar_id,
|
||||
temporal_number_component, temporal_option_string, temporal_option_value, temporal_overflow,
|
||||
temporal_to_show_calendar, validate_month_day_annotation_suffix, validate_month_day_time_suffix, ShowCalendar,
|
||||
MAX_ISO_DAY,
|
||||
IsoDateTime, MAX_ISO_DAY,
|
||||
};
|
||||
|
||||
/// 读 PlainYearMonth 的 年/月/参考日 三元组(槽 0-2),含 receiver 校验。
|
||||
@@ -884,5 +884,18 @@ fn plain_year_month_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool)
|
||||
{
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "date is out of range"));
|
||||
}
|
||||
difference_core(vm, (ry, rm, 1), 0, (i128::from(oy), i128::from(om), 1), 0, settings, since, 1)
|
||||
difference_core(
|
||||
vm,
|
||||
IsoDateTime {
|
||||
date: (ry, rm, 1),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(oy), i128::from(om), 1),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -104,20 +104,79 @@ fn format_time_zone_annotation_normalizes_offset() {
|
||||
#[test]
|
||||
fn format_zoned_date_time_iso_annotations() {
|
||||
// 偏移/时区/日历注解各取值组合,验证段序 `{offset}[{tz}][{ca}]` 与 critical 前缀。
|
||||
let base = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "auto");
|
||||
let base = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(base, Some("1970-01-01T01:00:00+01:00[+01:00]".to_string()));
|
||||
// offset never 省略偏移段,时区注解仍显示。
|
||||
let no_offset = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "never", "auto", "auto");
|
||||
let no_offset = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "never",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(no_offset, Some("1970-01-01T01:00:00[+01:00]".to_string()));
|
||||
// calendarName always 追加日历注解。
|
||||
let ca_always = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "always");
|
||||
let ca_always = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "always",
|
||||
},
|
||||
);
|
||||
assert_eq!(ca_always, Some("1970-01-01T01:00:00+01:00[+01:00][u-ca=iso8601]".to_string()));
|
||||
// timeZoneName/calendarName critical 均 `!` 置于括号内。
|
||||
let critical = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "critical", "critical");
|
||||
let critical = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "critical",
|
||||
calendar_name: "critical",
|
||||
},
|
||||
);
|
||||
assert_eq!(critical, Some("1970-01-01T01:00:00+01:00[!+01:00][!u-ca=iso8601]".to_string()));
|
||||
// offset critical 偏移段前加 !。
|
||||
let offset_critical =
|
||||
format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "critical", "auto", "auto");
|
||||
let offset_critical = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "critical",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(offset_critical, Some("1970-01-01T01:00:00!+01:00[+01:00]".to_string()));
|
||||
}
|
||||
|
||||
@@ -125,7 +184,19 @@ fn format_zoned_date_time_iso_annotations() {
|
||||
fn format_zoned_date_time_iso_epoch_rounding_cross_midnight() {
|
||||
// 2000-01-01 前 1ns 已舍入到 2000-01-01 整点,8 位小数输出。
|
||||
let rounded = round_instant_ns(946_684_799_999_999_999, 100, InstantRoundingMode::HalfExpand).unwrap();
|
||||
let output = format_zoned_date_time_iso(rounded, 0, "UTC", "iso8601", true, Some(8), "auto", "auto", "auto");
|
||||
let output = format_zoned_date_time_iso(
|
||||
rounded,
|
||||
0,
|
||||
"UTC",
|
||||
"iso8601",
|
||||
true,
|
||||
Some(8),
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(output, Some("2000-01-01T00:00:00.00000000+00:00[UTC]".to_string()));
|
||||
}
|
||||
|
||||
@@ -429,7 +500,19 @@ fn sub_minute_offset_getter_branch() {
|
||||
assert_eq!(format_offset_seconds_text(2670), "+00:44:30");
|
||||
assert_eq!(format_offset_seconds_text(3600), "+01:00");
|
||||
assert_eq!(format_time_zone_annotation("-00:44:30", false), "[-00:44:30]");
|
||||
let iso = format_zoned_date_time_iso(0, -2670, "-00:44:30", "iso8601", true, None, "auto", "auto", "auto");
|
||||
let iso = format_zoned_date_time_iso(
|
||||
0,
|
||||
-2670,
|
||||
"-00:44:30",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(iso, Some("1969-12-31T23:15:30-00:44:30[-00:44:30]".to_string()));
|
||||
assert_eq!(
|
||||
format_instant_iso(0, Some(-2670), true, None),
|
||||
@@ -614,10 +697,13 @@ fn calendar_date_add_iso_constrain_semantics() {
|
||||
use super::zoned_date_time::{calendar_date_add_iso, AddZonedError};
|
||||
|
||||
// 月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange。
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), 0, 1, 0, 0, true), Ok((2025, 2, 28)));
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), 0, 1, 0, 0, false), Err(AddZonedError::DayOutOfRange));
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), [0, 1, 0, 0], true), Ok((2025, 2, 28)));
|
||||
assert_eq!(
|
||||
calendar_date_add_iso((2025, 1, 31), [0, 1, 0, 0], false),
|
||||
Err(AddZonedError::DayOutOfRange)
|
||||
);
|
||||
// 闰年二月:constrain 钳到 29。
|
||||
assert_eq!(calendar_date_add_iso((2024, 1, 31), 0, 1, 0, 0, true), Ok((2024, 2, 29)));
|
||||
assert_eq!(calendar_date_add_iso((2024, 1, 31), [0, 1, 0, 0], true), Ok((2024, 2, 29)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -627,25 +713,61 @@ fn zoned_difference_day_correction() {
|
||||
// 同日纯时间差:无日期部分,时间部分为纳秒差。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 6, 15), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 6, 15), 66_600_000_000_000).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 0.0]);
|
||||
assert_eq!(d.time_ns, 23_400_000_000_000);
|
||||
|
||||
// 跨 DST 间隙日(23 小时):墙历时刻相同,日期部分 1 天、时间部分 0。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 43_200_000_000_000).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 1.0]);
|
||||
assert_eq!(d.time_ns, 0);
|
||||
|
||||
// dayCorrection 循环推进:12 小时跨间隙日,日期部分 0、时间部分 12 小时。
|
||||
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2b, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2: ns2b,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 0.0]);
|
||||
assert_eq!(d.time_ns, 43_200_000_000_000);
|
||||
|
||||
// 反向(sign 为正):日期部分为负一天、时间部分 0。
|
||||
let d = difference_zoned_datetime(ns2, ns1, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1: ns2,
|
||||
ns2: ns1,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, -1.0]);
|
||||
assert_eq!(d.time_ns, 0);
|
||||
}
|
||||
@@ -657,12 +779,30 @@ fn zoned_total_dst_fraction() {
|
||||
// 整日差(23 小时间隙日):总量 1。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 43_200_000_000_000).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert!((total - 1.0).abs() < 1e-12);
|
||||
|
||||
// 12 小时跨 23 小时间隙日:总量 12/23(按实际日长折算,非 24 小时)。
|
||||
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(ns1, ns2b, "America/New_York", "iso8601", 3).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2: ns2b,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert!((total - 12.0 / 23.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
@@ -681,14 +821,18 @@ fn zoned_round_nudge_to_zoned_time() {
|
||||
let p25h = [0.0_f64, 0.0, 0.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
||||
let target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p25h, true).unwrap();
|
||||
let internal = difference_zoned_datetime_with_rounding(
|
||||
origin,
|
||||
target,
|
||||
"Pacific/Apia",
|
||||
"iso8601",
|
||||
4,
|
||||
1,
|
||||
4,
|
||||
InstantRoundingMode::Trunc,
|
||||
ZonedPair {
|
||||
ns1: origin,
|
||||
ns2: target,
|
||||
time_zone: "Pacific/Apia",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
DifferenceSettings {
|
||||
largest_index: 4,
|
||||
smallest_index: 4,
|
||||
increment: 1,
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let values = internal_duration_to_values(&internal, 4);
|
||||
@@ -698,14 +842,18 @@ fn zoned_round_nudge_to_zoned_time() {
|
||||
let p1dt26h = [0.0_f64, 0.0, 0.0, 1.0, 26.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
||||
let target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p1dt26h, true).unwrap();
|
||||
let internal = difference_zoned_datetime_with_rounding(
|
||||
origin,
|
||||
target,
|
||||
"Pacific/Apia",
|
||||
"iso8601",
|
||||
3,
|
||||
1,
|
||||
4,
|
||||
InstantRoundingMode::Trunc,
|
||||
ZonedPair {
|
||||
ns1: origin,
|
||||
ns2: target,
|
||||
time_zone: "Pacific/Apia",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
DifferenceSettings {
|
||||
largest_index: 3,
|
||||
smallest_index: 4,
|
||||
increment: 1,
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let values = internal_duration_to_values(&internal, 4);
|
||||
|
||||
@@ -19,7 +19,7 @@ use super::common::{
|
||||
use super::difference::{
|
||||
add_days_iso, bubble_relative_duration, compare_iso_date, date_duration_sign, date_until_iso, difference_core,
|
||||
internal_duration_sign, nudge_to_calendar_unit, parse_difference_settings, round_instant_difference,
|
||||
InternalDuration, DAY_NS, MAX_ISO_DAY, TIME_UNIT_NS,
|
||||
DifferenceSettings, IsoDateTime, InternalDuration, RoundingSpec, DAY_NS, MAX_ISO_DAY, TIME_UNIT_NS,
|
||||
};
|
||||
use super::instant::{
|
||||
instant_rounding_mode, instant_string_without_annotations, parse_instant_string, primitive_to_bigint,
|
||||
@@ -533,17 +533,27 @@ pub(crate) fn format_time_zone_annotation(time_zone_id: &str, critical: bool) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// toString 注解名选项:offset / timeZoneName / calendarName 三取值。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct FormatNames<'a> {
|
||||
pub(crate) offset_name: &'a str,
|
||||
pub(crate) time_zone_name: &'a str,
|
||||
pub(crate) calendar_name: &'a str,
|
||||
}
|
||||
|
||||
/// ZDT 字符串化核心:epoch 域已舍入后按偏移与注解选项拼 `{date}T{time}.fff{offset}[{tz}][{ca}]`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - epoch_ns 须已按 quantum 舍入(调用方保证),此处仅做本地墙钟分解。
|
||||
/// - offset_name/time_zone_name 取 auto/never/critical,calendar_name 取 auto/never/always/critical。
|
||||
/// - 偏移段 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);时区注解命名区原样、偏移区规范化。
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn format_zoned_date_time_iso(
|
||||
epoch_ns: i128, offset_seconds: i64, time_zone_id: &str, calendar_id: &str, include_seconds: bool,
|
||||
output_digits: Option<usize>, offset_name: &str, time_zone_name: &str, calendar_name: &str,
|
||||
output_digits: Option<usize>, names: FormatNames,
|
||||
) -> Option<String> {
|
||||
let offset_name = names.offset_name;
|
||||
let time_zone_name = names.time_zone_name;
|
||||
let calendar_name = names.calendar_name;
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
let offset_ns = i128::from(offset_seconds).checked_mul(1_000_000_000)?;
|
||||
let local_ns = epoch_ns.checked_add(offset_ns)?;
|
||||
@@ -716,9 +726,11 @@ pub fn zoned_date_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
|
||||
&calendar_id,
|
||||
include_seconds,
|
||||
output_digits,
|
||||
&offset_name,
|
||||
&time_zone_name,
|
||||
&calendar_name,
|
||||
FormatNames {
|
||||
offset_name: &offset_name,
|
||||
time_zone_name: &time_zone_name,
|
||||
calendar_name: &calendar_name,
|
||||
},
|
||||
) {
|
||||
Some(output) => NativeResult::Ok(vm.new_string_owned(output)),
|
||||
None => NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime")),
|
||||
@@ -1350,10 +1362,14 @@ fn zoned_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
||||
let settings = native_try!(parse_difference_settings(vm, options_value, false, 4));
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(y_r), i128::from(m_r), i128::from(d_r)),
|
||||
time_ns_r as i128,
|
||||
(y_o, m_o, d_o),
|
||||
time_ns_o,
|
||||
IsoDateTime {
|
||||
date: (i128::from(y_r), i128::from(m_r), i128::from(d_r)),
|
||||
time_ns: time_ns_r as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (y_o, m_o, d_o),
|
||||
time_ns: time_ns_o,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
@@ -1710,14 +1726,15 @@ pub(crate) fn add_instant(epoch_ns: i128, time_ns: i128) -> Result<i128, AddZone
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日。
|
||||
/// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日(values 依次年/月/周/日)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 年/月推进(月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange)。
|
||||
/// 2. 周*7 + 日推进。
|
||||
pub(crate) fn calendar_date_add_iso(
|
||||
date: (i128, i128, i128), years: i128, months: i128, weeks: i128, days: i128, constrain: bool,
|
||||
date: (i128, i128, i128), values: [i128; 4], constrain: bool,
|
||||
) -> Result<(i128, i128, i128), AddZonedError> {
|
||||
let (years, months, weeks, days) = (values[0], values[1], values[2], values[3]);
|
||||
let total = date.0 * 12 + (date.1 - 1) + years * 12 + months;
|
||||
let ny = total.div_euclid(12);
|
||||
let nm = total.rem_euclid(12) + 1;
|
||||
@@ -1837,17 +1854,16 @@ pub(crate) fn add_zoned_datetime_pure(
|
||||
return add_instant(epoch_ns, time_ns);
|
||||
}
|
||||
// GetISODateTimeFor:墙历分量。
|
||||
let ((year, month, day), time_ns) = get_iso_date_time_for(time_zone, epoch_ns)?;
|
||||
let iso = get_iso_date_time_for(time_zone, epoch_ns)?;
|
||||
let (year, month, day) = iso.date;
|
||||
let time_ns = iso.time_ns;
|
||||
if !valid_plain_date_time_range(year as i32, month as u32, day as u32, time_ns as f64) {
|
||||
return Err(AddZonedError::OutOfDateTimeRange);
|
||||
}
|
||||
// CalendarDateAdd:日期分量(constrain 钳月末,reject 抛错)。
|
||||
let added = calendar_date_add_iso(
|
||||
(year, month, day),
|
||||
values[0] as i128,
|
||||
values[1] as i128,
|
||||
values[2] as i128,
|
||||
values[3] as i128,
|
||||
[values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
|
||||
constrain,
|
||||
)?;
|
||||
// 中间日期检查(ISODateTimeWithinLimits)。
|
||||
@@ -1887,20 +1903,18 @@ pub(crate) fn map_add_zoned_error<H: VmHost>(vm: &mut H, error: AddZonedError) -
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTimeWithRounding 的 VM 包装:错误映射为 RangeError。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_zoned_datetime_with_rounding_vm<H: VmHost>(
|
||||
vm: &mut H, ns1: i128, ns2: i128, time_zone: &str, calendar: &str, largest_unit: usize, increment: i128,
|
||||
smallest_unit: usize, mode: InstantRoundingMode,
|
||||
vm: &mut H, pair: ZonedPair, settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, JsValue> {
|
||||
difference_zoned_datetime_with_rounding(ns1, ns2, time_zone, calendar, largest_unit, increment, smallest_unit, mode)
|
||||
difference_zoned_datetime_with_rounding(pair, settings)
|
||||
.map_err(|error| map_add_zoned_error(vm, error))
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTimeWithTotal 的 VM 包装:错误映射为 RangeError。
|
||||
pub(crate) fn difference_zoned_datetime_with_total_vm<H: VmHost>(
|
||||
vm: &mut H, ns1: i128, ns2: i128, time_zone: &str, calendar: &str, unit: usize,
|
||||
vm: &mut H, pair: ZonedPair, unit: usize,
|
||||
) -> Result<f64, JsValue> {
|
||||
difference_zoned_datetime_with_total(ns1, ns2, time_zone, calendar, unit)
|
||||
difference_zoned_datetime_with_total(pair, unit)
|
||||
.map_err(|error| map_add_zoned_error(vm, error))
|
||||
}
|
||||
|
||||
@@ -1912,16 +1926,17 @@ pub(crate) fn difference_zoned_datetime_with_total_vm<H: VmHost>(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 时区名非法(不在区表且非固定偏移串)返回 InvalidTimeZone。
|
||||
pub(crate) fn get_iso_date_time_for(
|
||||
time_zone: &str, epoch_ns: i128,
|
||||
) -> Result<((i128, i128, i128), i128), AddZonedError> {
|
||||
pub(crate) fn get_iso_date_time_for(time_zone: &str, epoch_ns: i128) -> Result<IsoDateTime, AddZonedError> {
|
||||
let offset_seconds = zone_offset_seconds(time_zone, epoch_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or(AddZonedError::InvalidTimeZone)?;
|
||||
let local_ns = epoch_ns + i128::from(offset_seconds) * 1_000_000_000;
|
||||
let days = local_ns.div_euclid(DAY_NS);
|
||||
let time_ns = local_ns.rem_euclid(DAY_NS);
|
||||
let (year, month, day) = civil_from_days(days);
|
||||
Ok(((year, month, day), time_ns))
|
||||
Ok(IsoDateTime {
|
||||
date: (year, month, day),
|
||||
time_ns,
|
||||
})
|
||||
}
|
||||
|
||||
/// GetEpochNanosecondsFor(规范 11.1.11,compatible 消歧):墙历日期时间的纪元。
|
||||
@@ -1959,6 +1974,17 @@ fn time_duration_sign(ns: i128) -> i128 {
|
||||
}
|
||||
}
|
||||
|
||||
/// zoned 差值端点对:两个 epoch 加时区与日历(日历透传,引擎仅 iso8601)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ZonedPair<'a> {
|
||||
pub(crate) ns1: i128,
|
||||
pub(crate) ns2: i128,
|
||||
pub(crate) time_zone: &'a str,
|
||||
// 透传字段:引擎仅 iso8601 日历,差值计算不消费,保留以对齐规范签名。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) calendar: &'a str,
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTime(规范 6.5.6):两个 epoch 的差,平衡后结果中无大于
|
||||
/// largestUnit 的非零单位,计入日历 reckoning 与时区偏移变化。
|
||||
///
|
||||
@@ -1972,13 +1998,20 @@ fn time_duration_sign(ns: i128) -> i128 {
|
||||
/// - 全程 i128 纳秒;中间点时间取起点墙历时间(规范 (intermediateDate, startDT.Time))。
|
||||
/// - calendar 参数透传(引擎仅 iso8601 日历)。
|
||||
pub(crate) fn difference_zoned_datetime(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, largest_unit: usize,
|
||||
pair: ZonedPair, largest_unit: usize,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
if ns1 == ns2 {
|
||||
return Ok(InternalDuration { date: [0.0; 4], time_ns: 0 });
|
||||
}
|
||||
let (start_date, start_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
let (end_date, end_time) = get_iso_date_time_for(time_zone, ns2)?;
|
||||
let start = get_iso_date_time_for(time_zone, ns1)?;
|
||||
let end = get_iso_date_time_for(time_zone, ns2)?;
|
||||
let start_date = start.date;
|
||||
let start_time = start.time_ns;
|
||||
let end_date = end.date;
|
||||
let end_time = end.time_ns;
|
||||
if compare_iso_date(start_date, end_date) == 0 {
|
||||
return Ok(InternalDuration {
|
||||
date: [0.0; 4],
|
||||
@@ -2023,11 +2056,16 @@ pub(crate) fn difference_zoned_datetime(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - smallestUnit 不大于 largestUnit(调用方已校验)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, largest_unit: usize, increment: i128, smallest_unit: usize,
|
||||
mode: InstantRoundingMode,
|
||||
pair: ZonedPair, settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
let largest_unit = settings.largest_index;
|
||||
let increment = settings.increment;
|
||||
let smallest_unit = settings.smallest_index;
|
||||
let mode = settings.mode;
|
||||
if largest_unit >= 4 {
|
||||
let quantum = TIME_UNIT_NS[smallest_unit - 4]
|
||||
.checked_mul(increment)
|
||||
@@ -2038,22 +2076,12 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
time_ns: rounded,
|
||||
});
|
||||
}
|
||||
let difference = difference_zoned_datetime(ns1, ns2, time_zone, _calendar, largest_unit)?;
|
||||
let difference = difference_zoned_datetime(pair, largest_unit)?;
|
||||
if smallest_unit == 9 && increment == 1 {
|
||||
return Ok(difference);
|
||||
}
|
||||
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
round_relative_duration_zoned(
|
||||
difference,
|
||||
ns1,
|
||||
ns2,
|
||||
(iso_date, iso_time),
|
||||
time_zone,
|
||||
largest_unit,
|
||||
increment,
|
||||
smallest_unit,
|
||||
mode,
|
||||
)
|
||||
let iso = get_iso_date_time_for(time_zone, ns1)?;
|
||||
round_relative_duration_zoned(difference, ns1, ns2, iso, time_zone, settings)
|
||||
}
|
||||
|
||||
/// RoundRelativeDuration(规范 7.5.38)的 zoned 路由:日历单位(或 day)走日历
|
||||
@@ -2063,33 +2091,32 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
/// 1. 路由:smallestUnit 为日历单位或 day → NudgeToCalendarUnit(zoned 闭包);
|
||||
/// 否则 NudgeToZonedTime。
|
||||
/// 2. 扩窗且 smallestUnit 非 week → BubbleRelativeDuration(startUnit 取 smallestUnit 与 day 的较大者)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn round_relative_duration_zoned(
|
||||
difference: InternalDuration, origin_ns: i128, dest_ns: i128, iso: ((i128, i128, i128), i128), time_zone: &str,
|
||||
largest_unit: usize, increment: i128, smallest_unit: usize, mode: InstantRoundingMode,
|
||||
difference: InternalDuration, origin_ns: i128, dest_ns: i128, iso: IsoDateTime, time_zone: &str,
|
||||
settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let largest_unit = settings.largest_index;
|
||||
let increment = settings.increment;
|
||||
let smallest_unit = settings.smallest_index;
|
||||
let mode = settings.mode;
|
||||
let values = difference.to_values();
|
||||
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 };
|
||||
let (iso_date, iso_time) = iso;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
|
||||
// zoned 面时区恒在场:irregular = smallestUnit 为日历单位或 day。
|
||||
let irregular = smallest_unit <= 3;
|
||||
let spec = RoundingSpec {
|
||||
increment,
|
||||
unit: smallest_unit,
|
||||
mode,
|
||||
};
|
||||
let (mut result, nudged_epoch, did_expand) = if irregular {
|
||||
let outcome = nudge_to_calendar_unit(
|
||||
sign,
|
||||
&values,
|
||||
origin_ns,
|
||||
dest_ns,
|
||||
iso_date,
|
||||
increment,
|
||||
smallest_unit,
|
||||
mode,
|
||||
&epoch_of_date,
|
||||
)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
(outcome.duration, outcome.nudged_epoch, outcome.did_expand)
|
||||
} else {
|
||||
nudge_to_zoned_time(sign, &values, iso_date, iso_time, time_zone, increment, smallest_unit, mode)?
|
||||
nudge_to_zoned_time(sign, &values, iso, time_zone, spec)?
|
||||
};
|
||||
if did_expand && smallest_unit != 2 {
|
||||
let start_unit = smallest_unit.min(3);
|
||||
@@ -2113,18 +2140,18 @@ fn round_relative_duration_zoned(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - unit 为时间单位(时到纳秒);day 单位走日历窗口路径,不进本函数。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn nudge_to_zoned_time(
|
||||
sign: i128, values: &[f64; 10], iso_date: (i128, i128, i128), iso_time: i128, time_zone: &str, increment: i128,
|
||||
unit: usize, mode: InstantRoundingMode,
|
||||
sign: i128, values: &[f64; 10], iso: IsoDateTime, time_zone: &str, spec: RoundingSpec,
|
||||
) -> Result<([f64; 10], i128, bool), AddZonedError> {
|
||||
let increment = spec.increment;
|
||||
let unit = spec.unit;
|
||||
let mode = spec.mode;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let time_ns = duration_time_only_nanoseconds(values).ok_or(AddZonedError::OutOfTimeRange)?;
|
||||
let start = calendar_date_add_iso(
|
||||
iso_date,
|
||||
values[0] as i128,
|
||||
values[1] as i128,
|
||||
values[2] as i128,
|
||||
values[3] as i128,
|
||||
[values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
|
||||
true,
|
||||
)?;
|
||||
let start_epoch = get_epoch_nanos_for(time_zone, start, iso_time)?;
|
||||
@@ -2156,15 +2183,18 @@ fn nudge_to_zoned_time(
|
||||
/// 1. unit 为时间单位 → TotalTimeDuration 快路径(纳秒差直接除单位长度)。
|
||||
/// 2. 否则 DifferenceZonedDateTime + TotalRelativeDuration。
|
||||
pub(crate) fn difference_zoned_datetime_with_total(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, unit: usize,
|
||||
pair: ZonedPair, unit: usize,
|
||||
) -> Result<f64, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
if unit >= 4 {
|
||||
let difference = ns2 - ns1;
|
||||
return Ok(difference as f64 / TIME_UNIT_NS[unit - 4] as f64);
|
||||
}
|
||||
let difference = difference_zoned_datetime(ns1, ns2, time_zone, _calendar, unit)?;
|
||||
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
total_relative_duration(&difference, ns1, ns2, (iso_date, iso_time), time_zone, unit)
|
||||
let difference = difference_zoned_datetime(pair, unit)?;
|
||||
let iso = get_iso_date_time_for(time_zone, ns1)?;
|
||||
total_relative_duration(&difference, ns1, ns2, iso, time_zone, unit)
|
||||
}
|
||||
|
||||
/// TotalRelativeDuration(规范 7.5.39):时长以 unit 计的总量,相对起点折算。
|
||||
@@ -2174,27 +2204,23 @@ pub(crate) fn difference_zoned_datetime_with_total(
|
||||
/// (increment = 1、trunc)取 [[Total]]。
|
||||
/// 2. 否则 TotalTimeDuration(24h 日折算,zoned 面不可达)。
|
||||
fn total_relative_duration(
|
||||
difference: &InternalDuration, origin_ns: i128, dest_ns: i128, iso: ((i128, i128, i128), i128), time_zone: &str,
|
||||
difference: &InternalDuration, origin_ns: i128, dest_ns: i128, iso: IsoDateTime, time_zone: &str,
|
||||
unit: usize,
|
||||
) -> Result<f64, AddZonedError> {
|
||||
let values = difference.to_values();
|
||||
// zoned 面时区恒在场:unit ≤ day 恒走日历窗口路径。
|
||||
if unit <= 3 {
|
||||
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 };
|
||||
let (iso_date, iso_time) = iso;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
|
||||
let outcome = nudge_to_calendar_unit(
|
||||
sign,
|
||||
&values,
|
||||
origin_ns,
|
||||
dest_ns,
|
||||
iso_date,
|
||||
1,
|
||||
let spec = RoundingSpec {
|
||||
increment: 1,
|
||||
unit,
|
||||
InstantRoundingMode::Trunc,
|
||||
&epoch_of_date,
|
||||
)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
};
|
||||
let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
return Ok(outcome.total);
|
||||
}
|
||||
let time_ns = duration_time_only_nanoseconds(&values).ok_or(AddZonedError::OutOfTimeRange)?;
|
||||
|
||||
Reference in New Issue
Block a user