mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
refactor: temporal 与 json 长参数收进结构体
This commit is contained in:
+123
-102
@@ -977,46 +977,79 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
let mut output: Vec<u16> = Vec::new();
|
||||
let indent_level: usize = 0;
|
||||
match jsvalue_to_json(
|
||||
vm,
|
||||
value,
|
||||
&mut visited,
|
||||
&mut output,
|
||||
let ctx = StringifyCtx {
|
||||
visited: &mut visited,
|
||||
out: &mut output,
|
||||
replacer_fn,
|
||||
replacer_whitelist.as_ref(),
|
||||
&space,
|
||||
indent_level,
|
||||
) {
|
||||
replacer_whitelist: replacer_whitelist.as_ref(),
|
||||
space: &space,
|
||||
indent_level: 0,
|
||||
};
|
||||
match jsvalue_to_json(vm, value, ctx) {
|
||||
Ok(()) => NativeResult::Ok(vm.new_string_units_owned(output)),
|
||||
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 钩子不在本入口应用——
|
||||
/// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array`
|
||||
/// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序
|
||||
/// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
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> {
|
||||
fn jsvalue_to_json<H: VmHost>(vm: &mut H, val: JsValue, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||||
if val.is_null() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
} else if val.is_undefined() {
|
||||
} else if val.is_symbol() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
} 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() {
|
||||
push_ascii(out, &val.as_int().to_string());
|
||||
push_ascii(ctx.out, &val.as_int().to_string());
|
||||
} else if val.is_double() {
|
||||
let n = val.as_double();
|
||||
if !n.is_finite() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
} else {
|
||||
push_number(out, n);
|
||||
push_number(ctx.out, n);
|
||||
}
|
||||
} else if val.is_bigint() {
|
||||
// SerializeJSONProperty 步 10:BigInt 无 JSON 文本形态,抛 TypeError。
|
||||
@@ -1024,11 +1057,11 @@ fn jsvalue_to_json<H: VmHost>(
|
||||
} else if val.is_string() {
|
||||
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_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() {
|
||||
let obj_ptr = val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1040,12 +1073,12 @@ fn jsvalue_to_json<H: VmHost>(
|
||||
let raw_val = obj.get_prop_at(0);
|
||||
// SAFETY: rawJSON 属性构造期即写入字符串值。
|
||||
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(());
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -1058,23 +1091,23 @@ fn jsvalue_to_json<H: VmHost>(
|
||||
let exc = vm
|
||||
.take_uncaught_value()
|
||||
.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);
|
||||
}
|
||||
};
|
||||
if n.is_finite() {
|
||||
push_number(out, n);
|
||||
push_number(ctx.out, n);
|
||||
} 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(());
|
||||
}
|
||||
// 步 4b:装箱 Boolean → [[BooleanData]]。
|
||||
if obj.is_boolean_obj() {
|
||||
let b = obj.boxed_value();
|
||||
push_ascii(out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
|
||||
visited.remove(&(obj_ptr as *const JsObject));
|
||||
push_ascii(ctx.out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
|
||||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||||
return Ok(());
|
||||
}
|
||||
// 步 4d:装箱 BigInt 无条件解包 [[BigIntData]],后续步 10 抛 TypeError。
|
||||
@@ -1090,21 +1123,21 @@ fn jsvalue_to_json<H: VmHost>(
|
||||
let exc = vm
|
||||
.take_uncaught_value()
|
||||
.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);
|
||||
}
|
||||
};
|
||||
let units = vm.string_units(s);
|
||||
stringify_string_units(&units, out);
|
||||
stringify_string_units(&units, ctx.out);
|
||||
} 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() {
|
||||
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
||||
stringify_array(vm, obj, ctx.same())?;
|
||||
} 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(())
|
||||
}
|
||||
@@ -1153,17 +1186,13 @@ fn stringify_string_units(units: &[u16], out: &mut Vec<u16>) {
|
||||
push_ascii(out, "\"");
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn stringify_object<H: VmHost>(
|
||||
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();
|
||||
push_ascii(out, "{");
|
||||
fn stringify_object<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||||
let has_space = !ctx.space.is_empty();
|
||||
push_ascii(ctx.out, "{");
|
||||
|
||||
// 键集:白名单给定时 K = P(列表序,不限自身可枚举,值读走 Get);
|
||||
// 无白名单走自身可枚举序(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
|
||||
.iter()
|
||||
.map(|name| {
|
||||
@@ -1198,7 +1227,7 @@ fn stringify_object<H: VmHost>(
|
||||
let val = call_to_json(vm, val, &units)?;
|
||||
|
||||
// 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());
|
||||
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
|
||||
Ok(v) => {
|
||||
@@ -1227,55 +1256,51 @@ fn stringify_object<H: VmHost>(
|
||||
}
|
||||
|
||||
if !first {
|
||||
push_ascii(out, ",");
|
||||
push_ascii(ctx.out, ",");
|
||||
}
|
||||
first = false;
|
||||
|
||||
if has_space {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level + 1 {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level + 1 {
|
||||
ctx.out.extend_from_slice(ctx.space);
|
||||
}
|
||||
stringify_string_units(&units, out);
|
||||
push_ascii(out, ":");
|
||||
push_ascii(out, " ");
|
||||
stringify_string_units(&units, ctx.out);
|
||||
push_ascii(ctx.out, ":");
|
||||
push_ascii(ctx.out, " ");
|
||||
} else {
|
||||
stringify_string_units(&units, out);
|
||||
push_ascii(out, ":");
|
||||
stringify_string_units(&units, ctx.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 {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level {
|
||||
ctx.out.extend_from_slice(ctx.space);
|
||||
}
|
||||
}
|
||||
push_ascii(out, "}");
|
||||
push_ascii(ctx.out, "}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn stringify_array<H: VmHost>(
|
||||
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();
|
||||
push_ascii(out, "[");
|
||||
fn stringify_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||||
let has_space = !ctx.space.is_empty();
|
||||
push_ascii(ctx.out, "[");
|
||||
|
||||
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||||
let len = obj.prop_count() as usize;
|
||||
for i in 0..len {
|
||||
if i > 0 {
|
||||
push_ascii(out, ",");
|
||||
push_ascii(ctx.out, ",");
|
||||
}
|
||||
|
||||
if has_space {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level + 1 {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level + 1 {
|
||||
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)?;
|
||||
|
||||
// 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);
|
||||
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
|
||||
Ok(v) => {
|
||||
if v.is_undefined() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
continue;
|
||||
}
|
||||
v
|
||||
@@ -1313,39 +1338,35 @@ fn stringify_array<H: VmHost>(
|
||||
!ptr.is_null() && unsafe { (*ptr).is_function() }
|
||||
};
|
||||
if is_function || val.is_undefined() {
|
||||
push_ascii(out, "null");
|
||||
push_ascii(ctx.out, "null");
|
||||
} 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 {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level {
|
||||
ctx.out.extend_from_slice(ctx.space);
|
||||
}
|
||||
}
|
||||
push_ascii(out, "]");
|
||||
push_ascii(ctx.out, "]");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链),
|
||||
/// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn stringify_typed_array<H: VmHost>(
|
||||
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();
|
||||
fn stringify_typed_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||||
let has_space = !ctx.space.is_empty();
|
||||
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)?;
|
||||
// live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。
|
||||
let len = crate::typed_array::ta_live_length(view);
|
||||
push_ascii(out, "{");
|
||||
push_ascii(ctx.out, "{");
|
||||
|
||||
// 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、
|
||||
// 非索引名经 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();
|
||||
for name in whitelist {
|
||||
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)?;
|
||||
|
||||
// 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());
|
||||
match vm.call_function_sync(replacer, this_val, &[key_val, val]) {
|
||||
Ok(v) => {
|
||||
@@ -1406,32 +1427,32 @@ fn stringify_typed_array<H: VmHost>(
|
||||
}
|
||||
|
||||
if !first {
|
||||
push_ascii(out, ",");
|
||||
push_ascii(ctx.out, ",");
|
||||
}
|
||||
first = false;
|
||||
|
||||
if has_space {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level + 1 {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level + 1 {
|
||||
ctx.out.extend_from_slice(ctx.space);
|
||||
}
|
||||
stringify_string_units(&index_units, out);
|
||||
push_ascii(out, ":");
|
||||
push_ascii(out, " ");
|
||||
stringify_string_units(&index_units, ctx.out);
|
||||
push_ascii(ctx.out, ":");
|
||||
push_ascii(ctx.out, " ");
|
||||
} else {
|
||||
stringify_string_units(&index_units, out);
|
||||
push_ascii(out, ":");
|
||||
stringify_string_units(&index_units, ctx.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 {
|
||||
push_ascii(out, "\n");
|
||||
for _ in 0..indent_level {
|
||||
out.extend_from_slice(space);
|
||||
push_ascii(ctx.out, "\n");
|
||||
for _ in 0..ctx.indent_level {
|
||||
ctx.out.extend_from_slice(ctx.space);
|
||||
}
|
||||
}
|
||||
push_ascii(out, "}");
|
||||
push_ascii(ctx.out, "}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ pub enum LastChunkHandling {
|
||||
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_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
@@ -62,9 +70,9 @@ fn base64_value(unit: u16, alphabet: Base64Alphabet) -> Option<u32> {
|
||||
/// - `chunk` 恰 4 字符;调用方保证只在此处解码满块。
|
||||
/// - 返回 `(24 位值, 解码字节数 1-3)`:24 位值按 4 槽位左对齐(padding 槽补 0),
|
||||
/// 字节数 = 3 - padding 数。
|
||||
fn decode_full_chunk(
|
||||
chunk: [u16; 4], alphabet: Base64Alphabet, handling: LastChunkHandling,
|
||||
) -> Result<(u32, usize), DecodeError> {
|
||||
fn decode_full_chunk(chunk: [u16; 4], config: DecodeConfig) -> Result<(u32, usize), DecodeError> {
|
||||
let alphabet = config.alphabet;
|
||||
let handling = config.handling;
|
||||
let pad = match (chunk[2] == 0x3d, chunk[3] == 0x3d) {
|
||||
(false, false) => 0,
|
||||
(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 字节)并写出。
|
||||
/// - 成功 `read`:解出字节的 loose 路径取块后消费位置;各"停在块前"路径
|
||||
/// (sbp、余量不足)取块前位置 `chunk_start`。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn finish_partial(
|
||||
chunk: [u16; 4], clen: usize, alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
|
||||
write: &mut dyn FnMut(usize, u8), mut written: usize, chunk_start: usize, read: usize,
|
||||
chunk: [u16; 4], clen: usize, config: DecodeConfig, write: &mut dyn FnMut(usize, u8), mut written: usize,
|
||||
chunk_start: usize, read: usize,
|
||||
) -> 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) {
|
||||
let trailing = (0..clen).rev().take_while(|&i| chunk[i] == 0x3d).count();
|
||||
if clen == 3 && trailing == 1 && handling == LastChunkHandling::StopBeforePartial {
|
||||
@@ -166,9 +176,9 @@ fn finish_partial(
|
||||
/// - `Ok((read, written))` 中 `read` 为成功语义下的消费位置(含跳过空白,
|
||||
/// 停止路径为块前位置)。
|
||||
pub fn decode_base64(
|
||||
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
|
||||
write: &mut dyn FnMut(usize, u8),
|
||||
units: &[u16], config: DecodeConfig, write: &mut dyn FnMut(usize, u8),
|
||||
) -> Result<(usize, usize), DecodeError> {
|
||||
let max_len = config.max_len;
|
||||
if max_len == Some(0) {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
@@ -197,7 +207,7 @@ pub fn decode_base64(
|
||||
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 written + count > m {
|
||||
return Ok((chunk_start, written));
|
||||
@@ -211,7 +221,7 @@ pub fn decode_base64(
|
||||
}
|
||||
}
|
||||
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>,
|
||||
) -> Result<(usize, Vec<u8>), DecodeError> {
|
||||
let mut bytes = Vec::new();
|
||||
decode_base64(units, alphabet, handling, max_len, &mut |_, b| bytes.push(b))
|
||||
.map(|(r, w)| (r, bytes.into_iter().take(w).collect()))
|
||||
decode_base64(
|
||||
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> {
|
||||
@@ -475,9 +493,11 @@ mod tests {
|
||||
let mut out = Vec::new();
|
||||
let r = decode_base64(
|
||||
&units("MjYyZm.9v"),
|
||||
Base64Alphabet::Standard,
|
||||
LastChunkHandling::Loose,
|
||||
None,
|
||||
DecodeConfig {
|
||||
alphabet: Base64Alphabet::Standard,
|
||||
handling: LastChunkHandling::Loose,
|
||||
max_len: None,
|
||||
},
|
||||
&mut |i, b| out.insert(i, b),
|
||||
);
|
||||
assert!(r.is_err());
|
||||
@@ -486,9 +506,11 @@ mod tests {
|
||||
let mut out = Vec::new();
|
||||
let r = decode_base64(
|
||||
&units("MjYyZg==="),
|
||||
Base64Alphabet::Standard,
|
||||
LastChunkHandling::Loose,
|
||||
None,
|
||||
DecodeConfig {
|
||||
alphabet: Base64Alphabet::Standard,
|
||||
handling: LastChunkHandling::Loose,
|
||||
max_len: None,
|
||||
},
|
||||
&mut |i, b| out.insert(i, b),
|
||||
);
|
||||
assert!(r.is_err());
|
||||
|
||||
@@ -86,6 +86,21 @@ pub(crate) struct InternalDuration {
|
||||
pub(crate) time_ns: i128,
|
||||
}
|
||||
|
||||
/// ISO 日期时间:日期三元(年/月/日)加时间纳秒(不含日分量)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct IsoDateTime {
|
||||
pub(crate) date: (i128, i128, i128),
|
||||
pub(crate) time_ns: i128,
|
||||
}
|
||||
|
||||
/// 取整规格:增量、单位与舍入模式(吸收取整三参)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct RoundingSpec {
|
||||
pub(crate) increment: i128,
|
||||
pub(crate) unit: usize,
|
||||
pub(crate) mode: InstantRoundingMode,
|
||||
}
|
||||
|
||||
impl InternalDuration {
|
||||
/// 转十分量数组:时间部分按向零截断分解为时/分/秒/毫秒/微秒/纳秒(与时长同号)。
|
||||
pub(crate) fn to_values(&self) -> [f64; 10] {
|
||||
@@ -394,11 +409,11 @@ pub(crate) struct NudgeCalendarResult {
|
||||
///
|
||||
/// `epoch_of_date` 是窗口端点日期的纪元换算闭包:plain 面传墙钟换算(行为逐字节
|
||||
/// 不变),zoned 面传 GetEpochNanosecondsFor(compatible) 换算。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn nudge_to_calendar_unit(
|
||||
sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128), increment: i128,
|
||||
unit: usize, mode: InstantRoundingMode, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>,
|
||||
sign: i128, values: &[f64; 10], origin_epoch: i128, dest_epoch: i128, date1: (i128, i128, i128),
|
||||
spec: RoundingSpec, epoch_of_date: &dyn Fn((i128, i128, i128)) -> Result<i128, ()>,
|
||||
) -> Result<NudgeCalendarResult, ()> {
|
||||
let RoundingSpec { increment, unit, mode } = spec;
|
||||
let epoch_of = |dur: &[f64; 10]| -> Result<i128, ()> {
|
||||
if date_duration_sign(dur) == 0 {
|
||||
return Ok(origin_epoch);
|
||||
@@ -744,12 +759,10 @@ pub(crate) fn parse_year_month_difference_settings<H: VmHost>(
|
||||
/// 的舍入模式并在最后整体取反(不调换两端,调换会改变 0.5 边界所在的年长)。
|
||||
/// no_rounding = 单位索引空间中"最细单位"的索引:该单位且 increment=1 时
|
||||
/// 舍入为恒等变换,跳过 nudge(边界处避免窗口端点的无效范围抛错)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_core<H: VmHost>(
|
||||
vm: &mut H, start: (i128, i128, i128), start_time_ns: i128, end: (i128, i128, i128), end_time_ns: i128,
|
||||
settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
) -> NativeResult {
|
||||
let values = match nudge_iso_difference(vm, start, start_time_ns, end, end_time_ns, settings, since, no_rounding) {
|
||||
let values = match nudge_iso_difference(vm, start, end, settings, since, no_rounding) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
@@ -769,10 +782,8 @@ pub(crate) fn difference_core<H: VmHost>(
|
||||
/// - Duration.round 的日历单位路径与本函数共用:round 把 duration 应用 relativeTo 后
|
||||
/// 以同样的 start/end 差输入本函数,输出即 balance 后的舍入结果。
|
||||
/// - 时间分量为带符号 i128;start/end 日与时间各自独立,符号由差值推导。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
vm: &mut H, start: (i128, i128, i128), start_time_ns: i128, end: (i128, i128, i128), end_time_ns: i128,
|
||||
settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
vm: &mut H, start: IsoDateTime, end: IsoDateTime, settings: DifferenceSettings, since: bool, no_rounding: usize,
|
||||
) -> Result<[f64; 10], JsValue> {
|
||||
let DifferenceSettings {
|
||||
largest_index,
|
||||
@@ -794,17 +805,17 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
|
||||
// 规范语义:internal = other - receiver;since 用 NegateRoundingMode 的舍入模式,最后整体取反。
|
||||
// 不调换两端(调换会改变 0.5 边界所在的年长,破坏对称性)。
|
||||
let origin_epoch = days_from_civil(start.0, start.1, start.2) * DAY_NS + start_time_ns;
|
||||
let dest_epoch = days_from_civil(end.0, end.1, end.2) * DAY_NS + end_time_ns;
|
||||
let origin_epoch = days_from_civil(start.date.0, start.date.1, start.date.2) * DAY_NS + start.time_ns;
|
||||
let dest_epoch = days_from_civil(end.date.0, end.date.1, end.date.2) * DAY_NS + end.time_ns;
|
||||
|
||||
let date1 = start;
|
||||
let time1_ns = start_time_ns;
|
||||
let date1 = start.date;
|
||||
let time1_ns = start.time_ns;
|
||||
// plain 面纪元换算闭包:墙钟(日 × 24h + 时间),行为与旧内联算法逐字节一致。
|
||||
let epoch_of_date = |date: (i128, i128, i128)| -> Result<i128, ()> {
|
||||
Ok(days_from_civil(date.0, date.1, date.2) * DAY_NS + time1_ns)
|
||||
};
|
||||
let mut date2 = end;
|
||||
let mut time_ns = end_time_ns - time1_ns;
|
||||
let mut date2 = end.date;
|
||||
let mut time_ns = end.time_ns - time1_ns;
|
||||
let time_sign = if time_ns > 0 {
|
||||
1_i128
|
||||
} else if time_ns < 0 {
|
||||
@@ -906,9 +917,11 @@ pub(crate) fn nudge_iso_difference<H: VmHost>(
|
||||
origin_epoch,
|
||||
dest_epoch,
|
||||
date1,
|
||||
increment,
|
||||
smallest_index,
|
||||
mode,
|
||||
RoundingSpec {
|
||||
increment,
|
||||
unit: smallest_index,
|
||||
mode,
|
||||
},
|
||||
&epoch_of_date,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
|
||||
@@ -15,14 +15,14 @@ use super::common::{
|
||||
use super::difference::{
|
||||
add_date_duration, add_days_iso, balance_instant_difference, compare_iso_date, date_duration_sign, date_until_iso,
|
||||
nudge_iso_difference, nudge_window, plain_date_time_unit_index, round_instant_difference, DifferenceSettings,
|
||||
InternalDuration, DAY_NS, MAX_ISO_DAY,
|
||||
IsoDateTime, InternalDuration, DAY_NS, MAX_ISO_DAY,
|
||||
};
|
||||
use super::instant::{instant_rounding_mode, InstantRoundingMode};
|
||||
use super::time_zone::zone_offset_seconds;
|
||||
use super::zoned_date_time::{
|
||||
add_zoned_datetime, difference_zoned_datetime_with_rounding_vm, difference_zoned_datetime_with_total_vm,
|
||||
disambiguate_possible_epoch_nanoseconds, extract_time_zone_annotation, get_possible_epoch_nanoseconds,
|
||||
map_add_zoned_error, parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts,
|
||||
map_add_zoned_error, parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts, ZonedPair,
|
||||
};
|
||||
use super::{make_duration, parse_plain_date_time_string};
|
||||
|
||||
@@ -708,9 +708,13 @@ pub fn duration_total<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithTotal(门与单位无关)。
|
||||
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date {
|
||||
let target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
|
||||
let total = native_try!(difference_zoned_datetime_with_total_vm(
|
||||
vm, *epoch_ns, target, time_zone, calendar, unit_index
|
||||
));
|
||||
let pair = ZonedPair {
|
||||
ns1: *epoch_ns,
|
||||
ns2: target,
|
||||
time_zone,
|
||||
calendar,
|
||||
};
|
||||
let total = native_try!(difference_zoned_datetime_with_total_vm(vm, pair, unit_index));
|
||||
return NativeResult::Ok(JsValue::float(total));
|
||||
}
|
||||
|
||||
@@ -1369,17 +1373,19 @@ pub fn duration_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// Zoned 载荷:AddZonedDateTime → DifferenceZonedDateTimeWithRounding(门与单位无关)。
|
||||
if let Some(RelativePoint::Zoned { epoch_ns, time_zone, calendar }) = &relative_date {
|
||||
let target = native_try!(add_zoned_datetime(vm, *epoch_ns, time_zone, calendar, &values, true));
|
||||
let internal = native_try!(difference_zoned_datetime_with_rounding_vm(
|
||||
vm,
|
||||
*epoch_ns,
|
||||
target,
|
||||
let pair = ZonedPair {
|
||||
ns1: *epoch_ns,
|
||||
ns2: target,
|
||||
time_zone,
|
||||
calendar,
|
||||
largest,
|
||||
increment,
|
||||
};
|
||||
let settings = DifferenceSettings {
|
||||
largest_index: largest,
|
||||
smallest_index,
|
||||
increment,
|
||||
mode,
|
||||
));
|
||||
};
|
||||
let internal = native_try!(difference_zoned_datetime_with_rounding_vm(vm, pair, settings));
|
||||
// largestUnit 为日期类时降为 hour。
|
||||
let largest = if largest <= 3 { 4 } else { largest };
|
||||
let result = internal_duration_to_values(&internal, largest);
|
||||
@@ -1460,7 +1466,15 @@ pub fn duration_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
increment,
|
||||
mode,
|
||||
};
|
||||
let result = match nudge_iso_difference(vm, rel, 0, end, target_time, settings, false, 9) {
|
||||
let start = IsoDateTime {
|
||||
date: rel,
|
||||
time_ns: 0,
|
||||
};
|
||||
let end_dt = IsoDateTime {
|
||||
date: end,
|
||||
time_ns: target_time,
|
||||
};
|
||||
let result = match nudge_iso_difference(vm, start, end_dt, settings, false, 9) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::common::{
|
||||
is_ctor_call, parse_plain_date_string, plain_date_time_object_parts, receiver_obj, temporal_calendar_id_strict,
|
||||
temporal_number_component, temporal_overflow, zoned_date_time_plain_parts,
|
||||
};
|
||||
use super::difference::{difference_core, parse_difference_settings};
|
||||
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
||||
use super::duration::duration_like_values;
|
||||
use super::make_plain_date;
|
||||
|
||||
@@ -623,10 +623,14 @@ pub fn plain_date_until<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
0,
|
||||
(i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
0,
|
||||
IsoDateTime {
|
||||
date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
false,
|
||||
9,
|
||||
@@ -651,10 +655,14 @@ pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
0,
|
||||
(i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
0,
|
||||
IsoDateTime {
|
||||
date: (i128::from(start.0), i128::from(start.1), i128::from(start.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(other.0), i128::from(other.1), i128::from(other.2)),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
true,
|
||||
9,
|
||||
|
||||
@@ -14,7 +14,7 @@ use super::{
|
||||
parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, receiver_obj, round_instant_ns,
|
||||
temporal_calendar_id_strict, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, valid_iso_date, valid_plain_date_time_range, valid_plain_time, zoned_date_time_plain_parts,
|
||||
FractionalSecondDigitsInput,
|
||||
FractionalSecondDigitsInput, IsoDateTime,
|
||||
};
|
||||
// ───────────────────── PlainDateTime 基础方法 ─────────────────────
|
||||
|
||||
@@ -589,10 +589,14 @@ fn plain_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
||||
};
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(sy), i128::from(sm), i128::from(sd)),
|
||||
st as i128,
|
||||
(i128::from(oy), i128::from(om), i128::from(od)),
|
||||
ot as i128,
|
||||
IsoDateTime {
|
||||
date: (i128::from(sy), i128::from(sm), i128::from(sd)),
|
||||
time_ns: st as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(oy), i128::from(om), i128::from(od)),
|
||||
time_ns: ot as i128,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
make_plain_time, parse_any_offset_seconds, parse_difference_settings, parse_fractional_second_digits,
|
||||
parse_iso_date, receiver_obj, reject_partial_object_with_calendar_or_time_zone, round_instant_ns,
|
||||
temporal_number_component, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, valid_offset_fraction, zoned_date_time_plain_parts, FractionalSecondDigitsInput,
|
||||
temporal_overflow, valid_offset_fraction, zoned_date_time_plain_parts, FractionalSecondDigitsInput, IsoDateTime,
|
||||
};
|
||||
|
||||
/// 时间串的日期歧义判定:形如 YYYY-MM / MMDD / YYYYMM / MM-DD 且作为日期合法 → 歧义。
|
||||
@@ -701,7 +701,20 @@ fn plain_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -> Nat
|
||||
Ok(settings) => settings,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
difference_core(vm, (0, 0, 0), receiver_ns as i128, (0, 0, 0), other_ns as i128, settings, since, 9)
|
||||
difference_core(
|
||||
vm,
|
||||
IsoDateTime {
|
||||
date: (0, 0, 0),
|
||||
time_ns: receiver_ns as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (0, 0, 0),
|
||||
time_ns: other_ns as i128,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainTime.prototype.until(other, options)`。
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::{
|
||||
read_iso2, receiver_obj, reject_partial_object_with_calendar_or_time_zone, temporal_constructor_calendar_id,
|
||||
temporal_number_component, temporal_option_string, temporal_option_value, temporal_overflow,
|
||||
temporal_to_show_calendar, validate_month_day_annotation_suffix, validate_month_day_time_suffix, ShowCalendar,
|
||||
MAX_ISO_DAY,
|
||||
IsoDateTime, MAX_ISO_DAY,
|
||||
};
|
||||
|
||||
/// 读 PlainYearMonth 的 年/月/参考日 三元组(槽 0-2),含 receiver 校验。
|
||||
@@ -884,5 +884,18 @@ fn plain_year_month_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool)
|
||||
{
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "date is out of range"));
|
||||
}
|
||||
difference_core(vm, (ry, rm, 1), 0, (i128::from(oy), i128::from(om), 1), 0, settings, since, 1)
|
||||
difference_core(
|
||||
vm,
|
||||
IsoDateTime {
|
||||
date: (ry, rm, 1),
|
||||
time_ns: 0,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (i128::from(oy), i128::from(om), 1),
|
||||
time_ns: 0,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -104,20 +104,79 @@ fn format_time_zone_annotation_normalizes_offset() {
|
||||
#[test]
|
||||
fn format_zoned_date_time_iso_annotations() {
|
||||
// 偏移/时区/日历注解各取值组合,验证段序 `{offset}[{tz}][{ca}]` 与 critical 前缀。
|
||||
let base = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "auto");
|
||||
let base = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(base, Some("1970-01-01T01:00:00+01:00[+01:00]".to_string()));
|
||||
// offset never 省略偏移段,时区注解仍显示。
|
||||
let no_offset = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "never", "auto", "auto");
|
||||
let no_offset = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "never",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(no_offset, Some("1970-01-01T01:00:00[+01:00]".to_string()));
|
||||
// calendarName always 追加日历注解。
|
||||
let ca_always = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "auto", "always");
|
||||
let ca_always = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "always",
|
||||
},
|
||||
);
|
||||
assert_eq!(ca_always, Some("1970-01-01T01:00:00+01:00[+01:00][u-ca=iso8601]".to_string()));
|
||||
// timeZoneName/calendarName critical 均 `!` 置于括号内。
|
||||
let critical = format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "auto", "critical", "critical");
|
||||
let critical = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "critical",
|
||||
calendar_name: "critical",
|
||||
},
|
||||
);
|
||||
assert_eq!(critical, Some("1970-01-01T01:00:00+01:00[!+01:00][!u-ca=iso8601]".to_string()));
|
||||
// offset critical 偏移段前加 !。
|
||||
let offset_critical =
|
||||
format_zoned_date_time_iso(0, 3600, "+01:00", "iso8601", true, None, "critical", "auto", "auto");
|
||||
let offset_critical = format_zoned_date_time_iso(
|
||||
0,
|
||||
3600,
|
||||
"+01:00",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "critical",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(offset_critical, Some("1970-01-01T01:00:00!+01:00[+01:00]".to_string()));
|
||||
}
|
||||
|
||||
@@ -125,7 +184,19 @@ fn format_zoned_date_time_iso_annotations() {
|
||||
fn format_zoned_date_time_iso_epoch_rounding_cross_midnight() {
|
||||
// 2000-01-01 前 1ns 已舍入到 2000-01-01 整点,8 位小数输出。
|
||||
let rounded = round_instant_ns(946_684_799_999_999_999, 100, InstantRoundingMode::HalfExpand).unwrap();
|
||||
let output = format_zoned_date_time_iso(rounded, 0, "UTC", "iso8601", true, Some(8), "auto", "auto", "auto");
|
||||
let output = format_zoned_date_time_iso(
|
||||
rounded,
|
||||
0,
|
||||
"UTC",
|
||||
"iso8601",
|
||||
true,
|
||||
Some(8),
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(output, Some("2000-01-01T00:00:00.00000000+00:00[UTC]".to_string()));
|
||||
}
|
||||
|
||||
@@ -429,7 +500,19 @@ fn sub_minute_offset_getter_branch() {
|
||||
assert_eq!(format_offset_seconds_text(2670), "+00:44:30");
|
||||
assert_eq!(format_offset_seconds_text(3600), "+01:00");
|
||||
assert_eq!(format_time_zone_annotation("-00:44:30", false), "[-00:44:30]");
|
||||
let iso = format_zoned_date_time_iso(0, -2670, "-00:44:30", "iso8601", true, None, "auto", "auto", "auto");
|
||||
let iso = format_zoned_date_time_iso(
|
||||
0,
|
||||
-2670,
|
||||
"-00:44:30",
|
||||
"iso8601",
|
||||
true,
|
||||
None,
|
||||
FormatNames {
|
||||
offset_name: "auto",
|
||||
time_zone_name: "auto",
|
||||
calendar_name: "auto",
|
||||
},
|
||||
);
|
||||
assert_eq!(iso, Some("1969-12-31T23:15:30-00:44:30[-00:44:30]".to_string()));
|
||||
assert_eq!(
|
||||
format_instant_iso(0, Some(-2670), true, None),
|
||||
@@ -614,10 +697,13 @@ fn calendar_date_add_iso_constrain_semantics() {
|
||||
use super::zoned_date_time::{calendar_date_add_iso, AddZonedError};
|
||||
|
||||
// 月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange。
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), 0, 1, 0, 0, true), Ok((2025, 2, 28)));
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), 0, 1, 0, 0, false), Err(AddZonedError::DayOutOfRange));
|
||||
assert_eq!(calendar_date_add_iso((2025, 1, 31), [0, 1, 0, 0], true), Ok((2025, 2, 28)));
|
||||
assert_eq!(
|
||||
calendar_date_add_iso((2025, 1, 31), [0, 1, 0, 0], false),
|
||||
Err(AddZonedError::DayOutOfRange)
|
||||
);
|
||||
// 闰年二月:constrain 钳到 29。
|
||||
assert_eq!(calendar_date_add_iso((2024, 1, 31), 0, 1, 0, 0, true), Ok((2024, 2, 29)));
|
||||
assert_eq!(calendar_date_add_iso((2024, 1, 31), [0, 1, 0, 0], true), Ok((2024, 2, 29)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -627,25 +713,61 @@ fn zoned_difference_day_correction() {
|
||||
// 同日纯时间差:无日期部分,时间部分为纳秒差。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 6, 15), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 6, 15), 66_600_000_000_000).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 0.0]);
|
||||
assert_eq!(d.time_ns, 23_400_000_000_000);
|
||||
|
||||
// 跨 DST 间隙日(23 小时):墙历时刻相同,日期部分 1 天、时间部分 0。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 43_200_000_000_000).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 1.0]);
|
||||
assert_eq!(d.time_ns, 0);
|
||||
|
||||
// dayCorrection 循环推进:12 小时跨间隙日,日期部分 0、时间部分 12 小时。
|
||||
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap();
|
||||
let d = difference_zoned_datetime(ns1, ns2b, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2: ns2b,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, 0.0]);
|
||||
assert_eq!(d.time_ns, 43_200_000_000_000);
|
||||
|
||||
// 反向(sign 为正):日期部分为负一天、时间部分 0。
|
||||
let d = difference_zoned_datetime(ns2, ns1, "America/New_York", "iso8601", 3).unwrap();
|
||||
let d = difference_zoned_datetime(
|
||||
ZonedPair {
|
||||
ns1: ns2,
|
||||
ns2: ns1,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(d.date, [0.0, 0.0, 0.0, -1.0]);
|
||||
assert_eq!(d.time_ns, 0);
|
||||
}
|
||||
@@ -657,12 +779,30 @@ fn zoned_total_dst_fraction() {
|
||||
// 整日差(23 小时间隙日):总量 1。
|
||||
let ns1 = get_epoch_nanos_for("America/New_York", (2025, 3, 8), 43_200_000_000_000).unwrap();
|
||||
let ns2 = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 43_200_000_000_000).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(ns1, ns2, "America/New_York", "iso8601", 3).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert!((total - 1.0).abs() < 1e-12);
|
||||
|
||||
// 12 小时跨 23 小时间隙日:总量 12/23(按实际日长折算,非 24 小时)。
|
||||
let ns2b = get_epoch_nanos_for("America/New_York", (2025, 3, 9), 0).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(ns1, ns2b, "America/New_York", "iso8601", 3).unwrap();
|
||||
let total = difference_zoned_datetime_with_total(
|
||||
ZonedPair {
|
||||
ns1,
|
||||
ns2: ns2b,
|
||||
time_zone: "America/New_York",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert!((total - 12.0 / 23.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
@@ -681,14 +821,18 @@ fn zoned_round_nudge_to_zoned_time() {
|
||||
let p25h = [0.0_f64, 0.0, 0.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
||||
let target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p25h, true).unwrap();
|
||||
let internal = difference_zoned_datetime_with_rounding(
|
||||
origin,
|
||||
target,
|
||||
"Pacific/Apia",
|
||||
"iso8601",
|
||||
4,
|
||||
1,
|
||||
4,
|
||||
InstantRoundingMode::Trunc,
|
||||
ZonedPair {
|
||||
ns1: origin,
|
||||
ns2: target,
|
||||
time_zone: "Pacific/Apia",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
DifferenceSettings {
|
||||
largest_index: 4,
|
||||
smallest_index: 4,
|
||||
increment: 1,
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let values = internal_duration_to_values(&internal, 4);
|
||||
@@ -698,14 +842,18 @@ fn zoned_round_nudge_to_zoned_time() {
|
||||
let p1dt26h = [0.0_f64, 0.0, 0.0, 1.0, 26.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
||||
let target = add_zoned_datetime_pure(origin, "Pacific/Apia", "iso8601", &p1dt26h, true).unwrap();
|
||||
let internal = difference_zoned_datetime_with_rounding(
|
||||
origin,
|
||||
target,
|
||||
"Pacific/Apia",
|
||||
"iso8601",
|
||||
3,
|
||||
1,
|
||||
4,
|
||||
InstantRoundingMode::Trunc,
|
||||
ZonedPair {
|
||||
ns1: origin,
|
||||
ns2: target,
|
||||
time_zone: "Pacific/Apia",
|
||||
calendar: "iso8601",
|
||||
},
|
||||
DifferenceSettings {
|
||||
largest_index: 3,
|
||||
smallest_index: 4,
|
||||
increment: 1,
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let values = internal_duration_to_values(&internal, 4);
|
||||
|
||||
@@ -19,7 +19,7 @@ use super::common::{
|
||||
use super::difference::{
|
||||
add_days_iso, bubble_relative_duration, compare_iso_date, date_duration_sign, date_until_iso, difference_core,
|
||||
internal_duration_sign, nudge_to_calendar_unit, parse_difference_settings, round_instant_difference,
|
||||
InternalDuration, DAY_NS, MAX_ISO_DAY, TIME_UNIT_NS,
|
||||
DifferenceSettings, IsoDateTime, InternalDuration, RoundingSpec, DAY_NS, MAX_ISO_DAY, TIME_UNIT_NS,
|
||||
};
|
||||
use super::instant::{
|
||||
instant_rounding_mode, instant_string_without_annotations, parse_instant_string, primitive_to_bigint,
|
||||
@@ -533,17 +533,27 @@ pub(crate) fn format_time_zone_annotation(time_zone_id: &str, critical: bool) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// toString 注解名选项:offset / timeZoneName / calendarName 三取值。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct FormatNames<'a> {
|
||||
pub(crate) offset_name: &'a str,
|
||||
pub(crate) time_zone_name: &'a str,
|
||||
pub(crate) calendar_name: &'a str,
|
||||
}
|
||||
|
||||
/// ZDT 字符串化核心:epoch 域已舍入后按偏移与注解选项拼 `{date}T{time}.fff{offset}[{tz}][{ca}]`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - epoch_ns 须已按 quantum 舍入(调用方保证),此处仅做本地墙钟分解。
|
||||
/// - offset_name/time_zone_name 取 auto/never/critical,calendar_name 取 auto/never/always/critical。
|
||||
/// - 偏移段 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);时区注解命名区原样、偏移区规范化。
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn format_zoned_date_time_iso(
|
||||
epoch_ns: i128, offset_seconds: i64, time_zone_id: &str, calendar_id: &str, include_seconds: bool,
|
||||
output_digits: Option<usize>, offset_name: &str, time_zone_name: &str, calendar_name: &str,
|
||||
output_digits: Option<usize>, names: FormatNames,
|
||||
) -> Option<String> {
|
||||
let offset_name = names.offset_name;
|
||||
let time_zone_name = names.time_zone_name;
|
||||
let calendar_name = names.calendar_name;
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
let offset_ns = i128::from(offset_seconds).checked_mul(1_000_000_000)?;
|
||||
let local_ns = epoch_ns.checked_add(offset_ns)?;
|
||||
@@ -716,9 +726,11 @@ pub fn zoned_date_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
|
||||
&calendar_id,
|
||||
include_seconds,
|
||||
output_digits,
|
||||
&offset_name,
|
||||
&time_zone_name,
|
||||
&calendar_name,
|
||||
FormatNames {
|
||||
offset_name: &offset_name,
|
||||
time_zone_name: &time_zone_name,
|
||||
calendar_name: &calendar_name,
|
||||
},
|
||||
) {
|
||||
Some(output) => NativeResult::Ok(vm.new_string_owned(output)),
|
||||
None => NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime")),
|
||||
@@ -1350,10 +1362,14 @@ fn zoned_date_time_difference<H: VmHost>(vm: &mut H, args: &[u8], since: bool) -
|
||||
let settings = native_try!(parse_difference_settings(vm, options_value, false, 4));
|
||||
difference_core(
|
||||
vm,
|
||||
(i128::from(y_r), i128::from(m_r), i128::from(d_r)),
|
||||
time_ns_r as i128,
|
||||
(y_o, m_o, d_o),
|
||||
time_ns_o,
|
||||
IsoDateTime {
|
||||
date: (i128::from(y_r), i128::from(m_r), i128::from(d_r)),
|
||||
time_ns: time_ns_r as i128,
|
||||
},
|
||||
IsoDateTime {
|
||||
date: (y_o, m_o, d_o),
|
||||
time_ns: time_ns_o,
|
||||
},
|
||||
settings,
|
||||
since,
|
||||
9,
|
||||
@@ -1710,14 +1726,15 @@ pub(crate) fn add_instant(epoch_ns: i128, time_ns: i128) -> Result<i128, AddZone
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日。
|
||||
/// CalendarDateAdd(ISO 8601 日历):日期叠加年/月/周/日(values 依次年/月/周/日)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 年/月推进(月末截断:constrain 钳到目标月末,reject 抛 DayOutOfRange)。
|
||||
/// 2. 周*7 + 日推进。
|
||||
pub(crate) fn calendar_date_add_iso(
|
||||
date: (i128, i128, i128), years: i128, months: i128, weeks: i128, days: i128, constrain: bool,
|
||||
date: (i128, i128, i128), values: [i128; 4], constrain: bool,
|
||||
) -> Result<(i128, i128, i128), AddZonedError> {
|
||||
let (years, months, weeks, days) = (values[0], values[1], values[2], values[3]);
|
||||
let total = date.0 * 12 + (date.1 - 1) + years * 12 + months;
|
||||
let ny = total.div_euclid(12);
|
||||
let nm = total.rem_euclid(12) + 1;
|
||||
@@ -1837,17 +1854,16 @@ pub(crate) fn add_zoned_datetime_pure(
|
||||
return add_instant(epoch_ns, time_ns);
|
||||
}
|
||||
// GetISODateTimeFor:墙历分量。
|
||||
let ((year, month, day), time_ns) = get_iso_date_time_for(time_zone, epoch_ns)?;
|
||||
let iso = get_iso_date_time_for(time_zone, epoch_ns)?;
|
||||
let (year, month, day) = iso.date;
|
||||
let time_ns = iso.time_ns;
|
||||
if !valid_plain_date_time_range(year as i32, month as u32, day as u32, time_ns as f64) {
|
||||
return Err(AddZonedError::OutOfDateTimeRange);
|
||||
}
|
||||
// CalendarDateAdd:日期分量(constrain 钳月末,reject 抛错)。
|
||||
let added = calendar_date_add_iso(
|
||||
(year, month, day),
|
||||
values[0] as i128,
|
||||
values[1] as i128,
|
||||
values[2] as i128,
|
||||
values[3] as i128,
|
||||
[values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
|
||||
constrain,
|
||||
)?;
|
||||
// 中间日期检查(ISODateTimeWithinLimits)。
|
||||
@@ -1887,20 +1903,18 @@ pub(crate) fn map_add_zoned_error<H: VmHost>(vm: &mut H, error: AddZonedError) -
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTimeWithRounding 的 VM 包装:错误映射为 RangeError。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_zoned_datetime_with_rounding_vm<H: VmHost>(
|
||||
vm: &mut H, ns1: i128, ns2: i128, time_zone: &str, calendar: &str, largest_unit: usize, increment: i128,
|
||||
smallest_unit: usize, mode: InstantRoundingMode,
|
||||
vm: &mut H, pair: ZonedPair, settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, JsValue> {
|
||||
difference_zoned_datetime_with_rounding(ns1, ns2, time_zone, calendar, largest_unit, increment, smallest_unit, mode)
|
||||
difference_zoned_datetime_with_rounding(pair, settings)
|
||||
.map_err(|error| map_add_zoned_error(vm, error))
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTimeWithTotal 的 VM 包装:错误映射为 RangeError。
|
||||
pub(crate) fn difference_zoned_datetime_with_total_vm<H: VmHost>(
|
||||
vm: &mut H, ns1: i128, ns2: i128, time_zone: &str, calendar: &str, unit: usize,
|
||||
vm: &mut H, pair: ZonedPair, unit: usize,
|
||||
) -> Result<f64, JsValue> {
|
||||
difference_zoned_datetime_with_total(ns1, ns2, time_zone, calendar, unit)
|
||||
difference_zoned_datetime_with_total(pair, unit)
|
||||
.map_err(|error| map_add_zoned_error(vm, error))
|
||||
}
|
||||
|
||||
@@ -1912,16 +1926,17 @@ pub(crate) fn difference_zoned_datetime_with_total_vm<H: VmHost>(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 时区名非法(不在区表且非固定偏移串)返回 InvalidTimeZone。
|
||||
pub(crate) fn get_iso_date_time_for(
|
||||
time_zone: &str, epoch_ns: i128,
|
||||
) -> Result<((i128, i128, i128), i128), AddZonedError> {
|
||||
pub(crate) fn get_iso_date_time_for(time_zone: &str, epoch_ns: i128) -> Result<IsoDateTime, AddZonedError> {
|
||||
let offset_seconds = zone_offset_seconds(time_zone, epoch_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or(AddZonedError::InvalidTimeZone)?;
|
||||
let local_ns = epoch_ns + i128::from(offset_seconds) * 1_000_000_000;
|
||||
let days = local_ns.div_euclid(DAY_NS);
|
||||
let time_ns = local_ns.rem_euclid(DAY_NS);
|
||||
let (year, month, day) = civil_from_days(days);
|
||||
Ok(((year, month, day), time_ns))
|
||||
Ok(IsoDateTime {
|
||||
date: (year, month, day),
|
||||
time_ns,
|
||||
})
|
||||
}
|
||||
|
||||
/// GetEpochNanosecondsFor(规范 11.1.11,compatible 消歧):墙历日期时间的纪元。
|
||||
@@ -1959,6 +1974,17 @@ fn time_duration_sign(ns: i128) -> i128 {
|
||||
}
|
||||
}
|
||||
|
||||
/// zoned 差值端点对:两个 epoch 加时区与日历(日历透传,引擎仅 iso8601)。
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ZonedPair<'a> {
|
||||
pub(crate) ns1: i128,
|
||||
pub(crate) ns2: i128,
|
||||
pub(crate) time_zone: &'a str,
|
||||
// 透传字段:引擎仅 iso8601 日历,差值计算不消费,保留以对齐规范签名。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) calendar: &'a str,
|
||||
}
|
||||
|
||||
/// DifferenceZonedDateTime(规范 6.5.6):两个 epoch 的差,平衡后结果中无大于
|
||||
/// largestUnit 的非零单位,计入日历 reckoning 与时区偏移变化。
|
||||
///
|
||||
@@ -1972,13 +1998,20 @@ fn time_duration_sign(ns: i128) -> i128 {
|
||||
/// - 全程 i128 纳秒;中间点时间取起点墙历时间(规范 (intermediateDate, startDT.Time))。
|
||||
/// - calendar 参数透传(引擎仅 iso8601 日历)。
|
||||
pub(crate) fn difference_zoned_datetime(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, largest_unit: usize,
|
||||
pair: ZonedPair, largest_unit: usize,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
if ns1 == ns2 {
|
||||
return Ok(InternalDuration { date: [0.0; 4], time_ns: 0 });
|
||||
}
|
||||
let (start_date, start_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
let (end_date, end_time) = get_iso_date_time_for(time_zone, ns2)?;
|
||||
let start = get_iso_date_time_for(time_zone, ns1)?;
|
||||
let end = get_iso_date_time_for(time_zone, ns2)?;
|
||||
let start_date = start.date;
|
||||
let start_time = start.time_ns;
|
||||
let end_date = end.date;
|
||||
let end_time = end.time_ns;
|
||||
if compare_iso_date(start_date, end_date) == 0 {
|
||||
return Ok(InternalDuration {
|
||||
date: [0.0; 4],
|
||||
@@ -2023,11 +2056,16 @@ pub(crate) fn difference_zoned_datetime(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - smallestUnit 不大于 largestUnit(调用方已校验)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, largest_unit: usize, increment: i128, smallest_unit: usize,
|
||||
mode: InstantRoundingMode,
|
||||
pair: ZonedPair, settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
let largest_unit = settings.largest_index;
|
||||
let increment = settings.increment;
|
||||
let smallest_unit = settings.smallest_index;
|
||||
let mode = settings.mode;
|
||||
if largest_unit >= 4 {
|
||||
let quantum = TIME_UNIT_NS[smallest_unit - 4]
|
||||
.checked_mul(increment)
|
||||
@@ -2038,22 +2076,12 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
time_ns: rounded,
|
||||
});
|
||||
}
|
||||
let difference = difference_zoned_datetime(ns1, ns2, time_zone, _calendar, largest_unit)?;
|
||||
let difference = difference_zoned_datetime(pair, largest_unit)?;
|
||||
if smallest_unit == 9 && increment == 1 {
|
||||
return Ok(difference);
|
||||
}
|
||||
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
round_relative_duration_zoned(
|
||||
difference,
|
||||
ns1,
|
||||
ns2,
|
||||
(iso_date, iso_time),
|
||||
time_zone,
|
||||
largest_unit,
|
||||
increment,
|
||||
smallest_unit,
|
||||
mode,
|
||||
)
|
||||
let iso = get_iso_date_time_for(time_zone, ns1)?;
|
||||
round_relative_duration_zoned(difference, ns1, ns2, iso, time_zone, settings)
|
||||
}
|
||||
|
||||
/// RoundRelativeDuration(规范 7.5.38)的 zoned 路由:日历单位(或 day)走日历
|
||||
@@ -2063,33 +2091,32 @@ pub(crate) fn difference_zoned_datetime_with_rounding(
|
||||
/// 1. 路由:smallestUnit 为日历单位或 day → NudgeToCalendarUnit(zoned 闭包);
|
||||
/// 否则 NudgeToZonedTime。
|
||||
/// 2. 扩窗且 smallestUnit 非 week → BubbleRelativeDuration(startUnit 取 smallestUnit 与 day 的较大者)。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn round_relative_duration_zoned(
|
||||
difference: InternalDuration, origin_ns: i128, dest_ns: i128, iso: ((i128, i128, i128), i128), time_zone: &str,
|
||||
largest_unit: usize, increment: i128, smallest_unit: usize, mode: InstantRoundingMode,
|
||||
difference: InternalDuration, origin_ns: i128, dest_ns: i128, iso: IsoDateTime, time_zone: &str,
|
||||
settings: DifferenceSettings,
|
||||
) -> Result<InternalDuration, AddZonedError> {
|
||||
let largest_unit = settings.largest_index;
|
||||
let increment = settings.increment;
|
||||
let smallest_unit = settings.smallest_index;
|
||||
let mode = settings.mode;
|
||||
let values = difference.to_values();
|
||||
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 };
|
||||
let (iso_date, iso_time) = iso;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
|
||||
// zoned 面时区恒在场:irregular = smallestUnit 为日历单位或 day。
|
||||
let irregular = smallest_unit <= 3;
|
||||
let spec = RoundingSpec {
|
||||
increment,
|
||||
unit: smallest_unit,
|
||||
mode,
|
||||
};
|
||||
let (mut result, nudged_epoch, did_expand) = if irregular {
|
||||
let outcome = nudge_to_calendar_unit(
|
||||
sign,
|
||||
&values,
|
||||
origin_ns,
|
||||
dest_ns,
|
||||
iso_date,
|
||||
increment,
|
||||
smallest_unit,
|
||||
mode,
|
||||
&epoch_of_date,
|
||||
)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
(outcome.duration, outcome.nudged_epoch, outcome.did_expand)
|
||||
} else {
|
||||
nudge_to_zoned_time(sign, &values, iso_date, iso_time, time_zone, increment, smallest_unit, mode)?
|
||||
nudge_to_zoned_time(sign, &values, iso, time_zone, spec)?
|
||||
};
|
||||
if did_expand && smallest_unit != 2 {
|
||||
let start_unit = smallest_unit.min(3);
|
||||
@@ -2113,18 +2140,18 @@ fn round_relative_duration_zoned(
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - unit 为时间单位(时到纳秒);day 单位走日历窗口路径,不进本函数。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn nudge_to_zoned_time(
|
||||
sign: i128, values: &[f64; 10], iso_date: (i128, i128, i128), iso_time: i128, time_zone: &str, increment: i128,
|
||||
unit: usize, mode: InstantRoundingMode,
|
||||
sign: i128, values: &[f64; 10], iso: IsoDateTime, time_zone: &str, spec: RoundingSpec,
|
||||
) -> Result<([f64; 10], i128, bool), AddZonedError> {
|
||||
let increment = spec.increment;
|
||||
let unit = spec.unit;
|
||||
let mode = spec.mode;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let time_ns = duration_time_only_nanoseconds(values).ok_or(AddZonedError::OutOfTimeRange)?;
|
||||
let start = calendar_date_add_iso(
|
||||
iso_date,
|
||||
values[0] as i128,
|
||||
values[1] as i128,
|
||||
values[2] as i128,
|
||||
values[3] as i128,
|
||||
[values[0] as i128, values[1] as i128, values[2] as i128, values[3] as i128],
|
||||
true,
|
||||
)?;
|
||||
let start_epoch = get_epoch_nanos_for(time_zone, start, iso_time)?;
|
||||
@@ -2156,15 +2183,18 @@ fn nudge_to_zoned_time(
|
||||
/// 1. unit 为时间单位 → TotalTimeDuration 快路径(纳秒差直接除单位长度)。
|
||||
/// 2. 否则 DifferenceZonedDateTime + TotalRelativeDuration。
|
||||
pub(crate) fn difference_zoned_datetime_with_total(
|
||||
ns1: i128, ns2: i128, time_zone: &str, _calendar: &str, unit: usize,
|
||||
pair: ZonedPair, unit: usize,
|
||||
) -> Result<f64, AddZonedError> {
|
||||
let ns1 = pair.ns1;
|
||||
let ns2 = pair.ns2;
|
||||
let time_zone = pair.time_zone;
|
||||
if unit >= 4 {
|
||||
let difference = ns2 - ns1;
|
||||
return Ok(difference as f64 / TIME_UNIT_NS[unit - 4] as f64);
|
||||
}
|
||||
let difference = difference_zoned_datetime(ns1, ns2, time_zone, _calendar, unit)?;
|
||||
let (iso_date, iso_time) = get_iso_date_time_for(time_zone, ns1)?;
|
||||
total_relative_duration(&difference, ns1, ns2, (iso_date, iso_time), time_zone, unit)
|
||||
let difference = difference_zoned_datetime(pair, unit)?;
|
||||
let iso = get_iso_date_time_for(time_zone, ns1)?;
|
||||
total_relative_duration(&difference, ns1, ns2, iso, time_zone, unit)
|
||||
}
|
||||
|
||||
/// TotalRelativeDuration(规范 7.5.39):时长以 unit 计的总量,相对起点折算。
|
||||
@@ -2174,27 +2204,23 @@ pub(crate) fn difference_zoned_datetime_with_total(
|
||||
/// (increment = 1、trunc)取 [[Total]]。
|
||||
/// 2. 否则 TotalTimeDuration(24h 日折算,zoned 面不可达)。
|
||||
fn total_relative_duration(
|
||||
difference: &InternalDuration, origin_ns: i128, dest_ns: i128, iso: ((i128, i128, i128), i128), time_zone: &str,
|
||||
difference: &InternalDuration, origin_ns: i128, dest_ns: i128, iso: IsoDateTime, time_zone: &str,
|
||||
unit: usize,
|
||||
) -> Result<f64, AddZonedError> {
|
||||
let values = difference.to_values();
|
||||
// zoned 面时区恒在场:unit ≤ day 恒走日历窗口路径。
|
||||
if unit <= 3 {
|
||||
let sign = if internal_duration_sign(&values) < 0 { -1 } else { 1 };
|
||||
let (iso_date, iso_time) = iso;
|
||||
let iso_date = iso.date;
|
||||
let iso_time = iso.time_ns;
|
||||
let epoch_of_date = zoned_epoch_of_date(time_zone, iso_time);
|
||||
let outcome = nudge_to_calendar_unit(
|
||||
sign,
|
||||
&values,
|
||||
origin_ns,
|
||||
dest_ns,
|
||||
iso_date,
|
||||
1,
|
||||
let spec = RoundingSpec {
|
||||
increment: 1,
|
||||
unit,
|
||||
InstantRoundingMode::Trunc,
|
||||
&epoch_of_date,
|
||||
)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
mode: InstantRoundingMode::Trunc,
|
||||
};
|
||||
let outcome = nudge_to_calendar_unit(sign, &values, origin_ns, dest_ns, iso_date, spec, &epoch_of_date)
|
||||
.map_err(|_| AddZonedError::OutOfDateTimeRange)?;
|
||||
return Ok(outcome.total);
|
||||
}
|
||||
let time_ns = duration_time_only_nanoseconds(&values).ok_or(AddZonedError::OutOfTimeRange)?;
|
||||
|
||||
@@ -2701,7 +2701,15 @@ fn ta_decode_base64<H: VmHost>(
|
||||
handling = ta_handling_option(vm, obj, opts_val)?;
|
||||
}
|
||||
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),
|
||||
Err(_) => Err(crate::error::create_syntax_error(vm, "invalid base64 input")),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user