@tishlang/tish-format 1.0.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
//! `Date` — real constructor + instance methods for the non-JS targets (interpreter, VM, native).
|
|
2
|
+
//!
|
|
3
|
+
//! Representation is runtime-agnostic: a `Date` instance is a plain `Value::Object` whose methods
|
|
4
|
+
//! are per-instance `Value::native` closures that all capture the SAME `VmRef<f64>` epoch-millis
|
|
5
|
+
//! cell. Mutators (`setTime`) write the cell; getters recompute calendar fields from it. No new
|
|
6
|
+
//! `Value` variant is needed, so the interpreter, bytecode VM and native-compiled code all share
|
|
7
|
+
//! this one implementation.
|
|
8
|
+
//!
|
|
9
|
+
//! **Timezone:** Tish's `Date` runs in **UTC** — `getTimezoneOffset()` is `0` and the local-time
|
|
10
|
+
//! getters (`getFullYear`, `getHours`, …) are exact aliases of their `getUTC*` counterparts. There
|
|
11
|
+
//! is no timezone database in the core builtins (keeps them lean + wasm-friendly). `getTime`,
|
|
12
|
+
//! `valueOf`, the `getUTC*` family and `toISOString` are therefore fully deterministic everywhere.
|
|
13
|
+
|
|
14
|
+
use std::sync::Arc;
|
|
15
|
+
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
16
|
+
|
|
17
|
+
const CONSTRUCT: &str = "__construct";
|
|
18
|
+
const MS_PER_DAY: i64 = 86_400_000;
|
|
19
|
+
|
|
20
|
+
const WEEKDAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
21
|
+
const MONTHS: [&str; 12] = [
|
|
22
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/// Current wall-clock time as epoch milliseconds.
|
|
26
|
+
fn now_ms() -> f64 {
|
|
27
|
+
std::time::SystemTime::now()
|
|
28
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
29
|
+
.map(|d| d.as_millis() as f64)
|
|
30
|
+
.unwrap_or(0.0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// A `(method name, field extractor)` pair for the numeric `Date` getters. The explicit
|
|
34
|
+
/// `fn(&Civil) -> i64` coerces the (non-capturing) extractor closures to a single fn-pointer type so
|
|
35
|
+
/// they can live in one array.
|
|
36
|
+
type DateGetter = (&'static str, fn(&Civil) -> i64);
|
|
37
|
+
|
|
38
|
+
/// Broken-down UTC calendar fields for an epoch-millis instant.
|
|
39
|
+
struct Civil {
|
|
40
|
+
year: i64,
|
|
41
|
+
/// 1..=12 (callers convert to JS's 0-based month where needed).
|
|
42
|
+
month: i64,
|
|
43
|
+
day: i64,
|
|
44
|
+
hours: i64,
|
|
45
|
+
minutes: i64,
|
|
46
|
+
seconds: i64,
|
|
47
|
+
millis: i64,
|
|
48
|
+
/// 0 = Sunday … 6 = Saturday.
|
|
49
|
+
weekday: i64,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Days since 1970-01-01 for a proleptic-Gregorian (y, m∈1..=12, d) — Howard Hinnant's algorithm.
|
|
53
|
+
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
|
|
54
|
+
let y = if m <= 2 { y - 1 } else { y };
|
|
55
|
+
let era = if y >= 0 { y } else { y - 399 } / 400;
|
|
56
|
+
let yoe = y - era * 400; // [0, 399]
|
|
57
|
+
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1; // [0, 365]
|
|
58
|
+
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
|
|
59
|
+
era * 146097 + doe - 719468
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Inverse of [`days_from_civil`]: days-since-epoch → (year, month∈1..=12, day) — Hinnant.
|
|
63
|
+
fn civil_from_days(z: i64) -> (i64, i64, i64) {
|
|
64
|
+
let z = z + 719468;
|
|
65
|
+
let era = if z >= 0 { z } else { z - 146096 } / 146097;
|
|
66
|
+
let doe = z - era * 146097; // [0, 146096]
|
|
67
|
+
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
|
|
68
|
+
let y = yoe + era * 400;
|
|
69
|
+
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
|
|
70
|
+
let mp = (5 * doy + 2) / 153; // [0, 11]
|
|
71
|
+
let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
|
|
72
|
+
let m = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
|
|
73
|
+
(if m <= 2 { y + 1 } else { y }, m, d)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Decompose epoch milliseconds into UTC calendar fields. Uses Euclidean div/rem so pre-1970
|
|
77
|
+
/// (negative) instants decompose correctly.
|
|
78
|
+
fn civil_from_ms(ms: f64) -> Civil {
|
|
79
|
+
let ms_i = ms.floor() as i64;
|
|
80
|
+
let days = ms_i.div_euclid(MS_PER_DAY);
|
|
81
|
+
let tod = ms_i.rem_euclid(MS_PER_DAY); // [0, 86_399_999]
|
|
82
|
+
let (year, month, day) = civil_from_days(days);
|
|
83
|
+
// 1970-01-01 was a Thursday (weekday index 4).
|
|
84
|
+
let weekday = (days.rem_euclid(7) + 4).rem_euclid(7);
|
|
85
|
+
Civil {
|
|
86
|
+
year,
|
|
87
|
+
month,
|
|
88
|
+
day,
|
|
89
|
+
hours: tod / 3_600_000,
|
|
90
|
+
minutes: (tod / 60_000) % 60,
|
|
91
|
+
seconds: (tod / 1000) % 60,
|
|
92
|
+
millis: tod % 1000,
|
|
93
|
+
weekday,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Build epoch millis from UTC components (month is 0-based, JS-style).
|
|
98
|
+
#[allow(clippy::too_many_arguments)]
|
|
99
|
+
fn ms_from_utc(
|
|
100
|
+
year: i64,
|
|
101
|
+
month0: i64,
|
|
102
|
+
day: i64,
|
|
103
|
+
hours: i64,
|
|
104
|
+
minutes: i64,
|
|
105
|
+
seconds: i64,
|
|
106
|
+
millis: i64,
|
|
107
|
+
) -> f64 {
|
|
108
|
+
// Normalize the 0-based month into a year/month carry so `Date.UTC(2020, 13, 1)` works.
|
|
109
|
+
let y = year + month0.div_euclid(12);
|
|
110
|
+
let m = month0.rem_euclid(12) + 1; // 1..=12
|
|
111
|
+
let days = days_from_civil(y, m, day);
|
|
112
|
+
(days * MS_PER_DAY
|
|
113
|
+
+ hours * 3_600_000
|
|
114
|
+
+ minutes * 60_000
|
|
115
|
+
+ seconds * 1000
|
|
116
|
+
+ millis) as f64
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// ISO-8601 string for an epoch-millis instant (always UTC, `…Z`). `None` when the instant is NaN
|
|
120
|
+
/// (JS would throw `RangeError` from `toISOString` on an invalid date).
|
|
121
|
+
fn to_iso(ms: f64) -> Option<String> {
|
|
122
|
+
if !ms.is_finite() {
|
|
123
|
+
return None;
|
|
124
|
+
}
|
|
125
|
+
let c = civil_from_ms(ms);
|
|
126
|
+
Some(if (0..=9999).contains(&c.year) {
|
|
127
|
+
format!(
|
|
128
|
+
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
|
|
129
|
+
c.year, c.month, c.day, c.hours, c.minutes, c.seconds, c.millis
|
|
130
|
+
)
|
|
131
|
+
} else {
|
|
132
|
+
// Expanded-year form (JS uses ±YYYYYY for years outside 0..=9999).
|
|
133
|
+
format!(
|
|
134
|
+
"{}{:06}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
|
|
135
|
+
if c.year < 0 { '-' } else { '+' },
|
|
136
|
+
c.year.abs(),
|
|
137
|
+
c.month,
|
|
138
|
+
c.day,
|
|
139
|
+
c.hours,
|
|
140
|
+
c.minutes,
|
|
141
|
+
c.seconds,
|
|
142
|
+
c.millis
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Human form, e.g. `Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time)`.
|
|
148
|
+
fn to_string_full(ms: f64) -> String {
|
|
149
|
+
if !ms.is_finite() {
|
|
150
|
+
return "Invalid Date".to_string();
|
|
151
|
+
}
|
|
152
|
+
let c = civil_from_ms(ms);
|
|
153
|
+
format!(
|
|
154
|
+
"{} {} {:02} {:04} {:02}:{:02}:{:02} GMT+0000 (Coordinated Universal Time)",
|
|
155
|
+
WEEKDAYS[c.weekday as usize],
|
|
156
|
+
MONTHS[(c.month - 1) as usize],
|
|
157
|
+
c.day,
|
|
158
|
+
c.year,
|
|
159
|
+
c.hours,
|
|
160
|
+
c.minutes,
|
|
161
|
+
c.seconds
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn to_date_string(ms: f64) -> String {
|
|
166
|
+
if !ms.is_finite() {
|
|
167
|
+
return "Invalid Date".to_string();
|
|
168
|
+
}
|
|
169
|
+
let c = civil_from_ms(ms);
|
|
170
|
+
format!(
|
|
171
|
+
"{} {} {:02} {:04}",
|
|
172
|
+
WEEKDAYS[c.weekday as usize],
|
|
173
|
+
MONTHS[(c.month - 1) as usize],
|
|
174
|
+
c.day,
|
|
175
|
+
c.year
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Parse a subset of the formats `Date.parse` accepts: ISO-8601 date (`YYYY-MM-DD`) and date-time
|
|
180
|
+
/// (`YYYY-MM-DDTHH:MM[:SS[.sss]]`) with an optional `Z` or `±HH:MM` offset. No offset ⇒ UTC
|
|
181
|
+
/// (Tish runs Dates in UTC). Returns NaN on anything it cannot parse.
|
|
182
|
+
fn parse_date(s: &str) -> f64 {
|
|
183
|
+
let s = s.trim();
|
|
184
|
+
// Split date and (optional) time on 'T' or a space.
|
|
185
|
+
let (date_part, time_part) = match s.find(['T', ' ']) {
|
|
186
|
+
Some(i) => (&s[..i], Some(&s[i + 1..])),
|
|
187
|
+
None => (s, None),
|
|
188
|
+
};
|
|
189
|
+
let mut dit = date_part.split('-');
|
|
190
|
+
// Leading '-' (negative year) is not supported here; the common cases are positive years.
|
|
191
|
+
let year: i64 = match dit.next().and_then(|x| x.parse().ok()) {
|
|
192
|
+
Some(y) => y,
|
|
193
|
+
None => return f64::NAN,
|
|
194
|
+
};
|
|
195
|
+
let month: i64 = dit.next().and_then(|x| x.parse().ok()).unwrap_or(1);
|
|
196
|
+
let day: i64 = dit.next().and_then(|x| x.parse().ok()).unwrap_or(1);
|
|
197
|
+
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
|
|
198
|
+
return f64::NAN;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let (mut hh, mut mm, mut ss, mut ms, mut offset_min) = (0i64, 0i64, 0i64, 0i64, 0i64);
|
|
202
|
+
if let Some(tp) = time_part {
|
|
203
|
+
let tp = tp.trim();
|
|
204
|
+
// Strip a trailing timezone designator.
|
|
205
|
+
let (clock, tz): (&str, Option<&str>) = if let Some(stripped) = tp.strip_suffix('Z') {
|
|
206
|
+
(stripped, Some("Z"))
|
|
207
|
+
} else if let Some(pos) = tp.rfind(['+', '-']) {
|
|
208
|
+
(&tp[..pos], Some(&tp[pos..]))
|
|
209
|
+
} else {
|
|
210
|
+
(tp, None)
|
|
211
|
+
};
|
|
212
|
+
let mut cit = clock.split(':');
|
|
213
|
+
hh = cit.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
|
214
|
+
mm = cit.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
|
215
|
+
if let Some(sec) = cit.next() {
|
|
216
|
+
let mut sp = sec.split('.');
|
|
217
|
+
ss = sp.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
|
218
|
+
if let Some(frac) = sp.next() {
|
|
219
|
+
// milliseconds = first 3 fractional digits, right-padded.
|
|
220
|
+
let mut f = frac.to_string();
|
|
221
|
+
f.truncate(3);
|
|
222
|
+
while f.len() < 3 {
|
|
223
|
+
f.push('0');
|
|
224
|
+
}
|
|
225
|
+
ms = f.parse().unwrap_or(0);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if let Some(tz) = tz {
|
|
229
|
+
if tz != "Z" && tz.len() >= 3 {
|
|
230
|
+
let sign = if tz.starts_with('-') { -1 } else { 1 };
|
|
231
|
+
let body = &tz[1..];
|
|
232
|
+
let mut oit = body.split(':');
|
|
233
|
+
let oh: i64 = oit.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
|
234
|
+
let om: i64 = oit.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
|
235
|
+
offset_min = sign * (oh * 60 + om);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if !(0..=23).contains(&hh) || !(0..=59).contains(&mm) || !(0..=60).contains(&ss) {
|
|
240
|
+
return f64::NAN;
|
|
241
|
+
}
|
|
242
|
+
ms_from_utc(year, month - 1, day, hh, mm, ss, ms) - (offset_min * 60_000) as f64
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// `args[i]` as f64, defaulting to `dflt` when absent.
|
|
246
|
+
fn arg_num(args: &[Value], i: usize, dflt: f64) -> f64 {
|
|
247
|
+
args.get(i).and_then(Value::as_number).unwrap_or(dflt)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// A numeric getter over the instance's current epoch-millis (NaN-safe).
|
|
251
|
+
fn num_getter(store: &VmRef<f64>, f: fn(&Civil) -> i64) -> Value {
|
|
252
|
+
let s = store.clone();
|
|
253
|
+
Value::native(move |_args: &[Value]| {
|
|
254
|
+
let ms = *s.borrow();
|
|
255
|
+
if ms.is_nan() {
|
|
256
|
+
Value::Number(f64::NAN)
|
|
257
|
+
} else {
|
|
258
|
+
Value::Number(f(&civil_from_ms(ms)) as f64)
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// A string getter over the instance's current epoch-millis.
|
|
264
|
+
fn str_getter(store: &VmRef<f64>, f: fn(f64) -> String) -> Value {
|
|
265
|
+
let s = store.clone();
|
|
266
|
+
Value::native(move |_args: &[Value]| Value::String(f(*s.borrow()).into()))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Construct a `Date` instance object backing onto a shared `VmRef<f64>` epoch-millis cell.
|
|
270
|
+
pub fn date_instance(ms: f64) -> Value {
|
|
271
|
+
let store: VmRef<f64> = VmRef::new(ms);
|
|
272
|
+
let mut m = ObjectMap::default();
|
|
273
|
+
|
|
274
|
+
// Identity / numeric value.
|
|
275
|
+
{
|
|
276
|
+
let s = store.clone();
|
|
277
|
+
m.insert(
|
|
278
|
+
Arc::from("getTime"),
|
|
279
|
+
Value::native(move |_| Value::Number(*s.borrow())),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
{
|
|
283
|
+
let s = store.clone();
|
|
284
|
+
m.insert(
|
|
285
|
+
Arc::from("valueOf"),
|
|
286
|
+
Value::native(move |_| Value::Number(*s.borrow())),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
{
|
|
290
|
+
let s = store.clone();
|
|
291
|
+
m.insert(
|
|
292
|
+
Arc::from("setTime"),
|
|
293
|
+
Value::native(move |args: &[Value]| {
|
|
294
|
+
let ms = arg_num(args, 0, f64::NAN);
|
|
295
|
+
*s.borrow_mut() = ms;
|
|
296
|
+
Value::Number(ms)
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// UTC field getters (the canonical, deterministic family).
|
|
302
|
+
let utc: [DateGetter; 8] = [
|
|
303
|
+
("getUTCFullYear", |c| c.year),
|
|
304
|
+
("getUTCMonth", |c| c.month - 1), // JS months are 0-based
|
|
305
|
+
("getUTCDate", |c| c.day),
|
|
306
|
+
("getUTCDay", |c| c.weekday),
|
|
307
|
+
("getUTCHours", |c| c.hours),
|
|
308
|
+
("getUTCMinutes", |c| c.minutes),
|
|
309
|
+
("getUTCSeconds", |c| c.seconds),
|
|
310
|
+
("getUTCMilliseconds", |c| c.millis),
|
|
311
|
+
];
|
|
312
|
+
for (name, f) in utc {
|
|
313
|
+
m.insert(Arc::from(name), num_getter(&store, f));
|
|
314
|
+
}
|
|
315
|
+
// Local-time getters: Tish runs Dates in UTC, so these alias the UTC family.
|
|
316
|
+
let local: [DateGetter; 8] = [
|
|
317
|
+
("getFullYear", |c| c.year),
|
|
318
|
+
("getMonth", |c| c.month - 1),
|
|
319
|
+
("getDate", |c| c.day),
|
|
320
|
+
("getDay", |c| c.weekday),
|
|
321
|
+
("getHours", |c| c.hours),
|
|
322
|
+
("getMinutes", |c| c.minutes),
|
|
323
|
+
("getSeconds", |c| c.seconds),
|
|
324
|
+
("getMilliseconds", |c| c.millis),
|
|
325
|
+
];
|
|
326
|
+
for (name, f) in local {
|
|
327
|
+
m.insert(Arc::from(name), num_getter(&store, f));
|
|
328
|
+
}
|
|
329
|
+
m.insert(
|
|
330
|
+
Arc::from("getTimezoneOffset"),
|
|
331
|
+
Value::native(|_| Value::Number(0.0)),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// String renderings.
|
|
335
|
+
{
|
|
336
|
+
let s = store.clone();
|
|
337
|
+
m.insert(
|
|
338
|
+
Arc::from("toISOString"),
|
|
339
|
+
Value::native(move |_| match to_iso(*s.borrow()) {
|
|
340
|
+
Some(iso) => Value::String(iso.into()),
|
|
341
|
+
None => Value::Null,
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
{
|
|
346
|
+
let s = store.clone();
|
|
347
|
+
m.insert(
|
|
348
|
+
Arc::from("toJSON"),
|
|
349
|
+
Value::native(move |_| match to_iso(*s.borrow()) {
|
|
350
|
+
Some(iso) => Value::String(iso.into()),
|
|
351
|
+
None => Value::Null,
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
m.insert(Arc::from("toString"), str_getter(&store, to_string_full));
|
|
356
|
+
m.insert(
|
|
357
|
+
Arc::from("toUTCString"),
|
|
358
|
+
str_getter(&store, |ms| {
|
|
359
|
+
if !ms.is_finite() {
|
|
360
|
+
return "Invalid Date".to_string();
|
|
361
|
+
}
|
|
362
|
+
let c = civil_from_ms(ms);
|
|
363
|
+
format!(
|
|
364
|
+
"{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT",
|
|
365
|
+
WEEKDAYS[c.weekday as usize],
|
|
366
|
+
c.day,
|
|
367
|
+
MONTHS[(c.month - 1) as usize],
|
|
368
|
+
c.year,
|
|
369
|
+
c.hours,
|
|
370
|
+
c.minutes,
|
|
371
|
+
c.seconds
|
|
372
|
+
)
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
m.insert(Arc::from("toDateString"), str_getter(&store, to_date_string));
|
|
376
|
+
m.insert(
|
|
377
|
+
Arc::from("toTimeString"),
|
|
378
|
+
str_getter(&store, |ms| {
|
|
379
|
+
if !ms.is_finite() {
|
|
380
|
+
return "Invalid Date".to_string();
|
|
381
|
+
}
|
|
382
|
+
let c = civil_from_ms(ms);
|
|
383
|
+
format!(
|
|
384
|
+
"{:02}:{:02}:{:02} GMT+0000 (Coordinated Universal Time)",
|
|
385
|
+
c.hours, c.minutes, c.seconds
|
|
386
|
+
)
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
// Locale variants map to the UTC renderings (no locale/ICU data in core builtins).
|
|
390
|
+
m.insert(
|
|
391
|
+
Arc::from("toLocaleDateString"),
|
|
392
|
+
str_getter(&store, to_date_string),
|
|
393
|
+
);
|
|
394
|
+
m.insert(
|
|
395
|
+
Arc::from("toLocaleTimeString"),
|
|
396
|
+
str_getter(&store, |ms| {
|
|
397
|
+
if !ms.is_finite() {
|
|
398
|
+
return "Invalid Date".to_string();
|
|
399
|
+
}
|
|
400
|
+
let c = civil_from_ms(ms);
|
|
401
|
+
format!("{:02}:{:02}:{:02}", c.hours, c.minutes, c.seconds)
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
m.insert(Arc::from("toLocaleString"), str_getter(&store, to_string_full));
|
|
405
|
+
|
|
406
|
+
Value::object(m)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/// Interpret constructor arguments (`new Date(...)`) into an epoch-millis instant.
|
|
410
|
+
fn ms_from_args(args: &[Value]) -> f64 {
|
|
411
|
+
match args.len() {
|
|
412
|
+
0 => now_ms(),
|
|
413
|
+
1 => match &args[0] {
|
|
414
|
+
Value::Number(n) => *n,
|
|
415
|
+
Value::String(s) => parse_date(s),
|
|
416
|
+
other => other.as_number().unwrap_or(f64::NAN),
|
|
417
|
+
},
|
|
418
|
+
_ => {
|
|
419
|
+
// (year, month0, day=1, hours=0, minutes=0, seconds=0, ms=0) — UTC semantics.
|
|
420
|
+
let year = arg_num(args, 0, f64::NAN);
|
|
421
|
+
if !year.is_finite() {
|
|
422
|
+
return f64::NAN;
|
|
423
|
+
}
|
|
424
|
+
ms_from_utc(
|
|
425
|
+
year as i64,
|
|
426
|
+
arg_num(args, 1, 0.0) as i64,
|
|
427
|
+
arg_num(args, 2, 1.0) as i64,
|
|
428
|
+
arg_num(args, 3, 0.0) as i64,
|
|
429
|
+
arg_num(args, 4, 0.0) as i64,
|
|
430
|
+
arg_num(args, 5, 0.0) as i64,
|
|
431
|
+
arg_num(args, 6, 0.0) as i64,
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/// The global `Date`: callable as a constructor (`new Date(...)`) and carrying the statics
|
|
438
|
+
/// `Date.now()`, `Date.parse(str)` and `Date.UTC(...)`. Backwards-compatible with the previous
|
|
439
|
+
/// `Date.now()`-only object.
|
|
440
|
+
pub fn date_constructor_value() -> Value {
|
|
441
|
+
let mut m = ObjectMap::default();
|
|
442
|
+
m.insert(
|
|
443
|
+
Arc::from(CONSTRUCT),
|
|
444
|
+
Value::native(|args: &[Value]| date_instance(ms_from_args(args))),
|
|
445
|
+
);
|
|
446
|
+
m.insert(
|
|
447
|
+
Arc::from("now"),
|
|
448
|
+
Value::native(|_| Value::Number(now_ms())),
|
|
449
|
+
);
|
|
450
|
+
m.insert(
|
|
451
|
+
Arc::from("parse"),
|
|
452
|
+
Value::native(|args: &[Value]| {
|
|
453
|
+
let ms = match args.first() {
|
|
454
|
+
Some(Value::String(s)) => parse_date(s),
|
|
455
|
+
Some(v) => v.as_number().unwrap_or(f64::NAN),
|
|
456
|
+
None => f64::NAN,
|
|
457
|
+
};
|
|
458
|
+
Value::Number(ms)
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
m.insert(
|
|
462
|
+
Arc::from("UTC"),
|
|
463
|
+
Value::native(|args: &[Value]| {
|
|
464
|
+
if args.is_empty() {
|
|
465
|
+
return Value::Number(f64::NAN);
|
|
466
|
+
}
|
|
467
|
+
let year = arg_num(args, 0, f64::NAN);
|
|
468
|
+
if !year.is_finite() {
|
|
469
|
+
return Value::Number(f64::NAN);
|
|
470
|
+
}
|
|
471
|
+
Value::Number(ms_from_utc(
|
|
472
|
+
year as i64,
|
|
473
|
+
arg_num(args, 1, 0.0) as i64,
|
|
474
|
+
arg_num(args, 2, 1.0) as i64,
|
|
475
|
+
arg_num(args, 3, 0.0) as i64,
|
|
476
|
+
arg_num(args, 4, 0.0) as i64,
|
|
477
|
+
arg_num(args, 5, 0.0) as i64,
|
|
478
|
+
arg_num(args, 6, 0.0) as i64,
|
|
479
|
+
))
|
|
480
|
+
}),
|
|
481
|
+
);
|
|
482
|
+
Value::object(m)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
#[cfg(test)]
|
|
486
|
+
mod tests {
|
|
487
|
+
use super::*;
|
|
488
|
+
|
|
489
|
+
fn iso(ms: f64) -> String {
|
|
490
|
+
to_iso(ms).unwrap()
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[test]
|
|
494
|
+
fn epoch_is_unix_zero() {
|
|
495
|
+
assert_eq!(iso(0.0), "1970-01-01T00:00:00.000Z");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
#[test]
|
|
499
|
+
fn known_instant_roundtrips() {
|
|
500
|
+
// 2021-06-09T12:34:56.789Z
|
|
501
|
+
let ms = ms_from_utc(2021, 5, 9, 12, 34, 56, 789);
|
|
502
|
+
assert_eq!(iso(ms), "2021-06-09T12:34:56.789Z");
|
|
503
|
+
let c = civil_from_ms(ms);
|
|
504
|
+
assert_eq!((c.year, c.month, c.day), (2021, 6, 9));
|
|
505
|
+
assert_eq!((c.hours, c.minutes, c.seconds, c.millis), (12, 34, 56, 789));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
#[test]
|
|
509
|
+
fn weekday_anchor() {
|
|
510
|
+
// 1970-01-01 = Thursday(4); 2021-06-09 = Wednesday(3).
|
|
511
|
+
assert_eq!(civil_from_ms(0.0).weekday, 4);
|
|
512
|
+
assert_eq!(civil_from_ms(ms_from_utc(2021, 5, 9, 0, 0, 0, 0)).weekday, 3);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#[test]
|
|
516
|
+
fn pre_epoch_negative() {
|
|
517
|
+
// 1969-12-31T00:00:00Z = -86_400_000 ms, a Wednesday(3).
|
|
518
|
+
let ms = ms_from_utc(1969, 11, 31, 0, 0, 0, 0);
|
|
519
|
+
assert_eq!(ms, -86_400_000.0);
|
|
520
|
+
assert_eq!(iso(ms), "1969-12-31T00:00:00.000Z");
|
|
521
|
+
assert_eq!(civil_from_ms(ms).weekday, 3);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#[test]
|
|
525
|
+
fn parse_iso_forms() {
|
|
526
|
+
assert_eq!(parse_date("1970-01-01"), 0.0);
|
|
527
|
+
assert_eq!(parse_date("1970-01-01T00:00:00.000Z"), 0.0);
|
|
528
|
+
assert_eq!(parse_date("2021-06-09T12:34:56.789Z"), ms_from_utc(2021, 5, 9, 12, 34, 56, 789));
|
|
529
|
+
// +01:00 offset pulls the UTC instant back one hour.
|
|
530
|
+
assert_eq!(parse_date("1970-01-01T01:00:00+01:00"), 0.0);
|
|
531
|
+
assert!(parse_date("not a date").is_nan());
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
#[test]
|
|
535
|
+
fn leap_day() {
|
|
536
|
+
assert_eq!(iso(ms_from_utc(2020, 1, 29, 0, 0, 0, 0)), "2020-02-29T00:00:00.000Z");
|
|
537
|
+
}
|
|
538
|
+
}
|