refactor: temporal 偏移表示由 i32 分钟升级为 i64 秒

This commit is contained in:
2026-09-27 23:34:25 +00:00
parent 51a79ed17c
commit f824afd041
6 changed files with 146 additions and 122 deletions
+18 -18
View File
@@ -641,7 +641,7 @@ pub(crate) fn parse_fractional_second_digits<H: VmHost>(
}
}
pub(crate) fn parse_offset_minutes(value: &str) -> Option<i32> {
pub(crate) fn parse_offset_seconds(value: &str) -> Option<i64> {
let bytes = value.as_bytes();
if bytes.len() != 6 || !matches!(bytes[0], b'+' | b'-') || bytes[3] != b':' {
return None;
@@ -649,21 +649,21 @@ pub(crate) fn parse_offset_minutes(value: &str) -> Option<i32> {
if !bytes[1..3].iter().all(u8::is_ascii_digit) || !bytes[4..6].iter().all(u8::is_ascii_digit) {
return None;
}
let hours = i32::from(bytes[1] - b'0') * 10 + i32::from(bytes[2] - b'0');
let minutes = i32::from(bytes[4] - b'0') * 10 + i32::from(bytes[5] - b'0');
let hours = i64::from(bytes[1] - b'0') * 10 + i64::from(bytes[2] - b'0');
let minutes = i64::from(bytes[4] - b'0') * 10 + i64::from(bytes[5] - b'0');
if hours > 23 || minutes > 59 {
return None;
}
let magnitude = hours * 60 + minutes;
let magnitude = hours * 3600 + minutes * 60;
Some(if bytes[0] == b'-' { -magnitude } else { magnitude })
}
pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i32)> {
pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
let input = value.trim();
if input.eq_ignore_ascii_case("UTC") || input.eq_ignore_ascii_case("Z") {
return Some(("UTC".to_string(), 0));
}
if let Some(offset) = parse_offset_minutes(input) {
if let Some(offset) = parse_offset_seconds(input) {
return Some((input.to_string(), offset));
}
@@ -672,10 +672,10 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i32)> {
&& matches!(input.as_bytes()[0], b'+' | b'-')
&& input.as_bytes()[1..3].iter().all(u8::is_ascii_digit)
{
let hour = (input.as_bytes()[1] - b'0') as i32 * 10 + (input.as_bytes()[2] - b'0') as i32;
let hour = (input.as_bytes()[1] - b'0') as i64 * 10 + (input.as_bytes()[2] - b'0') as i64;
if hour <= 23 {
let sign = if input.as_bytes()[0] == b'-' { -1 } else { 1 };
return Some((input.to_string(), sign * hour * 60));
return Some((input.to_string(), sign * hour * 3600));
}
return None;
}
@@ -685,11 +685,11 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i32)> {
&& matches!(input.as_bytes()[0], b'+' | b'-')
&& input.as_bytes()[1..5].iter().all(u8::is_ascii_digit)
{
let hour = (input.as_bytes()[1] - b'0') as i32 * 10 + (input.as_bytes()[2] - b'0') as i32;
let minute = (input.as_bytes()[3] - b'0') as i32 * 10 + (input.as_bytes()[4] - b'0') as i32;
let hour = (input.as_bytes()[1] - b'0') as i64 * 10 + (input.as_bytes()[2] - b'0') as i64;
let minute = (input.as_bytes()[3] - b'0') as i64 * 10 + (input.as_bytes()[4] - b'0') as i64;
if hour <= 23 && minute <= 59 {
let sign = if input.as_bytes()[0] == b'-' { -1 } else { 1 };
return Some((input.to_string(), sign * (hour * 60 + minute)));
return Some((input.to_string(), sign * (hour * 3600 + minute * 60)));
}
return None;
}
@@ -706,7 +706,7 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i32)> {
if annotation.eq_ignore_ascii_case("UTC") {
return Some(("UTC".to_string(), 0));
}
return parse_offset_minutes(annotation).map(|offset| (annotation.to_string(), offset));
return parse_offset_seconds(annotation).map(|offset| (annotation.to_string(), offset));
}
let time_start = input.find(['T', 't', ' '])?;
@@ -719,7 +719,7 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i32)> {
.rev()
.find_map(|(index, ch)| matches!(ch, '+' | '-').then_some(index))?;
let offset_id = &time[offset_start..];
parse_offset_minutes(offset_id).map(|offset| (offset_id.to_string(), offset))
parse_offset_seconds(offset_id).map(|offset| (offset_id.to_string(), offset))
}
pub(crate) fn civil_from_days(days: i128) -> (i128, i128, i128) {
@@ -1162,11 +1162,11 @@ pub(crate) fn zoned_date_time_plain_parts<H: VmHost>(
return Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
};
let time_zone_id = to_string(obj.get_prop_at(1));
let Some(offset_minutes) = instant_time_zone_offset(&time_zone_id) else {
let Some(offset_seconds) = instant_time_zone_offset(&time_zone_id) else {
return Err(crate::error::create_range_error(vm, "invalid time zone"));
};
const DAY_NS: i128 = 86_400_000_000_000;
let local_ns = epoch_ns + i128::from(offset_minutes) * 60_000_000_000;
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) as f64;
let (year, month, day) = civil_from_days(days);
@@ -1176,16 +1176,16 @@ pub(crate) fn zoned_date_time_plain_parts<H: VmHost>(
Ok((year as i32, month as u32, day as u32, time_ns))
}
/// 本地墙钟分量 + 时区偏移(分钟)→ 纪元纳秒(zoned_date_time_plain_parts 的逆)。
/// 本地墙钟分量 + 时区偏移(秒)→ 纪元纳秒(zoned_date_time_plain_parts 的逆)。
///
/// # 边界与前提
/// - (year, month, day, time_ns) 须已通过 valid_iso_date / valid_plain_time 校验(调用方保证)。
/// - 仅做 checked 溢出防护,Instant 范围校验由调用方按需执行。
pub(crate) fn local_to_epoch_ns(year: i32, month: u32, day: u32, time_ns: f64, offset_minutes: i32) -> Option<i128> {
pub(crate) fn local_to_epoch_ns(year: i32, month: u32, day: u32, time_ns: f64, offset_seconds: i64) -> Option<i128> {
let days = days_from_civil(i128::from(year), i128::from(month), i128::from(day));
days.checked_mul(86_400_000_000_000)?
.checked_add(time_ns as i128)?
.checked_sub(i128::from(offset_minutes) * 60_000_000_000)
.checked_sub(i128::from(offset_seconds) * 1_000_000_000)
}
pub(crate) fn make_plain_date<H: VmHost>(vm: &mut H, year: i32, month: u32, day: u32, calendar: &str) -> NativeResult {
@@ -16,7 +16,7 @@ use super::difference::{
nudge_window, plain_date_time_unit_index, round_instant_difference, DifferenceSettings, DAY_NS, MAX_ISO_DAY,
};
use super::instant::{instant_rounding_mode, instant_time_zone_offset, InstantRoundingMode};
use super::zoned_date_time::{parse_any_offset_minutes, valid_offset_fraction, zoned_date_time_string_parts};
use super::zoned_date_time::{parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts};
use super::{make_duration, parse_plain_date_time_string};
fn parse_duration_string(input: &str) -> Option<[f64; 10]> {
@@ -310,8 +310,8 @@ fn duration_relative_to_date<H: VmHost>(
// (canonical_time_zone 拒 IANA),可解析的带注解串已被 plain 首支接受。
// 按 instant 解析 + 注解时区反推墙钟日期。
let (epoch_ns, time_zone_id, _calendar) = zoned_date_time_string_parts(vm, &text, "reject")?;
let offset_minutes = instant_time_zone_offset(&time_zone_id).unwrap_or(0);
let wall_ns = epoch_ns + i128::from(offset_minutes) * 60_000_000_000;
let offset_seconds = instant_time_zone_offset(&time_zone_id).unwrap_or(0);
let wall_ns = epoch_ns + i128::from(offset_seconds) * 1_000_000_000;
let (year, month, day) = civil_from_days(wall_ns.div_euclid(DAY_NS));
return Ok(Some((year, month, day)));
}
@@ -363,7 +363,7 @@ fn duration_relative_to_date<H: VmHost>(
return Err(crate::error::create_type_error(vm, "invalid offset"));
}
let offset_input = to_string(offset_raw);
if parse_any_offset_minutes(&offset_input).is_none() || !valid_offset_fraction(&offset_input) {
if parse_any_offset_seconds(&offset_input).is_none() || !valid_offset_fraction(&offset_input) {
return Err(crate::error::create_range_error(vm, "invalid offset"));
}
}
@@ -727,15 +727,15 @@ pub fn instant_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
instant_difference(vm, args, true)
}
pub(crate) fn instant_time_zone_offset(value: &str) -> Option<i32> {
pub(crate) fn instant_time_zone_offset(value: &str) -> Option<i64> {
canonical_time_zone(value).map(|(_, offset)| offset)
}
fn format_instant_iso(
epoch_ns: i128, offset_minutes: Option<i32>, include_seconds: bool, fractional_digits: Option<usize>,
epoch_ns: i128, offset_seconds: Option<i64>, include_seconds: bool, fractional_digits: Option<usize>,
) -> Option<String> {
const DAY_NS: i128 = 86_400_000_000_000;
let offset_ns = i128::from(offset_minutes.unwrap_or(0)).checked_mul(60_000_000_000)?;
let offset_ns = i128::from(offset_seconds.unwrap_or(0)).checked_mul(1_000_000_000)?;
let local_ns = epoch_ns.checked_add(offset_ns)?;
let days = local_ns.div_euclid(DAY_NS);
let mut time_ns = local_ns.rem_euclid(DAY_NS);
@@ -766,10 +766,10 @@ fn format_instant_iso(
}
}
if let Some(offset) = offset_minutes {
if let Some(offset) = offset_seconds {
let sign = if offset < 0 { '-' } else { '+' };
let magnitude = offset.abs();
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 60, magnitude % 60));
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60));
} else {
output.push('Z');
}
@@ -850,7 +850,7 @@ pub fn instant_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
None => (1, true, None),
},
};
let offset_minutes = if time_zone_raw.is_undefined() {
let offset_seconds = if time_zone_raw.is_undefined() {
None
} else {
if !time_zone_raw.is_string() {
@@ -868,7 +868,7 @@ pub fn instant_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if rounded_ns.unsigned_abs() > MAX_INSTANT_NS as u128 {
return NativeResult::Err(crate::error::create_range_error(vm, "Instant outside supported range"));
}
match format_instant_iso(rounded_ns, offset_minutes, include_seconds, output_digits) {
match format_instant_iso(rounded_ns, offset_seconds, include_seconds, output_digits) {
Some(output) => NativeResult::Ok(vm.new_string_owned(output)),
None => NativeResult::Err(crate::error::create_range_error(vm, "invalid Instant")),
}
@@ -8,7 +8,7 @@ use super::common::native_try;
use super::{
difference_core, duration_component_integer, duration_like_values, ensure_plain_time, get_double_prop,
initialize_temporal_receiver, instant_rounding_mode, instant_string_without_annotations, is_ctor_call,
make_plain_time, parse_any_offset_minutes, parse_difference_settings, parse_fractional_second_digits,
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,
@@ -177,7 +177,7 @@ fn parse_plain_time_spec(s: &str) -> Option<f64> {
Some(index) => (&s[..index], &s[index..]),
None => (s, ""),
};
if !offset.is_empty() && (parse_any_offset_minutes(offset).is_none() || !valid_offset_fraction(offset)) {
if !offset.is_empty() && (parse_any_offset_seconds(offset).is_none() || !valid_offset_fraction(offset)) {
return None;
}
parse_plain_clock(clock)
+41 -17
View File
@@ -78,16 +78,16 @@ fn calendar_id_slot_fallback() {
fn start_of_day_epoch_ns_instant_range() {
// 当地午夜回推 epoch 越 Instant 界(±MAX)返回 None,供 startOfDay/hoursInDay RangeError 依据。
// -100000001 天 + 1h < -MAX → 越界。
assert_eq!(start_of_day_epoch_ns(-271_821, 4, 19, -60), None);
assert_eq!(start_of_day_epoch_ns(-271_821, 4, 19, -3600), None);
// -100000000 天(-271821-04-20)UTC 当地午夜恰为 -MAX,在边界内。
assert_eq!(start_of_day_epoch_ns(-271_821, 4, 20, 0), Some(-8_640_000_000_000_000_000_000));
// 同日期 +1h 偏移使当地午夜 -MAX - 1h 越界。
assert_eq!(start_of_day_epoch_ns(-271_821, 4, 20, 60), None);
assert_eq!(start_of_day_epoch_ns(-271_821, 4, 20, 3600), None);
// 明日边界越界:+100000001 天(UTC)。
assert_eq!(start_of_day_epoch_ns_by_days(100_000_001, 0), None);
// 正常日期返回当地午夜。
assert_eq!(start_of_day_epoch_ns(1970, 1, 1, 0), Some(0));
assert_eq!(start_of_day_epoch_ns(1970, 1, 1, 60), Some(-3_600_000_000_000));
assert_eq!(start_of_day_epoch_ns(1970, 1, 1, 3600), Some(-3_600_000_000_000));
}
#[test]
@@ -104,20 +104,20 @@ 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, 60, "+01:00", "iso8601", true, None, "auto", "auto", "auto");
let base = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "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, 60, "+01:00", "iso8601", true, None, "never", "auto", "auto");
let no_offset = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "never", "auto", "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, 60, "+01:00", "iso8601", true, None, "auto", "auto", "always");
let ca_always = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "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, 60, "+01:00", "iso8601", true, None, "auto", "critical", "critical");
let critical = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "critical", "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, 60, "+01:00", "iso8601", true, None, "critical", "auto", "auto");
format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "critical", "auto", "auto");
assert_eq!(offset_critical, Some("1970-01-01T01:00:00!+01:00[+01:00]".to_string()));
}
@@ -132,14 +132,17 @@ fn format_zoned_date_time_iso_epoch_rounding_cross_midnight() {
#[test]
fn local_to_epoch_ns_roundtrip() {
// 与 parse_instant_string 互逆对拍:同一时刻的本地分量 + 偏移换算回 epoch 一致。
assert_eq!(parse_instant_string("2024-01-01T00:00:00+01:00"), local_to_epoch_ns(2024, 1, 1, 0.0, 60),);
assert_eq!(
parse_instant_string("2024-01-01T00:00:00+01:00"),
local_to_epoch_ns(2024, 1, 1, 0.0, 3600),
);
assert_eq!(
parse_instant_string("1969-07-16T13:32:01.234567891Z"),
local_to_epoch_ns(1969, 7, 16, 48_721_234_567_891.0, 0),
);
// startOfDay 最小边界:-271821-04-20 加 1h 再回推 1h 偏移回到 -MAX。
assert_eq!(
local_to_epoch_ns(-271821, 4, 20, 3_600_000_000_000.0, 60),
local_to_epoch_ns(-271821, 4, 20, 3_600_000_000_000.0, 3600),
Some(-8_640_000_000_000_000_000_000),
);
}
@@ -161,16 +164,37 @@ fn zoned_date_time_string_wall_day_range_boundary() {
#[test]
fn canonical_time_zone_4_digit_offset() {
// ±HHMM 无冒号形式归一:ID 保留原串,offset 分钟数正确换算。
// ±HHMM 无冒号形式归一:ID 保留原串,offset 秒数正确换算。
assert_eq!(canonical_time_zone("+0000"), Some(("+0000".to_string(), 0)));
assert_eq!(canonical_time_zone("-0530"), Some(("-0530".to_string(), -330)));
assert_eq!(canonical_time_zone("+2330"), Some(("+2330".to_string(), 1410)));
assert_eq!(canonical_time_zone("-0530"), Some(("-0530".to_string(), -19800)));
assert_eq!(canonical_time_zone("+2330"), Some(("+2330".to_string(), 84600)));
// 非法分钟/小时拒绝。
assert_eq!(canonical_time_zone("+2400"), None);
assert_eq!(canonical_time_zone("+0060"), None);
assert_eq!(canonical_time_zone("+123"), None); // 长度不符
}
#[test]
fn offset_seconds_representation_sub_minute() {
// i64 秒表示保留亚分钟偏移(±HH:MM:SS 秒位),旧分钟表示丢弃秒位。
assert_eq!(parse_any_offset_seconds("+00:44:30"), Some(2670));
assert_eq!(parse_any_offset_seconds("-00:44:30"), Some(-2670));
assert_eq!(parse_any_offset_seconds("+01:00:00"), Some(3600));
assert_eq!(parse_any_offset_seconds("-23:59:59"), Some(-(23 * 3600 + 59 * 60 + 59)));
// 固定偏移秒值与分钟值 ×60 一致(±HH:MM 六字符形)。
assert_eq!(canonical_time_zone("+01:00"), Some(("+01:00".to_string(), 3600)));
assert_eq!(canonical_time_zone("-05:30"), Some(("-05:30".to_string(), -19800)));
// 亚分钟偏移经 local_to_epoch_ns 与 parse_instant_string 互逆对拍。
assert_eq!(
parse_instant_string("2024-01-01T00:00:00-00:44:30"),
local_to_epoch_ns(2024, 1, 1, 0.0, -2670),
);
assert_eq!(
parse_instant_string("2024-01-01T00:00:00+00:44:30"),
local_to_epoch_ns(2024, 1, 1, 0.0, 2670),
);
}
#[test]
fn extract_time_zone_annotation_forms() {
// UTC / critical / 数值偏移各形式提取;u-ca 注解跳过,时区注解在前后均能取到。
@@ -275,15 +299,15 @@ fn zoned_date_time_round_else_path_epoch() {
let quantum = 3_600_000_000_000 * 4;
let rounded = round_instant_ns(55_410_123_456_789, quantum, InstantRoundingMode::HalfExpand).unwrap();
assert_eq!(rounded, 57_600_000_000_000);
// 本地墙钟日回推:offset 60 分,local_to_epoch_ns 得目标 epoch。
let epoch = local_to_epoch_ns(1976, 11, 18, rounded as f64, 60).unwrap();
// 本地墙钟日回推:offset 3600 秒,local_to_epoch_ns 得目标 epoch。
let epoch = local_to_epoch_ns(1976, 11, 18, rounded as f64, 3600).unwrap();
assert_eq!(epoch, 217_177_200_000_000_000);
}
#[test]
fn zoned_date_time_round_day_path_epoch() {
// 同日本地午夜 startNs(2513 天,offset 60 分),dayProgress 舍到次日 → 217206000000000000。
let start_ns = start_of_day_epoch_ns(1976, 11, 18, 60).unwrap();
// 同日本地午夜 startNs(2513 天,offset 3600 秒),dayProgress 舍到次日 → 217206000000000000。
let start_ns = start_of_day_epoch_ns(1976, 11, 18, 3600).unwrap();
assert_eq!(start_ns, 217_119_600_000_000_000);
let day_progress = 217_175_010_123_456_789 - start_ns;
let rounded = round_instant_ns(day_progress, 86_400_000_000_000, InstantRoundingMode::HalfExpand).unwrap();
@@ -208,24 +208,24 @@ fn zoned_date_time_options<H: VmHost>(
Ok((offset, disambiguation))
}
/// 从剥注解后的 Instant 主体提取字符串内数值偏移分钟数(±HH / ±HHMM / ±HH:MM / 亚秒形式)。
/// Z/z 结尾视为 0 分钟;无偏移或偏移不可提取返回 None。
fn extract_string_offset_minutes(body: &str) -> Option<i32> {
/// 从剥注解后的 Instant 主体提取字符串内数值偏移秒数(±HH / ±HHMM / ±HH:MM / 亚秒形式)。
/// Z/z 结尾视为 0 秒;无偏移或偏移不可提取返回 None。
fn extract_string_offset_seconds(body: &str) -> Option<i64> {
let time_start = body.find(['T', 't', ' '])?;
let time = &body[time_start + 1..];
let offset_start = time
.char_indices()
.rev()
.find_map(|(index, ch)| matches!(ch, '+' | '-').then_some(index))?;
parse_any_offset_minutes(&time[offset_start..])
parse_any_offset_seconds(&time[offset_start..])
}
/// 解析数值偏移串(±HH / ±HHMM / ±HH:MM / ±HHMMSS / ±HH:MM:SS,可带小数秒)为分钟数。
pub(crate) fn parse_any_offset_minutes(value: &str) -> Option<i32> {
/// 解析数值偏移串(±HH / ±HHMM / ±HH:MM / ±HHMMSS / ±HH:MM:SS,可带小数秒)为秒数。
pub(crate) fn parse_any_offset_seconds(value: &str) -> Option<i64> {
let bytes = value.as_bytes();
let sign = match bytes.first() {
Some(b'+') => 1_i32,
Some(b'-') => -1_i32,
Some(b'+') => 1_i64,
Some(b'-') => -1_i64,
_ => return None,
};
let mut cursor = 1usize;
@@ -256,7 +256,7 @@ pub(crate) fn parse_any_offset_minutes(value: &str) -> Option<i32> {
if cursor != bytes.len() || hour > 23 || minute > 59 || second > 59 {
return None;
}
Some(sign * (hour * 60 + minute) as i32)
Some((hour * 3600 + minute * 60 + second) as i64 * sign)
}
/// 从 ZDT 对象 / ISO 字符串 / property bag 解析 (epoch_ns, time_zone_id, calendar_id) 三元组。
@@ -316,15 +316,15 @@ pub(crate) fn zoned_date_time_string_parts<H: VmHost>(
return Err(crate::error::create_range_error(vm, "invalid ISO 8601 date-time"));
};
let has_utc_designator = body.ends_with(['Z', 'z']);
let string_offset_minutes = if has_utc_designator { Some(0) } else { extract_string_offset_minutes(body) };
let string_offset_seconds = if has_utc_designator { Some(0) } else { extract_string_offset_seconds(body) };
// 有偏移/Z 时经 parse_instant_string 得 epoch 并反推墙钟;否则直接解析墙钟。
const DAY_NS: i128 = 86_400_000_000_000;
let (epoch_from_string, wall_parts) = if string_offset_minutes.is_some() {
let (epoch_from_string, wall_parts) = if string_offset_seconds.is_some() {
let epoch_ns = parse_instant_string(input)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid ISO 8601 date-time"))?;
let offset_minutes = string_offset_minutes.unwrap_or(0);
let wall_ns = epoch_ns + i128::from(offset_minutes) * 60_000_000_000;
let offset_seconds = string_offset_seconds.unwrap_or(0);
let wall_ns = epoch_ns + i128::from(offset_seconds) * 1_000_000_000;
let days = wall_ns.div_euclid(DAY_NS);
let (year, month, day) = civil_from_days(days);
(Some(epoch_ns), (year as i32, month as u32, day as u32, wall_ns.rem_euclid(DAY_NS) as f64))
@@ -340,14 +340,14 @@ pub(crate) fn zoned_date_time_string_parts<H: VmHost>(
return Err(crate::error::create_range_error(vm, "date-time out of range"));
}
let wall_epoch =
|offset_minutes: i32| local_to_epoch_ns(wall_parts.0, wall_parts.1, wall_parts.2, wall_parts.3, offset_minutes);
|offset_seconds: i64| local_to_epoch_ns(wall_parts.0, wall_parts.1, wall_parts.2, wall_parts.3, offset_seconds);
// 按 offsetBehaviour 决策:Z → exact(墙钟 epoch);无偏移 → wall(墙钟 + 时区偏移);
// 有偏移 → 按 offset 选项在字符串偏移与时区偏移之间选择。
let epoch_ns = if has_utc_designator {
epoch_from_string
} else {
match string_offset_minutes {
match string_offset_seconds {
None => wall_epoch(time_zone_offset),
Some(offset) => match offset_mode {
"use" => epoch_from_string,
@@ -401,7 +401,7 @@ fn zoned_date_time_bag_parts<H: VmHost>(
// offset 可选:语法校验(ToOffsetString 语义)先于数值字段转换。
let offset_raw = temporal_option_value(vm, obj, value, "offset")?;
let bag_offset_minutes = if offset_raw.is_undefined() {
let bag_offset_seconds = if offset_raw.is_undefined() {
None
} else {
let offset_input = temporal_option_string(vm, offset_raw)?;
@@ -415,7 +415,7 @@ fn zoned_date_time_bag_parts<H: VmHost>(
let (year, month, day, total_ns, calendar) = plain_date_time_object_parts(vm, value, obj, false, false)?;
// 按 offset 选项决定 epoch(InterpretISODateTimeOffset 固定偏移简化:候选恒唯一)。
let epoch_ns = match (bag_offset_minutes, offset_mode) {
let epoch_ns = match (bag_offset_seconds, offset_mode) {
(None, _) | (Some(_), "ignore") => local_to_epoch_ns(year, month, day, total_ns, time_zone_offset),
(Some(offset), "use") => local_to_epoch_ns(year, month, day, total_ns, offset),
(Some(offset), "prefer") => {
@@ -485,10 +485,10 @@ pub fn zoned_date_time_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号;critical 时 `!` 置于括号内。
pub(crate) fn format_time_zone_annotation(time_zone_id: &str, critical: bool) -> String {
let inner = if matches!(time_zone_id.as_bytes().first(), Some(b'+') | Some(b'-')) {
let offset_minutes = instant_time_zone_offset(time_zone_id).unwrap_or(0);
let sign = if offset_minutes < 0 { '-' } else { '+' };
let magnitude = offset_minutes.abs();
format!("{sign}{:02}:{:02}", magnitude / 60, magnitude % 60)
let offset_seconds = instant_time_zone_offset(time_zone_id).unwrap_or(0);
let sign = if offset_seconds < 0 { '-' } else { '+' };
let magnitude = offset_seconds.abs();
format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60)
} else {
time_zone_id.to_string()
};
@@ -507,11 +507,11 @@ pub(crate) fn format_time_zone_annotation(time_zone_id: &str, critical: bool) ->
/// - 偏移段恒 `±HH:MM` 带冒号;时区注解命名区原样、偏移区规范化。
#[expect(clippy::too_many_arguments)]
pub(crate) fn format_zoned_date_time_iso(
epoch_ns: i128, offset_minutes: i32, time_zone_id: &str, calendar_id: &str, include_seconds: bool,
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,
) -> Option<String> {
const DAY_NS: i128 = 86_400_000_000_000;
let offset_ns = i128::from(offset_minutes).checked_mul(60_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)?;
let days = local_ns.div_euclid(DAY_NS);
let mut time_ns = local_ns.rem_euclid(DAY_NS);
@@ -544,12 +544,12 @@ pub(crate) fn format_zoned_date_time_iso(
// offset 段:auto/always 显示,never 省略,critical 段前加 !。
if offset_name != "never" {
let sign = if offset_minutes < 0 { '-' } else { '+' };
let magnitude = offset_minutes.abs();
let sign = if offset_seconds < 0 { '-' } else { '+' };
let magnitude = offset_seconds.abs();
if offset_name == "critical" {
output.push('!');
}
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 60, magnitude % 60));
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60));
}
// timeZoneName 注解:auto 显示,never 省略,critical 时 `!` 置于括号内。
@@ -577,7 +577,7 @@ pub fn zoned_date_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
};
let time_zone_id = to_string(obj.get_prop_at(1));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let calendar_id = get_calendar_id(obj, 2);
let options_value = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
@@ -679,7 +679,7 @@ pub fn zoned_date_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
}
match format_zoned_date_time_iso(
rounded_ns,
offset_minutes,
offset_seconds,
&time_zone_id,
&calendar_id,
include_seconds,
@@ -783,7 +783,7 @@ struct ZdtMergedFields {
microsecond: f64,
nanosecond: f64,
month_code: Option<(f64, bool)>,
bag_offset: Option<i32>,
bag_offset: Option<i64>,
}
/// with 的部分字段读取与合并:按字典序读 bag 字段,与 receiver 默认分量合并。
@@ -791,7 +791,7 @@ struct ZdtMergedFields {
/// # 步骤
/// 1. 数值字段 ToNumber→trunc(NaN/±Inf RangeError,day 额外拒绝 <1);monthCode/offset 走 ToString。
/// 2. undefined 不覆盖;至少一个字段有定义否则 TypeError。
/// 3. 返回合并后的原始分量(未钳制)+ monthCode 解析 + bag offset 分钟。
/// 3. 返回合并后的原始分量(未钳制)+ monthCode 解析 + bag offset 秒。
///
/// # 边界与前提
/// - 调用方须先完成 RejectObjectWithCalendarOrTimeZone(calendar/timeZone 已拒绝)。
@@ -888,17 +888,17 @@ fn zoned_date_time_with_fields<H: VmHost>(
};
// offset 先经 ToString,再按偏移字符串语法校验(小数秒最多 9 位),解析出的
// 分钟数留给 offset 选项决策使用。
// 秒数留给 offset 选项决策使用。
let bag_offset = if offset_raw.is_undefined() {
None
} else {
let offset_input = temporal_string_strict(vm, offset_raw)?;
let minutes = parse_any_offset_minutes(&offset_input)
let seconds = parse_any_offset_seconds(&offset_input)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid offset"))?;
if !valid_offset_fraction(&offset_input) {
return Err(crate::error::create_range_error(vm, "invalid offset"));
}
Some(minutes)
Some(seconds)
};
let (receiver_year, _receiver_month, receiver_day, receiver_time_ns) = defaults;
@@ -942,7 +942,7 @@ pub fn zoned_date_time_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
native_try!(reject_partial_object_with_calendar_or_time_zone(vm, value));
let time_zone_id = to_string(obj.get_prop_at(1));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let (year, month, day, time_ns) = native_try!(zoned_date_time_plain_parts(vm, obj));
let merged = native_try!(zoned_date_time_with_fields(vm, value, (year, month, day, time_ns)));
@@ -1040,18 +1040,18 @@ pub fn zoned_date_time_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
// InterpretISODateTimeOffset(固定偏移简化):按 offset 选项在 bag 偏移与时区偏移间选择。
let epoch_ns = match (merged.bag_offset, offset_mode.as_str()) {
(None, _) => local_to_epoch_ns(year, month, day, total_ns, offset_minutes),
(None, _) => local_to_epoch_ns(year, month, day, total_ns, offset_seconds),
(Some(offset), "use") => local_to_epoch_ns(year, month, day, total_ns, offset),
(Some(_), "ignore") => local_to_epoch_ns(year, month, day, total_ns, offset_minutes),
(Some(_), "ignore") => local_to_epoch_ns(year, month, day, total_ns, offset_seconds),
(Some(offset), "prefer") => {
if offset == offset_minutes {
if offset == offset_seconds {
local_to_epoch_ns(year, month, day, total_ns, offset)
} else {
local_to_epoch_ns(year, month, day, total_ns, offset_minutes)
local_to_epoch_ns(year, month, day, total_ns, offset_seconds)
}
}
(Some(offset), "reject") => {
if offset == offset_minutes {
if offset == offset_seconds {
local_to_epoch_ns(year, month, day, total_ns, offset)
} else {
return NativeResult::Err(crate::error::create_range_error(vm, "offset and time zone disagree"));
@@ -1095,7 +1095,7 @@ pub fn zoned_date_time_with_calendar<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
make_zoned_date_time(vm, epoch_ns, &time_zone_id, &calendar)
}
/// 校验 offset 串的小数秒位数 ≤9(parse_any_offset_minutes 不限制位数,此处补查)。
/// 校验 offset 串的小数秒位数 ≤9(parse_any_offset_seconds 不限制位数,此处补查)。
pub(crate) fn valid_offset_fraction(value: &str) -> bool {
let Some(dot) = value.find(['.', ',']) else {
return true;
@@ -1119,10 +1119,10 @@ pub fn zoned_date_time_with_plain_time<H: VmHost>(vm: &mut H, args: &[u8]) -> Na
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let (year, month, day, _) = native_try!(zoned_date_time_plain_parts(vm, obj));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let plain_time_like = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let time_ns = native_try!(plain_time_like_ns(vm, plain_time_like));
let epoch_ns = local_to_epoch_ns(year, month, day, time_ns, offset_minutes)
let epoch_ns = local_to_epoch_ns(year, month, day, time_ns, offset_seconds)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"));
let epoch_ns = native_try!(epoch_ns);
if epoch_ns.unsigned_abs() > MAX_INSTANT_NS as u128 {
@@ -1207,9 +1207,9 @@ fn zoned_date_time_other_wall_epoch_ns<H: VmHost>(
if !valid_iso_date(year, month, day) || !total_ns.is_finite() || !(0.0..86_400_000_000_000.0).contains(&total_ns) {
return Err(crate::error::create_range_error(vm, "invalid date-time component"));
}
let offset_minutes = instant_time_zone_offset(default_tz_id)
let offset_seconds = instant_time_zone_offset(default_tz_id)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
local_to_epoch_ns(year, month, day, total_ns, offset_minutes)
local_to_epoch_ns(year, month, day, total_ns, offset_seconds)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))
}
@@ -1236,7 +1236,7 @@ fn zoned_date_time_other_bag_epoch_ns<H: VmHost>(
};
let offset_raw = temporal_option_value(vm, obj, value, "offset")?;
let bag_offset_minutes = if offset_raw.is_undefined() {
let bag_offset_seconds = if offset_raw.is_undefined() {
None
} else {
if !offset_raw.is_string() {
@@ -1250,7 +1250,7 @@ fn zoned_date_time_other_bag_epoch_ns<H: VmHost>(
};
let (year, month, day, total_ns, _calendar) = plain_date_time_object_parts(vm, value, obj, false, false)?;
if let Some(offset) = bag_offset_minutes {
if let Some(offset) = bag_offset_seconds {
if offset != time_zone_offset {
return Err(crate::error::create_range_error(vm, "offset and time zone disagree"));
}
@@ -1279,7 +1279,7 @@ fn zoned_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
let Some(epoch_r) = get_instant_epoch_ns(obj) else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
};
let offset_min_r = native_try!(zoned_date_time_offset_minutes(vm, obj));
let offset_seconds_r = native_try!(zoned_date_time_offset_seconds(vm, obj));
let time_zone_id = to_string(obj.get_prop_at(1));
let (y_r, m_r, d_r, time_ns_r) = native_try!(zoned_date_time_plain_parts(vm, obj));
@@ -1293,7 +1293,7 @@ fn zoned_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
// other 按 receiver 时区偏移拆本地墙钟(负 epoch 用 div_euclid/rem_euclid 保持非负余数)。
const DAY_NS: i128 = 86_400_000_000_000;
let local_ns_o = epoch_o + i128::from(offset_min_r) * 60_000_000_000;
let local_ns_o = epoch_o + i128::from(offset_seconds_r) * 1_000_000_000;
let days_o = local_ns_o.div_euclid(DAY_NS);
let (y_o, m_o, d_o) = civil_from_days(days_o);
let time_ns_o = local_ns_o.rem_euclid(DAY_NS);
@@ -1342,7 +1342,7 @@ pub(crate) fn zoned_date_time_round_unit(value: &str) -> Option<(i128, i128)> {
/// # 步骤
/// 1. roundTo 解析:undefined → TypeError;字符串 → {smallestUnit: 串};对象 → 依次 Get
/// roundingIncrement → roundingMode → smallestUnit,全部读完再统一校验。
/// 2. receiver:ensure_zoned_date_time → epoch_r;instant_time_zone_offset(tz) 得 offset_min;
/// 2. receiver:ensure_zoned_date_time → epoch_r;instant_time_zone_offset(tz) 得 offset_seconds;
/// zoned_date_time_plain_parts 得 (y, m, d, time_ns)。
/// 3. 单位表查 smallestUnit(None → RangeError "invalid smallest unit")。
/// 4. roundingIncrement 校验:1..=1e9 且真因子(increment < units_per_day 且
@@ -1363,7 +1363,7 @@ pub fn zoned_date_time_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
};
let time_zone_id = to_string(obj.get_prop_at(1));
let Some(offset_min) = instant_time_zone_offset(&time_zone_id) else {
let Some(offset_seconds) = instant_time_zone_offset(&time_zone_id) else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
};
let calendar_id = get_calendar_id(obj, 2);
@@ -1435,11 +1435,11 @@ pub fn zoned_date_time_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
const DAY_NS: i128 = 86_400_000_000_000;
let result_ns = if unit_ns == DAY_NS {
// day 路径:startNs/endNs 双算(越界 → RangeError),dayProgress 固定偏移下 ∈ [0, DAY)。
let start_ns = native_try!(start_of_day_epoch_ns(y, m, d, offset_min)
let start_ns = native_try!(start_of_day_epoch_ns(y, m, d, offset_seconds)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid start of day")));
native_try!(start_of_day_epoch_ns_by_days(
days_from_civil(i128::from(y), i128::from(m), i128::from(d)) + 1,
offset_min,
offset_seconds,
)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid start of day")));
let day_progress = epoch_r - start_ns;
@@ -1458,7 +1458,7 @@ pub fn zoned_date_time_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
let Some(rounded_time) = round_instant_ns(time_ns as i128, quantum_ns, mode) else {
return NativeResult::Err(crate::error::create_range_error(vm, "ZonedDateTime outside supported range"));
};
let Some(result) = local_to_epoch_ns(y, m, d, rounded_time as f64, offset_min) else {
let Some(result) = local_to_epoch_ns(y, m, d, rounded_time as f64, offset_seconds) else {
return NativeResult::Err(crate::error::create_range_error(vm, "ZonedDateTime outside supported range"));
};
result
@@ -1569,30 +1569,30 @@ zoned_date_time_brand_only_getter!(zoned_date_time_months_in_year, JsValue::floa
zoned_date_time_brand_only_getter!(zoned_date_time_era, JsValue::undefined());
zoned_date_time_brand_only_getter!(zoned_date_time_era_year, JsValue::undefined());
/// 读 ZDT 时区偏移(分钟);槽 1 解析失败返回 RangeError。调用方须先 ensure_zoned_date_time。
fn zoned_date_time_offset_minutes<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<i32, JsValue> {
/// 读 ZDT 时区偏移(秒);槽 1 解析失败返回 RangeError。调用方须先 ensure_zoned_date_time。
fn zoned_date_time_offset_seconds<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<i64, JsValue> {
let time_zone_id = to_string(obj.get_prop_at(1));
instant_time_zone_offset(&time_zone_id).ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))
}
/// `Temporal.ZonedDateTime.prototype.offset`:由偏移分钟数规范化为 ±HH:MM 字符串。
/// `Temporal.ZonedDateTime.prototype.offset`:由偏移秒数规范化为 ±HH:MM 字符串。
pub fn zoned_date_time_offset<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let ptr = native_try!(receiver_obj(vm, args));
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let sign = if offset_minutes < 0 { '-' } else { '+' };
let magnitude = offset_minutes.abs();
NativeResult::Ok(vm.new_string(&format!("{sign}{:02}:{:02}", magnitude / 60, magnitude % 60)))
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let sign = if offset_seconds < 0 { '-' } else { '+' };
let magnitude = offset_seconds.abs();
NativeResult::Ok(vm.new_string(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60)))
}
/// `Temporal.ZonedDateTime.prototype.offsetNanoseconds`:偏移分钟数换算纳秒(f64 精确域内)。
/// `Temporal.ZonedDateTime.prototype.offsetNanoseconds`:偏移秒数换算纳秒(f64 精确域内)。
pub fn zoned_date_time_offset_nanoseconds<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let ptr = native_try!(receiver_obj(vm, args));
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
NativeResult::Ok(JsValue::float(offset_minutes as f64 * 60_000_000_000.0))
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
NativeResult::Ok(JsValue::float(offset_seconds as f64 * 1_000_000_000.0))
}
/// `Temporal.ZonedDateTime.prototype.monthCode`:ISO 日历下恒为 `M{month:02}` 补零格式。
@@ -1610,26 +1610,26 @@ pub fn zoned_date_time_hours_in_day<H: VmHost>(vm: &mut H, args: &[u8]) -> Nativ
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let (year, month, day, _) = native_try!(zoned_date_time_plain_parts(vm, obj));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let today = native_try!(start_of_day_epoch_ns(year, month, day, offset_minutes)
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let today = native_try!(start_of_day_epoch_ns(year, month, day, offset_seconds)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date")));
let days = days_from_civil(i128::from(year), i128::from(month), i128::from(day));
let tomorrow = native_try!(start_of_day_epoch_ns_by_days(days + 1, offset_minutes)
let tomorrow = native_try!(start_of_day_epoch_ns_by_days(days + 1, offset_seconds)
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date")));
NativeResult::Ok(JsValue::float((tomorrow - today) as f64 / 3_600_000_000_000.0))
}
/// 求本地日期在给定时区偏移下的当地午夜纪元纳秒(GetStartOfDay 语义),
/// 结果超出 Instant 范围时返回 None。
pub(crate) fn start_of_day_epoch_ns(year: i32, month: u32, day: u32, offset_minutes: i32) -> Option<i128> {
start_of_day_epoch_ns_by_days(days_from_civil(i128::from(year), i128::from(month), i128::from(day)), offset_minutes)
pub(crate) fn start_of_day_epoch_ns(year: i32, month: u32, day: u32, offset_seconds: i64) -> Option<i128> {
start_of_day_epoch_ns_by_days(days_from_civil(i128::from(year), i128::from(month), i128::from(day)), offset_seconds)
}
/// 按日数直接算当地午夜(hoursInDay 的明日边界复用:days+1 不经 civil 回填)。
pub(crate) fn start_of_day_epoch_ns_by_days(days: i128, offset_minutes: i32) -> Option<i128> {
pub(crate) fn start_of_day_epoch_ns_by_days(days: i128, offset_seconds: i64) -> Option<i128> {
let start = days
.checked_mul(86_400_000_000_000)?
.checked_sub(i128::from(offset_minutes) * 60_000_000_000)?;
.checked_sub(i128::from(offset_seconds) * 1_000_000_000)?;
(start.unsigned_abs() <= MAX_INSTANT_NS as u128).then_some(start)
}
@@ -1653,7 +1653,7 @@ fn zoned_date_time_apply_duration<H: VmHost>(vm: &mut H, args: &[u8], sign: i64)
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let time_zone_id = to_string(obj.get_prop_at(1));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let (year, month, day, time_ns) = match zoned_date_time_plain_parts(vm, obj) {
Ok(parts) => parts,
Err(error) => return NativeResult::Err(error),
@@ -1711,7 +1711,7 @@ fn zoned_date_time_apply_duration<H: VmHost>(vm: &mut H, args: &[u8], sign: i64)
if !valid_plain_date_time_range(yy as i32, mm as u32, dd as u32, new_time_ns as f64) {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time"));
}
let epoch_ns = match local_to_epoch_ns(yy as i32, mm as u32, dd as u32, new_time_ns as f64, offset_minutes) {
let epoch_ns = match local_to_epoch_ns(yy as i32, mm as u32, dd as u32, new_time_ns as f64, offset_seconds) {
Some(epoch_ns) if epoch_ns.unsigned_abs() <= MAX_INSTANT_NS as u128 => epoch_ns,
_ => return NativeResult::Err(crate::error::create_range_error(vm, "ZonedDateTime outside supported range")),
};
@@ -1788,8 +1788,8 @@ pub fn zoned_date_time_start_of_day<H: VmHost>(vm: &mut H, args: &[u8]) -> Nativ
let obj = unsafe { &*ptr };
native_try!(ensure_zoned_date_time(vm, obj));
let (year, month, day, _) = native_try!(zoned_date_time_plain_parts(vm, obj));
let offset_minutes = native_try!(zoned_date_time_offset_minutes(vm, obj));
let Some(epoch_ns) = start_of_day_epoch_ns(year, month, day, offset_minutes) else {
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
let Some(epoch_ns) = start_of_day_epoch_ns(year, month, day, offset_seconds) else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date"));
};
make_zoned_date_time(vm, epoch_ns, &to_string(obj.get_prop_at(1)), &get_calendar_id(obj, 2))