feat: PlainDate/PlainDateTime until/since 舍入选项(roundingMode/roundingIncrement/smallestUnit,GetDifferenceSettings 对齐)
This commit is contained in:
@@ -3574,86 +3574,138 @@ fn add_months_i128(year: i128, month: i128, day: i128, months: i128) -> (i128, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `Temporal.PlainDateTime.prototype.until/since(other, options)`:按最大/最小单位
|
/// `Temporal.PlainDateTime.prototype.until/since(other, options)`:按最大/最小单位
|
||||||
/// 计算差值并舍入。until 返回 other 减 receiver,since 返回反向。
|
/// 差值设置(GetDifferenceSettings 产物:单位层级、增量、舍入模式)。
|
||||||
fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -> NativeResult {
|
struct DifferenceSettings {
|
||||||
let (sy, sm, sd, st) = match plain_date_time_parts(vm, args) {
|
largest_index: usize,
|
||||||
Ok(parts) => parts,
|
smallest_index: usize,
|
||||||
Err(error) => return NativeResult::Err(error),
|
increment: i128,
|
||||||
};
|
mode: InstantRoundingMode,
|
||||||
let other = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
}
|
||||||
let (oy, om, od, ot) = match plain_date_time_like_parts(vm, other, true) {
|
|
||||||
Ok(parts) => parts,
|
|
||||||
Err(error) => return NativeResult::Err(error),
|
|
||||||
};
|
|
||||||
let options_value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
|
||||||
|
|
||||||
|
/// PlainDate 差值单位层级:0=year 1=month 2=week 3=day(不含时间单位)。
|
||||||
|
fn plain_date_unit_index(value: &str) -> Option<usize> {
|
||||||
|
match value {
|
||||||
|
"year" | "years" => Some(0),
|
||||||
|
"month" | "months" => Some(1),
|
||||||
|
"week" | "weeks" => Some(2),
|
||||||
|
"day" | "days" => Some(3),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析差值选项(对齐 GetDifferenceSettings):读取顺序 largestUnit →
|
||||||
|
/// roundingIncrement → roundingMode → smallestUnit。date_only 时单位限定
|
||||||
|
/// year/month/week/day,smallestUnit 缺省 "day"(含时间时缺省 "nanosecond")。
|
||||||
|
fn parse_difference_settings<H: VmHost>(
|
||||||
|
vm: &mut H,
|
||||||
|
options_value: JsValue,
|
||||||
|
date_only: bool,
|
||||||
|
) -> Result<DifferenceSettings, JsValue> {
|
||||||
|
let unit_index = |value: &str| -> Option<usize> {
|
||||||
|
if date_only {
|
||||||
|
plain_date_unit_index(value)
|
||||||
|
} else {
|
||||||
|
plain_date_time_unit_index(value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let default_smallest = if date_only { "day" } else { "nanosecond" };
|
||||||
let (largest_raw, increment_value, mode_value, smallest_raw) = if options_value.is_undefined() {
|
let (largest_raw, increment_value, mode_value, smallest_raw) = if options_value.is_undefined() {
|
||||||
(None, 1.0, "trunc".to_string(), "nanosecond".to_string())
|
(None, 1.0, "trunc".to_string(), default_smallest.to_string())
|
||||||
} else {
|
} else {
|
||||||
if !options_value.is_object() {
|
if !options_value.is_object() {
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, "options must be an object"));
|
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
||||||
}
|
}
|
||||||
let options_ptr = options_value.as_js_object_ptr();
|
let options_ptr = options_value.as_js_object_ptr();
|
||||||
if options_ptr.is_null() {
|
if options_ptr.is_null() {
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, "options must be an object"));
|
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
||||||
}
|
}
|
||||||
let options = unsafe { &*options_ptr };
|
let options = unsafe { &*options_ptr };
|
||||||
// 读取顺序对齐 GetDifferenceSettings:largestUnit → roundingIncrement → roundingMode → smallestUnit。
|
|
||||||
let largest_raw = match temporal_option_value(vm, options, options_value, "largestUnit") {
|
let largest_raw = match temporal_option_value(vm, options, options_value, "largestUnit") {
|
||||||
Ok(raw) if raw.is_undefined() => None,
|
Ok(raw) if raw.is_undefined() => None,
|
||||||
Ok(raw) => match temporal_option_string(vm, raw) {
|
Ok(raw) => match temporal_option_string(vm, raw) {
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
},
|
},
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
let increment_raw = match temporal_option_value(vm, options, options_value, "roundingIncrement") {
|
let increment_raw = match temporal_option_value(vm, options, options_value, "roundingIncrement") {
|
||||||
Ok(raw) if raw.is_undefined() => 1.0,
|
Ok(raw) if raw.is_undefined() => 1.0,
|
||||||
Ok(raw) => match temporal_option_number(vm, raw) {
|
Ok(raw) => match temporal_option_number(vm, raw) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
},
|
},
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
let mode_raw = match temporal_option_value(vm, options, options_value, "roundingMode") {
|
let mode_raw = match temporal_option_value(vm, options, options_value, "roundingMode") {
|
||||||
Ok(raw) if raw.is_undefined() => "trunc".to_string(),
|
Ok(raw) if raw.is_undefined() => "trunc".to_string(),
|
||||||
Ok(raw) => match temporal_option_string(vm, raw) {
|
Ok(raw) => match temporal_option_string(vm, raw) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
},
|
},
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
let smallest_raw = match temporal_option_value(vm, options, options_value, "smallestUnit") {
|
let smallest_raw = match temporal_option_value(vm, options, options_value, "smallestUnit") {
|
||||||
Ok(raw) if raw.is_undefined() => "nanosecond".to_string(),
|
Ok(raw) if raw.is_undefined() => default_smallest.to_string(),
|
||||||
Ok(raw) => match temporal_option_string(vm, raw) {
|
Ok(raw) => match temporal_option_string(vm, raw) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
},
|
},
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
(largest_raw, increment_raw, mode_raw, smallest_raw)
|
(largest_raw, increment_raw, mode_raw, smallest_raw)
|
||||||
};
|
};
|
||||||
|
|
||||||
let smallest_index = match plain_date_time_unit_index(&smallest_raw) {
|
let smallest_index = match unit_index(&smallest_raw) {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
None => return NativeResult::Err(crate::error::create_range_error(vm, "invalid smallestUnit")),
|
None => return Err(crate::error::create_range_error(vm, "invalid smallestUnit")),
|
||||||
};
|
};
|
||||||
// auto/缺省:LargerOfTwoTemporalUnits('day', smallestUnit)。
|
// auto/缺省:LargerOfTwoTemporalUnits('day', smallestUnit)。
|
||||||
// 索引 0=year…3=day…9=nanosecond,更大单位取更小索引,故为 min(3, smallest)。
|
// 索引 0=year…3=day…9=nanosecond,更大单位取更小索引,故为 min(3, smallest)。
|
||||||
let largest_index = match largest_raw {
|
let largest_index = match largest_raw {
|
||||||
Some(value) if value == "auto" => smallest_index.min(3),
|
Some(value) if value == "auto" => smallest_index.min(3),
|
||||||
Some(value) => match plain_date_time_unit_index(&value) {
|
Some(value) => match unit_index(&value) {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
None => return NativeResult::Err(crate::error::create_range_error(vm, "invalid largestUnit")),
|
None => return Err(crate::error::create_range_error(vm, "invalid largestUnit")),
|
||||||
},
|
},
|
||||||
None => smallest_index.min(3),
|
None => smallest_index.min(3),
|
||||||
};
|
};
|
||||||
if largest_index > smallest_index {
|
if largest_index > smallest_index {
|
||||||
return NativeResult::Err(crate::error::create_range_error(vm, "smallestUnit exceeds largestUnit"));
|
return Err(crate::error::create_range_error(vm, "smallestUnit exceeds largestUnit"));
|
||||||
}
|
}
|
||||||
let Some(mut mode) = instant_rounding_mode(&mode_value) else {
|
let Some(mode) = instant_rounding_mode(&mode_value) else {
|
||||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid roundingMode"));
|
return Err(crate::error::create_range_error(vm, "invalid roundingMode"));
|
||||||
};
|
};
|
||||||
|
if !increment_value.is_finite() {
|
||||||
|
return Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
||||||
|
}
|
||||||
|
let increment = increment_value.trunc();
|
||||||
|
if !(1.0..=1_000_000_000.0).contains(&increment) {
|
||||||
|
return Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
||||||
|
}
|
||||||
|
let increment = increment as i128;
|
||||||
|
const UNIT_LIMITS: [i128; 6] = [24, 60, 60, 1_000, 1_000, 1_000];
|
||||||
|
if smallest_index >= 4 {
|
||||||
|
let limit = UNIT_LIMITS[smallest_index - 4];
|
||||||
|
if increment >= limit || limit % increment != 0 {
|
||||||
|
return Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(DifferenceSettings { largest_index, smallest_index, increment, mode })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 差值核心:internal = end - start,按设置取整;since 用 NegateRoundingMode
|
||||||
|
/// 的舍入模式并在最后整体取反(不调换两端,调换会改变 0.5 边界所在的年长)。
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
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,
|
||||||
|
) -> NativeResult {
|
||||||
|
let DifferenceSettings { largest_index, smallest_index, increment, mut mode } = settings;
|
||||||
if since {
|
if since {
|
||||||
mode = match mode {
|
mode = match mode {
|
||||||
InstantRoundingMode::Ceil => InstantRoundingMode::Floor,
|
InstantRoundingMode::Ceil => InstantRoundingMode::Floor,
|
||||||
@@ -3663,33 +3715,18 @@ fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
|||||||
other => other,
|
other => other,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if !increment_value.is_finite() {
|
|
||||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
|
||||||
}
|
|
||||||
let increment = increment_value.trunc();
|
|
||||||
if !(1.0..=1_000_000_000.0).contains(&increment) {
|
|
||||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
|
||||||
}
|
|
||||||
let increment = increment as i128;
|
|
||||||
const UNIT_LIMITS: [i128; 6] = [24, 60, 60, 1_000, 1_000, 1_000];
|
|
||||||
if smallest_index >= 4 {
|
|
||||||
let limit = UNIT_LIMITS[smallest_index - 4];
|
|
||||||
if increment >= limit || limit % increment != 0 {
|
|
||||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid roundingIncrement"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const UNIT_NS: [i128; 6] = [3_600_000_000_000, 60_000_000_000, 1_000_000_000, 1_000_000, 1_000, 1];
|
const UNIT_NS: [i128; 6] = [3_600_000_000_000, 60_000_000_000, 1_000_000_000, 1_000_000, 1_000, 1];
|
||||||
const DAY_NS: i128 = 86_400_000_000_000;
|
const DAY_NS: i128 = 86_400_000_000_000;
|
||||||
|
|
||||||
// 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。
|
// 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。
|
||||||
// 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。
|
// 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。
|
||||||
let origin_epoch = days_from_civil(i128::from(sy), i128::from(sm), i128::from(sd)) * DAY_NS + st as i128;
|
let origin_epoch = days_from_civil(start.0, start.1, start.2) * DAY_NS + start_time_ns;
|
||||||
let dest_epoch = days_from_civil(i128::from(oy), i128::from(om), i128::from(od)) * DAY_NS + ot as i128;
|
let dest_epoch = days_from_civil(end.0, end.1, end.2) * DAY_NS + end_time_ns;
|
||||||
|
|
||||||
let date1 = (i128::from(sy), i128::from(sm), i128::from(sd));
|
let date1 = start;
|
||||||
let time1_ns = st as i128;
|
let time1_ns = start_time_ns;
|
||||||
let mut date2 = (i128::from(oy), i128::from(om), i128::from(od));
|
let mut date2 = end;
|
||||||
let mut time_ns = ot as i128 - time1_ns;
|
let mut time_ns = end_time_ns - time1_ns;
|
||||||
let time_sign = if time_ns > 0 {
|
let time_sign = if time_ns > 0 {
|
||||||
1_i128
|
1_i128
|
||||||
} else if time_ns < 0 {
|
} else if time_ns < 0 {
|
||||||
@@ -3818,6 +3855,34 @@ fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
|||||||
}
|
}
|
||||||
make_duration(vm, values)
|
make_duration(vm, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算差值并舍入。until 返回 other 减 receiver,since 返回反向。
|
||||||
|
fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -> NativeResult {
|
||||||
|
let (sy, sm, sd, st) = match plain_date_time_parts(vm, args) {
|
||||||
|
Ok(parts) => parts,
|
||||||
|
Err(error) => return NativeResult::Err(error),
|
||||||
|
};
|
||||||
|
let other = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let (oy, om, od, ot) = match plain_date_time_like_parts(vm, other, true) {
|
||||||
|
Ok(parts) => parts,
|
||||||
|
Err(error) => return NativeResult::Err(error),
|
||||||
|
};
|
||||||
|
let options_value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
let settings = match parse_difference_settings(vm, options_value, false) {
|
||||||
|
Ok(settings) => settings,
|
||||||
|
Err(error) => return NativeResult::Err(error),
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
settings,
|
||||||
|
since,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// `Temporal.PlainDateTime.prototype.until(other, options)`。
|
/// `Temporal.PlainDateTime.prototype.until(other, options)`。
|
||||||
pub fn plain_date_time_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn plain_date_time_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
plain_date_time_difference(vm, args, false)
|
plain_date_time_difference(vm, args, false)
|
||||||
@@ -3828,6 +3893,7 @@ pub fn plain_date_time_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
|||||||
plain_date_time_difference(vm, args, true)
|
plain_date_time_difference(vm, args, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// `Temporal.PlainDateTime.prototype.add(durationLike, options)`。
|
/// `Temporal.PlainDateTime.prototype.add(durationLike, options)`。
|
||||||
pub fn plain_date_time_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn plain_date_time_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
plain_date_time_apply_duration(vm, args, 1)
|
plain_date_time_apply_duration(vm, args, 1)
|
||||||
@@ -3916,71 +3982,6 @@ fn date_like_ymd<H: VmHost>(vm: &mut H, val: JsValue) -> Result<(i32, u32, u32),
|
|||||||
Ok(ymd)
|
Ok(ymd)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_prop_text<H: VmHost>(vm: &mut H, obj: &JsObject, receiver: JsValue, name: &str) -> Option<String> {
|
|
||||||
let key_val = vm.new_string(name);
|
|
||||||
let si = vm.property_key_si(key_val);
|
|
||||||
vm.ordinary_get(obj, si, receiver)
|
|
||||||
.ok()
|
|
||||||
.filter(|value| value.is_string())
|
|
||||||
.map(to_string)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn largest_unit<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<String, JsValue> {
|
|
||||||
if args.len() <= 2 {
|
|
||||||
return Ok("days".to_string());
|
|
||||||
}
|
|
||||||
let options = vm.reg(args[2]);
|
|
||||||
if options.is_nullish() {
|
|
||||||
return Ok("days".to_string());
|
|
||||||
}
|
|
||||||
if !options.is_object() {
|
|
||||||
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
|
||||||
}
|
|
||||||
let ptr = options.as_js_object_ptr();
|
|
||||||
if ptr.is_null() {
|
|
||||||
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
|
||||||
}
|
|
||||||
let unit = read_prop_text(vm, unsafe { &*ptr }, options, "largestUnit").unwrap_or_else(|| "days".to_string());
|
|
||||||
let unit = unit.to_ascii_lowercase();
|
|
||||||
let unit = unit.strip_suffix('s').unwrap_or(&unit);
|
|
||||||
match unit {
|
|
||||||
"year" | "month" | "week" | "day" => Ok(unit.to_string()),
|
|
||||||
"auto" => Ok("day".to_string()),
|
|
||||||
_ => Err(crate::error::create_range_error(vm, "invalid largestUnit")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 两个 ISO 日期(全表示范围)的差值,语义与原 chrono 版本一致,
|
|
||||||
/// 但基于 civil 日计数,支持 PlainDate 全范围(-271821-04-19 … +275760-09-13,超出 chrono NaiveDate)。
|
|
||||||
fn date_difference_civil(start: (i32, u32, u32), end: (i32, u32, u32), unit: &str) -> [f64; 10] {
|
|
||||||
let start_days = days_from_civil(i128::from(start.0), i128::from(start.1), i128::from(start.2));
|
|
||||||
let end_days = days_from_civil(i128::from(end.0), i128::from(end.1), i128::from(end.2));
|
|
||||||
let total_days = end_days - start_days;
|
|
||||||
if unit == "day" {
|
|
||||||
return [0.0, 0.0, 0.0, total_days as f64, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
|
||||||
}
|
|
||||||
if unit == "week" {
|
|
||||||
return [0.0, 0.0, (total_days / 7) as f64, (total_days % 7) as f64, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
|
||||||
}
|
|
||||||
|
|
||||||
let direction = if end_days >= start_days { 1_i64 } else { -1_i64 };
|
|
||||||
let mut total_months =
|
|
||||||
(i128::from(end.0) - i128::from(start.0)) * 12 + i128::from(end.1 - 1) - i128::from(start.1 - 1);
|
|
||||||
let mut candidate = add_signed_months_civil(start, total_months as i64);
|
|
||||||
let mut candidate_days = days_from_civil(i128::from(candidate.0), i128::from(candidate.1), i128::from(candidate.2));
|
|
||||||
if (direction > 0 && candidate_days > end_days) || (direction < 0 && candidate_days < end_days) {
|
|
||||||
total_months -= i128::from(direction);
|
|
||||||
candidate = add_signed_months_civil(start, total_months as i64);
|
|
||||||
candidate_days = days_from_civil(i128::from(candidate.0), i128::from(candidate.1), i128::from(candidate.2));
|
|
||||||
}
|
|
||||||
let remainder_days = end_days - candidate_days;
|
|
||||||
let (years, months) = if unit == "year" {
|
|
||||||
(total_months / 12, total_months % 12)
|
|
||||||
} else {
|
|
||||||
(0, total_months)
|
|
||||||
};
|
|
||||||
[years as f64, months as f64, 0.0, remainder_days as f64, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Temporal.PlainDate.prototype.dayOfWeek` getter:ISO 周几(周一 1 … 周日 7)。
|
/// `Temporal.PlainDate.prototype.dayOfWeek` getter:ISO 周几(周一 1 … 周日 7)。
|
||||||
pub fn plain_date_day_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn plain_date_day_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
@@ -4230,17 +4231,9 @@ fn add_signed_months(date: NaiveDate, months: i64) -> Option<NaiveDate> {
|
|||||||
NaiveDate::from_ymd_opt(year, month, day)
|
NaiveDate::from_ymd_opt(year, month, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 在 ISO 日期(分量式)上加减月数:天钳制到月末,支持超出 chrono 的宽年份。
|
|
||||||
fn add_signed_months_civil(date: (i32, u32, u32), months: i64) -> (i32, u32, u32) {
|
|
||||||
let total = i128::from(date.0) * 12 + i128::from(date.1 - 1) + i128::from(months);
|
|
||||||
let year = total.div_euclid(12);
|
|
||||||
let month = total.rem_euclid(12) as u32 + 1;
|
|
||||||
let year = year.clamp(i128::from(i32::MIN), i128::from(i32::MAX)) as i32;
|
|
||||||
let max_day = days_in_month(i128::from(year), i128::from(month)).unwrap_or(31) as u32;
|
|
||||||
(year, month, date.2.min(max_day))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Temporal.PlainDate.prototype.until(other)`,按默认天单位返回差值。
|
/// `Temporal.PlainDate.prototype.until(other, options)`:date-only 单位
|
||||||
|
/// (year/month/week/day)差值 + 舍入语义。
|
||||||
pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
let start = match plain_date_ymd_checked(vm, args) {
|
let start = match plain_date_ymd_checked(vm, args) {
|
||||||
Ok(date) => date,
|
Ok(date) => date,
|
||||||
@@ -4251,14 +4244,23 @@ pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return NativeResult::Err(error),
|
||||||
};
|
};
|
||||||
let unit = match largest_unit(vm, args) {
|
let options_value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
Ok(unit) => unit,
|
let settings = match parse_difference_settings(vm, options_value, true) {
|
||||||
|
Ok(settings) => settings,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return NativeResult::Err(error),
|
||||||
};
|
};
|
||||||
make_duration(vm, date_difference_civil(start, other, &unit))
|
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,
|
||||||
|
settings,
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Temporal.PlainDate.prototype.since(other)`,按默认天单位返回差值。
|
/// `Temporal.PlainDate.prototype.since(other, options)`:date-only 单位差值 + 舍入。
|
||||||
pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
let start = match plain_date_ymd_checked(vm, args) {
|
let start = match plain_date_ymd_checked(vm, args) {
|
||||||
Ok(date) => date,
|
Ok(date) => date,
|
||||||
@@ -4269,15 +4271,18 @@ pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return NativeResult::Err(error),
|
||||||
};
|
};
|
||||||
let unit = match largest_unit(vm, args) {
|
let options_value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
Ok(unit) => unit,
|
let settings = match parse_difference_settings(vm, options_value, true) {
|
||||||
|
Ok(settings) => settings,
|
||||||
Err(error) => return NativeResult::Err(error),
|
Err(error) => return NativeResult::Err(error),
|
||||||
};
|
};
|
||||||
let mut values = date_difference_civil(start, other, &unit);
|
difference_core(
|
||||||
for value in &mut values {
|
vm,
|
||||||
if *value != 0.0 {
|
(i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||||
*value = -*value;
|
0,
|
||||||
}
|
(i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||||
}
|
0,
|
||||||
make_duration(vm, values)
|
settings,
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user