refactor: temporal 与 json 长参数收进结构体

This commit is contained in:
2026-10-12 18:21:31 +00:00
parent 92fd288b07
commit 4345d14a0b
13 changed files with 603 additions and 313 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_fold" name = "oxide_fold"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_bytecode", "oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]] [[package]]
name = "oxide_net" name = "oxide_net"
version = "0.17.4" version = "0.17.5"
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.17.4" version = "0.17.5"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.17.4" version = "0.17.5"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+123 -102
View File
@@ -977,46 +977,79 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let mut visited = HashSet::new(); let mut visited = HashSet::new();
let mut output: Vec<u16> = Vec::new(); let mut output: Vec<u16> = Vec::new();
let indent_level: usize = 0; let ctx = StringifyCtx {
match jsvalue_to_json( visited: &mut visited,
vm, out: &mut output,
value,
&mut visited,
&mut output,
replacer_fn, replacer_fn,
replacer_whitelist.as_ref(), replacer_whitelist: replacer_whitelist.as_ref(),
&space, space: &space,
indent_level, indent_level: 0,
) { };
match jsvalue_to_json(vm, value, ctx) {
Ok(()) => NativeResult::Ok(vm.new_string_units_owned(output)), Ok(()) => NativeResult::Ok(vm.new_string_units_owned(output)),
Err(exc) => NativeResult::Err(exc), Err(exc) => NativeResult::Err(exc),
} }
} }
/// JSON 序列化上下文:环检表、输出流、replacer 与缩进状态。
/// 递归传递时经 [`StringifyCtx::child`] 派生下一缩进层。
struct StringifyCtx<'data, 'w> {
visited: &'w mut HashSet<*const JsObject>,
out: &'w mut Vec<u16>,
replacer_fn: Option<JsValue>,
replacer_whitelist: Option<&'data Vec<String>>,
space: &'data [u16],
indent_level: usize,
}
impl<'data, 'w> StringifyCtx<'data, 'w> {
/// 派生同缩进层上下文:共享同一环检表与输出流,indent_level 不变
/// (值位序列化入口传给对象/数组展开时用)。
fn same(&mut self) -> StringifyCtx<'data, '_> {
StringifyCtx {
visited: &mut *self.visited,
out: &mut *self.out,
replacer_fn: self.replacer_fn,
replacer_whitelist: self.replacer_whitelist,
space: self.space,
indent_level: self.indent_level,
}
}
/// 派生下一缩进层上下文:共享同一环检表与输出流,indent_level 加一
/// (对象/数组展开内递归回值位序列化时用)。
fn child(&mut self) -> StringifyCtx<'data, '_> {
StringifyCtx {
visited: &mut *self.visited,
out: &mut *self.out,
replacer_fn: self.replacer_fn,
replacer_whitelist: self.replacer_whitelist,
space: self.space,
indent_level: self.indent_level + 1,
}
}
}
/// 值位序列化(SerializeValue 及对象/数组展开)。toJSON 钩子不在本入口应用—— /// 值位序列化(SerializeValue 及对象/数组展开)。toJSON 钩子不在本入口应用——
/// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array` /// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array`
/// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序 /// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序
/// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。 /// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。
#[allow(clippy::too_many_arguments)] fn jsvalue_to_json<H: VmHost>(vm: &mut H, val: JsValue, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
fn jsvalue_to_json<H: VmHost>(
vm: &mut H, val: JsValue, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>, replacer_fn: Option<JsValue>,
replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
if val.is_null() { if val.is_null() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
} else if val.is_undefined() { } else if val.is_undefined() {
} else if val.is_symbol() { } else if val.is_symbol() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
} else if val.is_bool() { } else if val.is_bool() {
push_ascii(out, if val.as_bool() { "true" } else { "false" }); push_ascii(ctx.out, if val.as_bool() { "true" } else { "false" });
} else if val.is_int() { } else if val.is_int() {
push_ascii(out, &val.as_int().to_string()); push_ascii(ctx.out, &val.as_int().to_string());
} else if val.is_double() { } else if val.is_double() {
let n = val.as_double(); let n = val.as_double();
if !n.is_finite() { if !n.is_finite() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
} else { } else {
push_number(out, n); push_number(ctx.out, n);
} }
} else if val.is_bigint() { } else if val.is_bigint() {
// SerializeJSONProperty 步 10:BigInt 无 JSON 文本形态,抛 TypeError。 // SerializeJSONProperty 步 10:BigInt 无 JSON 文本形态,抛 TypeError。
@@ -1024,11 +1057,11 @@ fn jsvalue_to_json<H: VmHost>(
} else if val.is_string() { } else if val.is_string() {
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。 // SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
let units = unsafe { (*val.as_string_ptr()).units() }; let units = unsafe { (*val.as_string_ptr()).units() };
stringify_string_units(&units, out); stringify_string_units(&units, ctx.out);
} else if val.is_object() { } else if val.is_object() {
let obj_ptr = val.as_js_object_ptr(); let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() { if obj_ptr.is_null() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
return Ok(()); return Ok(());
} }
@@ -1040,12 +1073,12 @@ fn jsvalue_to_json<H: VmHost>(
let raw_val = obj.get_prop_at(0); let raw_val = obj.get_prop_at(0);
// SAFETY: rawJSON 属性构造期即写入字符串值。 // SAFETY: rawJSON 属性构造期即写入字符串值。
let raw_text = unsafe { (*raw_val.as_string_ptr()).to_owned_string() }; let raw_text = unsafe { (*raw_val.as_string_ptr()).to_owned_string() };
push_ascii(out, &raw_text); push_ascii(ctx.out, &raw_text);
return Ok(()); return Ok(());
} }
} }
if !visited.insert(obj_ptr as *const JsObject) { if !ctx.visited.insert(obj_ptr as *const JsObject) {
return Err(crate::error::create_type_error(vm, "Converting circular structure to JSON")); return Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
} }
@@ -1058,23 +1091,23 @@ fn jsvalue_to_json<H: VmHost>(
let exc = vm let exc = vm
.take_uncaught_value() .take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)); .unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
visited.remove(&(obj_ptr as *const JsObject)); ctx.visited.remove(&(obj_ptr as *const JsObject));
return Err(exc); return Err(exc);
} }
}; };
if n.is_finite() { if n.is_finite() {
push_number(out, n); push_number(ctx.out, n);
} else { } else {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
} }
visited.remove(&(obj_ptr as *const JsObject)); ctx.visited.remove(&(obj_ptr as *const JsObject));
return Ok(()); return Ok(());
} }
// 步 4b:装箱 Boolean → [[BooleanData]]。 // 步 4b:装箱 Boolean → [[BooleanData]]。
if obj.is_boolean_obj() { if obj.is_boolean_obj() {
let b = obj.boxed_value(); let b = obj.boxed_value();
push_ascii(out, if b.is_bool() && b.as_bool() { "true" } else { "false" }); push_ascii(ctx.out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
visited.remove(&(obj_ptr as *const JsObject)); ctx.visited.remove(&(obj_ptr as *const JsObject));
return Ok(()); return Ok(());
} }
// 步 4d:装箱 BigInt 无条件解包 [[BigIntData]],后续步 10 抛 TypeError。 // 步 4d:装箱 BigInt 无条件解包 [[BigIntData]],后续步 10 抛 TypeError。
@@ -1090,21 +1123,21 @@ fn jsvalue_to_json<H: VmHost>(
let exc = vm let exc = vm
.take_uncaught_value() .take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)); .unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
visited.remove(&(obj_ptr as *const JsObject)); ctx.visited.remove(&(obj_ptr as *const JsObject));
return Err(exc); return Err(exc);
} }
}; };
let units = vm.string_units(s); let units = vm.string_units(s);
stringify_string_units(&units, out); stringify_string_units(&units, ctx.out);
} else if obj.is_typed_array_obj() { } else if obj.is_typed_array_obj() {
stringify_typed_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?; stringify_typed_array(vm, obj, ctx.same())?;
} else if obj.is_array() { } else if obj.is_array() {
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?; stringify_array(vm, obj, ctx.same())?;
} else { } else {
stringify_object(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?; stringify_object(vm, obj, ctx.same())?;
} }
visited.remove(&(obj_ptr as *const JsObject)); ctx.visited.remove(&(obj_ptr as *const JsObject));
} }
Ok(()) Ok(())
} }
@@ -1153,17 +1186,13 @@ fn stringify_string_units(units: &[u16], out: &mut Vec<u16>) {
push_ascii(out, "\""); push_ascii(out, "\"");
} }
#[allow(clippy::too_many_arguments)] fn stringify_object<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
fn stringify_object<H: VmHost>( let has_space = !ctx.space.is_empty();
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>, push_ascii(ctx.out, "{");
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
push_ascii(out, "{");
// 键集:白名单给定时 K = P(列表序,不限自身可枚举,值读走 Get); // 键集:白名单给定时 K = P(列表序,不限自身可枚举,值读走 Get);
// 无白名单走自身可枚举序(EnumerableOwnPropertyNames)。 // 无白名单走自身可枚举序(EnumerableOwnPropertyNames)。
let entries: Vec<(Vec<u16>, u32, Option<u32>)> = if let Some(whitelist) = replacer_whitelist { let entries: Vec<(Vec<u16>, u32, Option<u32>)> = if let Some(whitelist) = ctx.replacer_whitelist {
whitelist whitelist
.iter() .iter()
.map(|name| { .map(|name| {
@@ -1198,7 +1227,7 @@ fn stringify_object<H: VmHost>(
let val = call_to_json(vm, val, &units)?; let val = call_to_json(vm, val, &units)?;
// replacer 函数回调。 // replacer 函数回调。
let val = if let Some(replacer) = replacer_fn { let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string_units_owned(units.clone()); let key_val = vm.new_string_units_owned(units.clone());
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) { match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
Ok(v) => { Ok(v) => {
@@ -1227,55 +1256,51 @@ fn stringify_object<H: VmHost>(
} }
if !first { if !first {
push_ascii(out, ","); push_ascii(ctx.out, ",");
} }
first = false; first = false;
if has_space { if has_space {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level + 1 { for _ in 0..ctx.indent_level + 1 {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
stringify_string_units(&units, out); stringify_string_units(&units, ctx.out);
push_ascii(out, ":"); push_ascii(ctx.out, ":");
push_ascii(out, " "); push_ascii(ctx.out, " ");
} else { } else {
stringify_string_units(&units, out); stringify_string_units(&units, ctx.out);
push_ascii(out, ":"); push_ascii(ctx.out, ":");
} }
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?; jsvalue_to_json(vm, val, ctx.child())?;
} }
if has_space && !first { if has_space && !first {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level { for _ in 0..ctx.indent_level {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
} }
push_ascii(out, "}"); push_ascii(ctx.out, "}");
Ok(()) Ok(())
} }
#[allow(clippy::too_many_arguments)] fn stringify_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
fn stringify_array<H: VmHost>( let has_space = !ctx.space.is_empty();
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>, push_ascii(ctx.out, "[");
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
push_ascii(out, "[");
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let len = obj.prop_count() as usize; let len = obj.prop_count() as usize;
for i in 0..len { for i in 0..len {
if i > 0 { if i > 0 {
push_ascii(out, ","); push_ascii(ctx.out, ",");
} }
if has_space { if has_space {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level + 1 { for _ in 0..ctx.indent_level + 1 {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
} }
@@ -1287,12 +1312,12 @@ fn stringify_array<H: VmHost>(
let val = call_to_json(vm, val, &index_units)?; let val = call_to_json(vm, val, &index_units)?;
// replacer 函数回调。 // replacer 函数回调。
let val = if let Some(replacer) = replacer_fn { let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string(&index_str); let key_val = vm.new_string(&index_str);
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) { match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
Ok(v) => { Ok(v) => {
if v.is_undefined() { if v.is_undefined() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
continue; continue;
} }
v v
@@ -1313,39 +1338,35 @@ fn stringify_array<H: VmHost>(
!ptr.is_null() && unsafe { (*ptr).is_function() } !ptr.is_null() && unsafe { (*ptr).is_function() }
}; };
if is_function || val.is_undefined() { if is_function || val.is_undefined() {
push_ascii(out, "null"); push_ascii(ctx.out, "null");
} else { } else {
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?; jsvalue_to_json(vm, val, ctx.child())?;
} }
} }
if has_space && len > 0 { if has_space && len > 0 {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level { for _ in 0..ctx.indent_level {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
} }
push_ascii(out, "]"); push_ascii(ctx.out, "]");
Ok(()) Ok(())
} }
/// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链), /// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链),
/// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。 /// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。
#[allow(clippy::too_many_arguments)] fn stringify_typed_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
fn stringify_typed_array<H: VmHost>( let has_space = !ctx.space.is_empty();
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>,
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let view = crate::typed_array::get_typed_array_data(vm, this_val)?; let view = crate::typed_array::get_typed_array_data(vm, this_val)?;
// live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。 // live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。
let len = crate::typed_array::ta_live_length(view); let len = crate::typed_array::ta_live_length(view);
push_ascii(out, "{"); push_ascii(ctx.out, "{");
// 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、 // 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、
// 非索引名经 Get 读命名键如 length);无白名单走升序元素序。 // 非索引名经 Get 读命名键如 length);无白名单走升序元素序。
let entries: Vec<(Vec<u16>, JsValue)> = if let Some(whitelist) = replacer_whitelist { let entries: Vec<(Vec<u16>, JsValue)> = if let Some(whitelist) = ctx.replacer_whitelist {
let mut list: Vec<(Vec<u16>, JsValue)> = Vec::new(); let mut list: Vec<(Vec<u16>, JsValue)> = Vec::new();
for name in whitelist { for name in whitelist {
let si = vm.string_key_si(name); let si = vm.string_key_si(name);
@@ -1377,7 +1398,7 @@ fn stringify_typed_array<H: VmHost>(
let val = call_to_json(vm, val, &index_units)?; let val = call_to_json(vm, val, &index_units)?;
// replacer 函数回调。 // replacer 函数回调。
let val = if let Some(replacer) = replacer_fn { let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string_units_owned(index_units.clone()); let key_val = vm.new_string_units_owned(index_units.clone());
match vm.call_function_sync(replacer, this_val, &[key_val, val]) { match vm.call_function_sync(replacer, this_val, &[key_val, val]) {
Ok(v) => { Ok(v) => {
@@ -1406,32 +1427,32 @@ fn stringify_typed_array<H: VmHost>(
} }
if !first { if !first {
push_ascii(out, ","); push_ascii(ctx.out, ",");
} }
first = false; first = false;
if has_space { if has_space {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level + 1 { for _ in 0..ctx.indent_level + 1 {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
stringify_string_units(&index_units, out); stringify_string_units(&index_units, ctx.out);
push_ascii(out, ":"); push_ascii(ctx.out, ":");
push_ascii(out, " "); push_ascii(ctx.out, " ");
} else { } else {
stringify_string_units(&index_units, out); stringify_string_units(&index_units, ctx.out);
push_ascii(out, ":"); push_ascii(ctx.out, ":");
} }
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?; jsvalue_to_json(vm, val, ctx.child())?;
} }
if has_space && !first { if has_space && !first {
push_ascii(out, "\n"); push_ascii(ctx.out, "\n");
for _ in 0..indent_level { for _ in 0..ctx.indent_level {
out.extend_from_slice(space); ctx.out.extend_from_slice(ctx.space);
} }
} }
push_ascii(out, "}"); push_ascii(ctx.out, "}");
Ok(()) Ok(())
} }
+40 -18
View File
@@ -26,6 +26,14 @@ pub enum LastChunkHandling {
StopBeforePartial, StopBeforePartial,
} }
/// FromBase64 解码策略:字母表、尾块处理与长度上限三项。
#[derive(Debug, Clone, Copy)]
pub struct DecodeConfig {
pub alphabet: Base64Alphabet,
pub handling: LastChunkHandling,
pub max_len: Option<usize>,
}
const B64_ALPHABET_STANDARD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const B64_ALPHABET_STANDARD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const B64_ALPHABET_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; const B64_ALPHABET_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -62,9 +70,9 @@ fn base64_value(unit: u16, alphabet: Base64Alphabet) -> Option<u32> {
/// - `chunk` 恰 4 字符;调用方保证只在此处解码满块。 /// - `chunk` 恰 4 字符;调用方保证只在此处解码满块。
/// - 返回 `(24 位值, 解码字节数 1-3)`:24 位值按 4 槽位左对齐(padding 槽补 0), /// - 返回 `(24 位值, 解码字节数 1-3)`:24 位值按 4 槽位左对齐(padding 槽补 0),
/// 字节数 = 3 - padding 数。 /// 字节数 = 3 - padding 数。
fn decode_full_chunk( fn decode_full_chunk(chunk: [u16; 4], config: DecodeConfig) -> Result<(u32, usize), DecodeError> {
chunk: [u16; 4], alphabet: Base64Alphabet, handling: LastChunkHandling, let alphabet = config.alphabet;
) -> Result<(u32, usize), DecodeError> { let handling = config.handling;
let pad = match (chunk[2] == 0x3d, chunk[3] == 0x3d) { let pad = match (chunk[2] == 0x3d, chunk[3] == 0x3d) {
(false, false) => 0, (false, false) => 0,
(false, true) => 1, (false, true) => 1,
@@ -108,11 +116,13 @@ fn write_chunk_bytes(write: &mut dyn FnMut(usize, u8), written: usize, bits: u32
/// `clen - 1` 字节(2 字符 1 字节、3 字符 2 字节)并写出。 /// `clen - 1` 字节(2 字符 1 字节、3 字符 2 字节)并写出。
/// - 成功 `read`:解出字节的 loose 路径取块后消费位置;各"停在块前"路径 /// - 成功 `read`:解出字节的 loose 路径取块后消费位置;各"停在块前"路径
/// (sbp、余量不足)取块前位置 `chunk_start`。 /// (sbp、余量不足)取块前位置 `chunk_start`。
#[allow(clippy::too_many_arguments)]
fn finish_partial( fn finish_partial(
chunk: [u16; 4], clen: usize, alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>, chunk: [u16; 4], clen: usize, config: DecodeConfig, write: &mut dyn FnMut(usize, u8), mut written: usize,
write: &mut dyn FnMut(usize, u8), mut written: usize, chunk_start: usize, read: usize, chunk_start: usize, read: usize,
) -> Result<(usize, usize), DecodeError> { ) -> Result<(usize, usize), DecodeError> {
let alphabet = config.alphabet;
let handling = config.handling;
let max_len = config.max_len;
if (0..clen).any(|i| chunk[i] == 0x3d) { if (0..clen).any(|i| chunk[i] == 0x3d) {
let trailing = (0..clen).rev().take_while(|&i| chunk[i] == 0x3d).count(); let trailing = (0..clen).rev().take_while(|&i| chunk[i] == 0x3d).count();
if clen == 3 && trailing == 1 && handling == LastChunkHandling::StopBeforePartial { if clen == 3 && trailing == 1 && handling == LastChunkHandling::StopBeforePartial {
@@ -166,9 +176,9 @@ fn finish_partial(
/// - `Ok((read, written))` 中 `read` 为成功语义下的消费位置(含跳过空白, /// - `Ok((read, written))` 中 `read` 为成功语义下的消费位置(含跳过空白,
/// 停止路径为块前位置)。 /// 停止路径为块前位置)。
pub fn decode_base64( pub fn decode_base64(
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>, units: &[u16], config: DecodeConfig, write: &mut dyn FnMut(usize, u8),
write: &mut dyn FnMut(usize, u8),
) -> Result<(usize, usize), DecodeError> { ) -> Result<(usize, usize), DecodeError> {
let max_len = config.max_len;
if max_len == Some(0) { if max_len == Some(0) {
return Ok((0, 0)); return Ok((0, 0));
} }
@@ -197,7 +207,7 @@ pub fn decode_base64(
return Err(DecodeError); return Err(DecodeError);
} }
} }
let (bits, count) = decode_full_chunk(chunk, alphabet, handling)?; let (bits, count) = decode_full_chunk(chunk, config)?;
if let Some(m) = max_len { if let Some(m) = max_len {
if written + count > m { if written + count > m {
return Ok((chunk_start, written)); return Ok((chunk_start, written));
@@ -211,7 +221,7 @@ pub fn decode_base64(
} }
} }
0 => return Ok((read, written)), 0 => return Ok((read, written)),
_ => return finish_partial(chunk, clen, alphabet, handling, max_len, write, written, chunk_start, read), _ => return finish_partial(chunk, clen, config, write, written, chunk_start, read),
} }
} }
} }
@@ -302,8 +312,16 @@ mod tests {
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>, units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
) -> Result<(usize, Vec<u8>), DecodeError> { ) -> Result<(usize, Vec<u8>), DecodeError> {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
decode_base64(units, alphabet, handling, max_len, &mut |_, b| bytes.push(b)) decode_base64(
.map(|(r, w)| (r, bytes.into_iter().take(w).collect())) units,
DecodeConfig {
alphabet,
handling,
max_len,
},
&mut |_, b| bytes.push(b),
)
.map(|(r, w)| (r, bytes.into_iter().take(w).collect()))
} }
fn decode_hex_full(units: &[u16], max_len: Option<usize>) -> Result<(usize, Vec<u8>), DecodeError> { fn decode_hex_full(units: &[u16], max_len: Option<usize>) -> Result<(usize, Vec<u8>), DecodeError> {
@@ -475,9 +493,11 @@ mod tests {
let mut out = Vec::new(); let mut out = Vec::new();
let r = decode_base64( let r = decode_base64(
&units("MjYyZm.9v"), &units("MjYyZm.9v"),
Base64Alphabet::Standard, DecodeConfig {
LastChunkHandling::Loose, alphabet: Base64Alphabet::Standard,
None, handling: LastChunkHandling::Loose,
max_len: None,
},
&mut |i, b| out.insert(i, b), &mut |i, b| out.insert(i, b),
); );
assert!(r.is_err()); assert!(r.is_err());
@@ -486,9 +506,11 @@ mod tests {
let mut out = Vec::new(); let mut out = Vec::new();
let r = decode_base64( let r = decode_base64(
&units("MjYyZg==="), &units("MjYyZg==="),
Base64Alphabet::Standard, DecodeConfig {
LastChunkHandling::Loose, alphabet: Base64Alphabet::Standard,
None, handling: LastChunkHandling::Loose,
max_len: None,
},
&mut |i, b| out.insert(i, b), &mut |i, b| out.insert(i, b),
); );
assert!(r.is_err()); assert!(r.is_err());
@@ -86,6 +86,21 @@ pub(crate) struct InternalDuration {
pub(crate) time_ns: i128, 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 { impl InternalDuration {
/// 转十分量数组:时间部分按向零截断分解为时/分/秒/毫秒/微秒/纳秒(与时长同号)。 /// 转十分量数组:时间部分按向零截断分解为时/分/秒/毫秒/微秒/纳秒(与时长同号)。
pub(crate) fn to_values(&self) -> [f64; 10] { pub(crate) fn to_values(&self) -> [f64; 10] {
@@ -394,11 +409,11 @@ pub(crate) struct NudgeCalendarResult {
/// ///
/// `epoch_of_date` 是窗口端点日期的纪元换算闭包:plain 面传墙钟换算(行为逐字节 /// `epoch_of_date` 是窗口端点日期的纪元换算闭包:plain 面传墙钟换算(行为逐字节
/// 不变),zoned 面传 GetEpochNanosecondsFor(compatible) 换算。 /// 不变),zoned 面传 GetEpochNanosecondsFor(compatible) 换算。
#[allow(clippy::too_many_arguments)]
pub(crate) fn nudge_to_calendar_unit( pub(crate) fn nudge_to_calendar_unit(
sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128), increment: i128, sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128),
unit: usize, mode: InstantRoundingMode, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>, spec: RoundingSpec, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>,
) -> Result<NudgeCalendarResult, ()> { ) -> Result<NudgeCalendarResult, ()> {
let RoundingSpec { increment, unit, mode } = spec;
let epoch_of = |dur: &[f64; 10]| -> Result<i128, ()> { let epoch_of = |dur: &[f64; 10]| -> Result<i128, ()> {
if date_duration_sign(dur) == 0 { if date_duration_sign(dur) == 0 {
return Ok(origin_epoch); return Ok(origin_epoch);
@@ -744,12 +759,10 @@ pub(crate) fn parse_year_month_difference_settings<H: VmHost>(
/// 的舍入模式并在最后整体取反(不调换两端,调换会改变 0.5 边界所在的年长)。 /// 的舍入模式并在最后整体取反(不调换两端,调换会改变 0.5 边界所在的年长)。
/// no_rounding = 单位索引空间中"最细单位"的索引:该单位且 increment=1 时 /// no_rounding = 单位索引空间中"最细单位"的索引:该单位且 increment=1 时
/// 舍入为恒等变换,跳过 nudge(边界处避免窗口端点的无效范围抛错)。 /// 舍入为恒等变换,跳过 nudge(边界处避免窗口端点的无效范围抛错)。
#[allow(clippy::too_many_arguments)]
pub(crate) fn difference_core<H: VmHost>( 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, vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
settings: DifferenceSettings, since: bool, no_rounding: usize,
) -> NativeResult { ) -> 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, Ok(values) => values,
Err(error) => return NativeResult::Err(error), Err(error) => return NativeResult::Err(error),
}; };
@@ -769,10 +782,8 @@ pub(crate) fn difference_core<H: VmHost>(
/// - Duration.round 的日历单位路径与本函数共用:round 把 duration 应用 relativeTo 后 /// - Duration.round 的日历单位路径与本函数共用:round 把 duration 应用 relativeTo 后
/// 以同样的 start/end 差输入本函数,输出即 balance 后的舍入结果。 /// 以同样的 start/end 差输入本函数,输出即 balance 后的舍入结果。
/// - 时间分量为带符号 i128;start/end 日与时间各自独立,符号由差值推导。 /// - 时间分量为带符号 i128;start/end 日与时间各自独立,符号由差值推导。
#[allow(clippy::too_many_arguments)]
pub(crate) fn nudge_iso_difference<H: VmHost>( 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, vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
settings: DifferenceSettings, since: bool, no_rounding: usize,
) -> Result<[f64; 10], JsValue> { ) -> Result<[f64; 10], JsValue> {
let DifferenceSettings { let DifferenceSettings {
largest_index, largest_index,
@@ -794,17 +805,17 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
// 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。 // 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。
// 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。 // 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。
let origin_epoch = days_from_civil(start.0, start.1, start.2) * DAY_NS + start_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.0, end.1, end.2) * DAY_NS + end_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 date1 = start.date;
let time1_ns = start_time_ns; let time1_ns = start.time_ns;
// plain 面纪元换算闭包:墙钟(日 × 24h + 时间),行为与旧内联算法逐字节一致。 // plain 面纪元换算闭包:墙钟(日 × 24h + 时间),行为与旧内联算法逐字节一致。
let epoch_of_date = |date: (i128, i128, i128)| -> Result<i128, ()> { let epoch_of_date = |date: (i128, i128, i128)| -> Result<i128, ()> {
Ok(days_from_civil(date.0, date.1, date.2) * DAY_NS + time1_ns) Ok(days_from_civil(date.0, date.1, date.2) * DAY_NS + time1_ns)
}; };
let mut date2 = end; let mut date2 = end.date;
let mut time_ns = end_time_ns - 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 {
@@ -906,9 +917,11 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
origin_epoch, origin_epoch,
dest_epoch, dest_epoch,
date1, date1,
increment, RoundingSpec {
smallest_index, increment,
mode, unit: smallest_index,
mode,
},
&epoch_of_date, &epoch_of_date,
) { ) {
Ok(result) => result, Ok(result) => result,
+27 -13
View File
@@ -15,14 +15,14 @@ use super::common::{
use super::difference::{ use super::difference::{
add_date_duration, add_days_iso, balance_instant_difference, compare_iso_date, date_duration_sign, date_until_iso, 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, 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::instant::{instant_rounding_mode, InstantRoundingMode};
use super::time_zone::zone_offset_seconds; use super::time_zone::zone_offset_seconds;
use super::zoned_date_time::{ use super::zoned_date_time::{
add_zoned_datetime, difference_zoned_datetime_with_rounding_vm, difference_zoned_datetime_with_total_vm, 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, 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}; 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(门与单位无关)。 // Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithTotal(门与单位无关)。
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date { 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 target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
let total = native_try!(difference_zoned_datetime_with_total_vm( let pair = ZonedPair {
vm, *epoch_ns, target, time_zone, calendar, unit_index 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)); 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(门与单位无关)。 // Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithRounding(门与单位无关)。
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date { 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 target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
let internal = native_try!(difference_zoned_datetime_with_rounding_vm( let pair = ZonedPair {
vm, ns1: *epoch_ns,
*epoch_ns, ns2: target,
target,
time_zone, time_zone,
calendar, calendar,
largest, };
increment, let settings = DifferenceSettings {
largest_index: largest,
smallest_index, smallest_index,
increment,
mode, mode,
)); };
let internal = native_try!(difference_zoned_datetime_with_rounding_vm(vm, pair, settings));
// largestUnit 为日期类时降为 hour。 // largestUnit 为日期类时降为 hour。
let largest = if largest <= 3 { 4 } else { largest }; let largest = if largest <= 3 { 4 } else { largest };
let result = internal_duration_to_values(&internal, 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, increment,
mode, 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, Ok(values) => values,
Err(error) => return NativeResult::Err(error), 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, 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, 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::duration::duration_like_values;
use super::make_plain_date; use super::make_plain_date;
@@ -623,10 +623,14 @@ pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}; };
difference_core( difference_core(
vm, vm,
(i128::from(start.0), i128::from(start.1), i128::from(start.2)), IsoDateTime {
0, date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
(i128::from(other.0), i128::from(other.1), i128::from(other.2)), time_ns: 0,
0, },
IsoDateTime {
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
time_ns: 0,
},
settings, settings,
false, false,
9, 9,
@@ -651,10 +655,14 @@ pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}; };
difference_core( difference_core(
vm, vm,
(i128::from(start.0), i128::from(start.1), i128::from(start.2)), IsoDateTime {
0, date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
(i128::from(other.0), i128::from(other.1), i128::from(other.2)), time_ns: 0,
0, },
IsoDateTime {
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
time_ns: 0,
},
settings, settings,
true, true,
9, 9,
@@ -14,7 +14,7 @@ use super::{
parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, receiver_obj, round_instant_ns, 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_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, temporal_overflow, valid_iso_date, valid_plain_date_time_range, valid_plain_time, zoned_date_time_plain_parts,
FractionalSecondDigitsInput, FractionalSecondDigitsInput, IsoDateTime,
}; };
// ───────────────────── PlainDateTime 基础方法 ───────────────────── // ───────────────────── PlainDateTime 基础方法 ─────────────────────
@@ -589,10 +589,14 @@ fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
}; };
difference_core( difference_core(
vm, vm,
(i128::from(sy), i128::from(sm), i128::from(sd)), IsoDateTime {
st as i128, date: (i128::from(sy), i128::from(sm), i128::from(sd)),
(i128::from(oy), i128::from(om), i128::from(od)), time_ns: st as i128,
ot as i128, },
IsoDateTime {
date: (i128::from(oy), i128::from(om), i128::from(od)),
time_ns: ot as i128,
},
settings, settings,
since, since,
9, 9,
@@ -11,7 +11,7 @@ use super::{
make_plain_time, parse_any_offset_seconds, 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, 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_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 且作为日期合法 → 歧义。 /// 时间串的日期歧义判定:形如 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, Ok(settings) => settings,
Err(error) => return NativeResult::Err(error), 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)`。 /// `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, 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_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, 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 校验。 /// 读 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")); 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,
)
} }
+181 -33
View File
@@ -104,20 +104,79 @@ fn format_time_zone_annotation_normalizes_offset() {
#[test] #[test]
fn format_zoned_date_time_iso_annotations() { fn format_zoned_date_time_iso_annotations() {
// 偏移/时区/日历注解各取值组合,验证段序 `{offset}[{tz}][{ca}]` 与 critical 前缀。 // 偏移/时区/日历注解各取值组合,验证段序 `{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())); assert_eq!(base, Some("1970-01-01T01:00:00+01:00[+01:00]".to_string()));
// offset never 省略偏移段,时区注解仍显示。 // 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())); assert_eq!(no_offset, Some("1970-01-01T01:00:00[+01:00]".to_string()));
// calendarName always 追加日历注解。 // 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())); assert_eq!(ca_always, Some("1970-01-01T01:00:00+01:00[+01:00][u-ca=iso8601]".to_string()));
// timeZoneName/calendarName critical 均 `!` 置于括号内。 // 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())); assert_eq!(critical, Some("1970-01-01T01:00:00+01:00[!+01:00][!u-ca=iso8601]".to_string()));
// offset critical 偏移段前加 !。 // offset critical 偏移段前加 !。
let offset_critical = let offset_critical = format_zoned_date_time_iso(
format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "critical", "auto", "auto"); 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())); 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() { fn format_zoned_date_time_iso_epoch_rounding_cross_midnight() {
// 2000-01-01 前 1ns 已舍入到 2000-01-01 整点,8 位小数输出。 // 2000-01-01 前 1ns 已舍入到 2000-01-01 整点,8 位小数输出。
let rounded = round_instant_ns(946_684_799_999_999_999, 100, InstantRoundingMode::HalfExpand).unwrap(); 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())); 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(2670), "+00:44:30");
assert_eq!(format_offset_seconds_text(3600), "+01:00"); assert_eq!(format_offset_seconds_text(3600), "+01:00");
assert_eq!(format_time_zone_annotation("-00:44:30", false), "[-00:44:30]"); 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!(iso, Some("1969-12-31T23:15:30-00:44:30[-00:44:30]".to_string()));
assert_eq!( assert_eq!(
format_instant_iso(0, Some(-2670), true, None), 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}; use super::zoned_date_time::{calendar_date_add_iso, AddZonedError};
// 月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange。 // 月末截断: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], 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], false),
Err(AddZonedError::DayOutOfRange)
);
// 闰年二月:constrain 钳到 29。 // 闰年二月: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] #[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 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 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.date, [0.0, 0.0, 0.0, 0.0]);
assert_eq!(d.time_ns, 23_400_000_000_000); assert_eq!(d.time_ns, 23_400_000_000_000);
// 跨 DST 间隙日(23 小时):墙历时刻相同,日期部分 1 天、时间部分 0。 // 跨 DST 间隙日(23 小时):墙历时刻相同,日期部分 1 天、时间部分 0。
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap(); 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 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.date, [0.0, 0.0, 0.0, 1.0]);
assert_eq!(d.time_ns, 0); assert_eq!(d.time_ns, 0);
// dayCorrection 循环推进:12 小时跨间隙日,日期部分 0、时间部分 12 小时。 // dayCorrection 循环推进:12 小时跨间隙日,日期部分 0、时间部分 12 小时。
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap(); 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.date, [0.0, 0.0, 0.0, 0.0]);
assert_eq!(d.time_ns, 43_200_000_000_000); assert_eq!(d.time_ns, 43_200_000_000_000);
// 反向(sign 为正):日期部分为负一天、时间部分 0。 // 反向(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.date, [0.0, 0.0, 0.0, -1.0]);
assert_eq!(d.time_ns, 0); assert_eq!(d.time_ns, 0);
} }
@@ -657,12 +779,30 @@ fn zoned_total_dst_fraction() {
// 整日差(23 小时间隙日):总量 1。 // 整日差(23 小时间隙日):总量 1。
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap(); 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 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); assert!((total - 1.0).abs() < 1e-12);
// 12 小时跨 23 小时间隙日:总量 12/23(按实际日长折算,非 24 小时)。 // 12 小时跨 23 小时间隙日:总量 12/23(按实际日长折算,非 24 小时)。
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap(); 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); 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 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 target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p25h, true).unwrap();
let internal = difference_zoned_datetime_with_rounding( let internal = difference_zoned_datetime_with_rounding(
origin, ZonedPair {
target, ns1: origin,
"Pacific/Apia", ns2: target,
"iso8601", time_zone: "Pacific/Apia",
4, calendar: "iso8601",
1, },
4, DifferenceSettings {
InstantRoundingMode::Trunc, largest_index: 4,
smallest_index: 4,
increment: 1,
mode: InstantRoundingMode::Trunc,
},
) )
.unwrap(); .unwrap();
let values = internal_duration_to_values(&internal, 4); 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 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 target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p1dt26h, true).unwrap();
let internal = difference_zoned_datetime_with_rounding( let internal = difference_zoned_datetime_with_rounding(
origin, ZonedPair {
target, ns1: origin,
"Pacific/Apia", ns2: target,
"iso8601", time_zone: "Pacific/Apia",
3, calendar: "iso8601",
1, },
4, DifferenceSettings {
InstantRoundingMode::Trunc, largest_index: 3,
smallest_index: 4,
increment: 1,
mode: InstantRoundingMode::Trunc,
},
) )
.unwrap(); .unwrap();
let values = internal_duration_to_values(&internal, 4); let values = internal_duration_to_values(&internal, 4);
@@ -19,7 +19,7 @@ use super::common::{
use super::difference::{ use super::difference::{
add_days_iso, bubble_relative_duration, compare_iso_date, date_duration_sign, date_until_iso, difference_core, 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, 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::{ use super::instant::{
instant_rounding_mode, instant_string_without_annotations, parse_instant_string, primitive_to_bigint, 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}]`。 /// ZDT 字符串化核心:epoch 域已舍入后按偏移与注解选项拼 `{date}T{time}.fff{offset}[{tz}][{ca}]`。
/// ///
/// # 边界与前提 /// # 边界与前提
/// - epoch_ns 须已按 quantum 舍入(调用方保证),此处仅做本地墙钟分解。 /// - epoch_ns 须已按 quantum 舍入(调用方保证),此处仅做本地墙钟分解。
/// - offset_name/time_zone_name 取 auto/never/critical,calendar_name 取 auto/never/always/critical。 /// - offset_name/time_zone_name 取 auto/never/critical,calendar_name 取 auto/never/always/critical。
/// - 偏移段 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);时区注解命名区原样、偏移区规范化。 /// - 偏移段 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);时区注解命名区原样、偏移区规范化。
#[expect(clippy::too_many_arguments)]
pub(crate) fn format_zoned_date_time_iso( pub(crate) fn format_zoned_date_time_iso(
epoch_ns: i128, offset_seconds: i64, 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, output_digits: Option<usize>, names: FormatNames,
) -> Option<String> { ) -> 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; const DAY_NS: i128 = 86_400_000_000_000;
let offset_ns = i128::from(offset_seconds).checked_mul(1_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 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, &calendar_id,
include_seconds, include_seconds,
output_digits, output_digits,
&offset_name, FormatNames {
&time_zone_name, offset_name: &offset_name,
&calendar_name, time_zone_name: &time_zone_name,
calendar_name: &calendar_name,
},
) { ) {
Some(output) => NativeResult::Ok(vm.new_string_owned(output)), Some(output) => NativeResult::Ok(vm.new_string_owned(output)),
None => NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime")), 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)); let settings = native_try!(parse_difference_settings(vm, options_value, false, 4));
difference_core( difference_core(
vm, vm,
(i128::from(y_r), i128::from(m_r), i128::from(d_r)), IsoDateTime {
time_ns_r as i128, date: (i128::from(y_r), i128::from(m_r), i128::from(d_r)),
(y_o, m_o, d_o), time_ns: time_ns_r as i128,
time_ns_o, },
IsoDateTime {
date: (y_o, m_o, d_o),
time_ns: time_ns_o,
},
settings, settings,
since, since,
9, 9,
@@ -1710,14 +1726,15 @@ pub(crate) fn add_instant(epoch_ns: i128, time_ns: i128) -> Result<i128, AddZone
Ok(result) Ok(result)
} }
/// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日。 /// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日(values 依次年/月/周/日)。
/// ///
/// # 步骤 /// # 步骤
/// 1. 年/月推进(月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange)。 /// 1. 年/月推进(月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange)。
/// 2. 周*7 + 日推进。 /// 2. 周*7 + 日推进。
pub(crate) fn calendar_date_add_iso( 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> { ) -> 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 total = date.0 * 12 + (date.1 - 1) + years * 12 + months;
let ny = total.div_euclid(12); let ny = total.div_euclid(12);
let nm = total.rem_euclid(12) + 1; 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); return add_instant(epoch_ns, time_ns);
} }
// GetISODateTimeFor:墙历分量。 // 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) { if !valid_plain_date_time_range(year as i32, month as u32, day as u32, time_ns as f64) {
return Err(AddZonedError::OutOfDateTimeRange); return Err(AddZonedError::OutOfDateTimeRange);
} }
// CalendarDateAdd:日期分量(constrain 钳月末,reject 抛错)。 // CalendarDateAdd:日期分量(constrain 钳月末,reject 抛错)。
let added = calendar_date_add_iso( let added = calendar_date_add_iso(
(year, month, day), (year, month, day),
values[0] as i128, [values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
values[1] as i128,
values[2] as i128,
values[3] as i128,
constrain, constrain,
)?; )?;
// 中间日期检查(ISODateTimeWithinLimits)。 // 中间日期检查(ISODateTimeWithinLimits)。
@@ -1887,20 +1903,18 @@ pub(crate) fn map_add_zoned_error<H: VmHost>(vm: &mut H, error: AddZonedError) -
} }
/// DifferenceZonedDateTimeWithRounding 的 VM 包装:错误映射为 RangeError。 /// DifferenceZonedDateTimeWithRounding 的 VM 包装:错误映射为 RangeError。
#[allow(clippy::too_many_arguments)]
pub(crate) fn difference_zoned_datetime_with_rounding_vm<H: VmHost>( 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, vm: &mut H, pair: ZonedPair, settings: DifferenceSettings,
smallest_unit: usize, mode: InstantRoundingMode,
) -> Result<InternalDuration, JsValue> { ) -> 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)) .map_err(|error| map_add_zoned_error(vm, error))
} }
/// DifferenceZonedDateTimeWithTotal 的 VM 包装:错误映射为 RangeError。 /// DifferenceZonedDateTimeWithTotal 的 VM 包装:错误映射为 RangeError。
pub(crate) fn difference_zoned_datetime_with_total_vm<H: VmHost>( 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> { ) -> 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)) .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。 /// - 时区名非法(不在区表且非固定偏移串)返回 InvalidTimeZone。
pub(crate) fn get_iso_date_time_for( pub(crate) fn get_iso_date_time_for(time_zone: &str, epoch_ns: i128) -> Result<IsoDateTime, AddZonedError> {
time_zone: &str, epoch_ns: i128,
) -> Result<((i128, i128, i128), i128), AddZonedError> {
let offset_seconds = zone_offset_seconds(time_zone, epoch_ns.div_euclid(1_000_000_000) as i64) let offset_seconds = zone_offset_seconds(time_zone, epoch_ns.div_euclid(1_000_000_000) as i64)
.ok_or(AddZonedError::InvalidTimeZone)?; .ok_or(AddZonedError::InvalidTimeZone)?;
let local_ns = epoch_ns + i128::from(offset_seconds) * 1_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 days = local_ns.div_euclid(DAY_NS);
let time_ns = local_ns.rem_euclid(DAY_NS); let time_ns = local_ns.rem_euclid(DAY_NS);
let (year, month, day) = civil_from_days(days); 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 消歧):墙历日期时间的纪元。 /// 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 的差,平衡后结果中无大于 /// DifferenceZonedDateTime(规范 6.5.6):两个 epoch 的差,平衡后结果中无大于
/// largestUnit 的非零单位,计入日历 reckoning 与时区偏移变化。 /// largestUnit 的非零单位,计入日历 reckoning 与时区偏移变化。
/// ///
@@ -1972,13 +1998,20 @@ fn time_duration_sign(ns: i128) -> i128 {
/// - 全程 i128 纳秒;中间点时间取起点墙历时间(规范 (intermediateDate, startDT.Time))。 /// - 全程 i128 纳秒;中间点时间取起点墙历时间(规范 (intermediateDate, startDT.Time))。
/// - calendar 参数透传(引擎仅 iso8601 日历)。 /// - calendar 参数透传(引擎仅 iso8601 日历)。
pub(crate) fn difference_zoned_datetime( 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> { ) -> Result<InternalDuration, AddZonedError> {
let ns1 = pair.ns1;
let ns2 = pair.ns2;
let time_zone = pair.time_zone;
if ns1 == ns2 { if ns1 == ns2 {
return Ok(InternalDuration { date: [0.0; 4], time_ns: 0 }); return Ok(InternalDuration { date: [0.0; 4], time_ns: 0 });
} }
let (start_date, start_time) = get_iso_date_time_for(time_zone, ns1)?; let start = get_iso_date_time_for(time_zone, ns1)?;
let (end_date, end_time) = get_iso_date_time_for(time_zone, ns2)?; 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 { if compare_iso_date(start_date, end_date) == 0 {
return Ok(InternalDuration { return Ok(InternalDuration {
date: [0.0; 4], date: [0.0; 4],
@@ -2023,11 +2056,16 @@ pub(crate) fn difference_zoned_datetime(
/// ///
/// # 边界与前提 /// # 边界与前提
/// - smallestUnit 不大于 largestUnit(调用方已校验)。 /// - smallestUnit 不大于 largestUnit(调用方已校验)。
#[allow(clippy::too_many_arguments)]
pub(crate) fn difference_zoned_datetime_with_rounding( 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, pair: ZonedPair, settings: DifferenceSettings,
mode: InstantRoundingMode,
) -> Result<InternalDuration, AddZonedError> { ) -> 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 { if largest_unit >= 4 {
let quantum = TIME_UNIT_NS[smallest_unit - 4] let quantum = TIME_UNIT_NS[smallest_unit - 4]
.checked_mul(increment) .checked_mul(increment)
@@ -2038,22 +2076,12 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
time_ns: rounded, 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 { if smallest_unit == 9 && increment == 1 {
return Ok(difference); return Ok(difference);
} }
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?; let iso = get_iso_date_time_for(time_zone, ns1)?;
round_relative_duration_zoned( round_relative_duration_zoned(difference, ns1, ns2, iso, time_zone, settings)
difference,
ns1,
ns2,
(iso_date, iso_time),
time_zone,
largest_unit,
increment,
smallest_unit,
mode,
)
} }
/// RoundRelativeDuration(规范 7.5.38)的 zoned 路由:日历单位(或 day)走日历 /// RoundRelativeDuration(规范 7.5.38)的 zoned 路由:日历单位(或 day)走日历
@@ -2063,33 +2091,32 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
/// 1. 路由:smallestUnit 为日历单位或 day → NudgeToCalendarUnit(zoned 闭包); /// 1. 路由:smallestUnit 为日历单位或 day → NudgeToCalendarUnit(zoned 闭包);
/// 否则 NudgeToZonedTime。 /// 否则 NudgeToZonedTime。
/// 2. 扩窗且 smallestUnit 非 week → BubbleRelativeDuration(startUnit 取 smallestUnit 与 day 的较大者)。 /// 2. 扩窗且 smallestUnit 非 week → BubbleRelativeDuration(startUnit 取 smallestUnit 与 day 的较大者)。
#[allow(clippy::too_many_arguments)]
fn round_relative_duration_zoned( fn round_relative_duration_zoned(
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,
largest_unit: usize, increment: i128, smallest_unit: usize, mode: InstantRoundingMode, settings: DifferenceSettings,
) -> Result<InternalDuration, AddZonedError> { ) -> 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 values = difference.to_values();
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 }; 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 epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
// zoned 面时区恒在场:irregular = smallestUnit 为日历单位或 day。 // zoned 面时区恒在场:irregular = smallestUnit 为日历单位或 day。
let irregular = smallest_unit <= 3; let irregular = smallest_unit <= 3;
let spec = RoundingSpec {
increment,
unit: smallest_unit,
mode,
};
let (mut result, nudged_epoch, did_expand) = if irregular { let (mut result, nudged_epoch, did_expand) = if irregular {
let outcome = nudge_to_calendar_unit( let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
sign, .map_err(|_| AddZonedError::OutOfDateTimeRange)?;
&values,
origin_ns,
dest_ns,
iso_date,
increment,
smallest_unit,
mode,
&epoch_of_date,
)
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
(outcome.duration, outcome.nudged_epoch, outcome.did_expand) (outcome.duration, outcome.nudged_epoch, outcome.did_expand)
} else { } 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 { if did_expand && smallest_unit != 2 {
let start_unit = smallest_unit.min(3); let start_unit = smallest_unit.min(3);
@@ -2113,18 +2140,18 @@ fn round_relative_duration_zoned(
/// ///
/// # 边界与前提 /// # 边界与前提
/// - unit 为时间单位(时到纳秒);day 单位走日历窗口路径,不进本函数。 /// - unit 为时间单位(时到纳秒);day 单位走日历窗口路径,不进本函数。
#[allow(clippy::too_many_arguments)]
fn nudge_to_zoned_time( fn nudge_to_zoned_time(
sign: i128, values: &[f64; 10], iso_date: (i128, i128, i128), iso_time: i128, time_zone: &str, increment: i128, sign: i128, values: &[f64; 10], iso: IsoDateTime, time_zone: &str, spec: RoundingSpec,
unit: usize, mode: InstantRoundingMode,
) -> Result<([f64; 10], i128, bool), AddZonedError> { ) -> 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 time_ns = duration_time_only_nanoseconds(values).ok_or(AddZonedError::OutOfTimeRange)?;
let start = calendar_date_add_iso( let start = calendar_date_add_iso(
iso_date, iso_date,
values[0] as i128, [values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
values[1] as i128,
values[2] as i128,
values[3] as i128,
true, true,
)?; )?;
let start_epoch = get_epoch_nanos_for(time_zone, start, iso_time)?; let start_epoch = get_epoch_nanos_for(time_zone, start, iso_time)?;
@@ -2156,15 +2183,18 @@ fn nudge_to_zoned_time(
/// 1. unit 为时间单位 → TotalTimeDuration 快路径(纳秒差直接除单位长度)。 /// 1. unit 为时间单位 → TotalTimeDuration 快路径(纳秒差直接除单位长度)。
/// 2. 否则 DifferenceZonedDateTime + TotalRelativeDuration。 /// 2. 否则 DifferenceZonedDateTime + TotalRelativeDuration。
pub(crate) fn difference_zoned_datetime_with_total( 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> { ) -> Result<f64, AddZonedError> {
let ns1 = pair.ns1;
let ns2 = pair.ns2;
let time_zone = pair.time_zone;
if unit >= 4 { if unit >= 4 {
let difference = ns2 - ns1; let difference = ns2 - ns1;
return Ok(difference as f64 / TIME_UNIT_NS[unit - 4] as f64); return Ok(difference as f64 / TIME_UNIT_NS[unit - 4] as f64);
} }
let difference = difference_zoned_datetime(ns1, ns2, time_zone, _calendar, unit)?; let difference = difference_zoned_datetime(pair, unit)?;
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?; let iso = get_iso_date_time_for(time_zone, ns1)?;
total_relative_duration(&difference, ns1, ns2, (iso_date, iso_time), time_zone, unit) total_relative_duration(&difference, ns1, ns2, iso, time_zone, unit)
} }
/// TotalRelativeDuration(规范 7.5.39):时长以 unit 计的总量,相对起点折算。 /// TotalRelativeDuration(规范 7.5.39):时长以 unit 计的总量,相对起点折算。
@@ -2174,27 +2204,23 @@ pub(crate) fn difference_zoned_datetime_with_total(
/// (increment = 1、trunc)取 [[Total]]。 /// (increment = 1、trunc)取 [[Total]]。
/// 2. 否则 TotalTimeDuration(24h 日折算,zoned 面不可达)。 /// 2. 否则 TotalTimeDuration(24h 日折算,zoned 面不可达)。
fn total_relative_duration( 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, unit: usize,
) -> Result<f64, AddZonedError> { ) -> Result<f64, AddZonedError> {
let values = difference.to_values(); let values = difference.to_values();
// zoned 面时区恒在场:unit ≤ day 恒走日历窗口路径。 // zoned 面时区恒在场:unit ≤ day 恒走日历窗口路径。
if unit <= 3 { if unit <= 3 {
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 }; 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 epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
let outcome = nudge_to_calendar_unit( let spec = RoundingSpec {
sign, increment: 1,
&values,
origin_ns,
dest_ns,
iso_date,
1,
unit, unit,
InstantRoundingMode::Trunc, mode: InstantRoundingMode::Trunc,
&epoch_of_date, };
) let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
.map_err(|_| AddZonedError::OutOfDateTimeRange)?; .map_err(|_| AddZonedError::OutOfDateTimeRange)?;
return Ok(outcome.total); return Ok(outcome.total);
} }
let time_ns = duration_time_only_nanoseconds(&values).ok_or(AddZonedError::OutOfTimeRange)?; let time_ns = duration_time_only_nanoseconds(&values).ok_or(AddZonedError::OutOfTimeRange)?;
+9 -1
View File
@@ -2701,7 +2701,15 @@ fn ta_decode_base64<H: VmHost>(
handling = ta_handling_option(vm, obj, opts_val)?; handling = ta_handling_option(vm, obj, opts_val)?;
} }
let units = vm.string_units(string).into_owned(); let units = vm.string_units(string).into_owned();
match crate::ta_codec::decode_base64(&units, alphabet, handling, max_len, write) { match crate::ta_codec::decode_base64(
&units,
crate::ta_codec::DecodeConfig {
alphabet,
handling,
max_len,
},
write,
) {
Ok(r) => Ok(r), Ok(r) => Ok(r),
Err(_) => Err(crate::error::create_syntax_error(vm, "invalid base64 input")), Err(_) => Err(crate::error::create_syntax_error(vm, "invalid base64 input")),
} }