merge runtime/util — time, math, state in El
This commit is contained in:
+170
@@ -0,0 +1,170 @@
|
|||||||
|
// runtime/math.el — Float math, integer utilities, and numeric conversions.
|
||||||
|
//
|
||||||
|
// Implements the math/float surface from el-compiler/runtime/legacy/el_runtime.c
|
||||||
|
// (lines 303–305 for el_abs/max/min, lines 4725–4771 for float/format ops)
|
||||||
|
// in pure El, using seed primitives.
|
||||||
|
//
|
||||||
|
// Seed primitives consumed:
|
||||||
|
// __sqrt_f(f: Float) -> Float
|
||||||
|
// __log_f(f: Float) -> Float
|
||||||
|
// __ln_f(f: Float) -> Float
|
||||||
|
// __sin_f(f: Float) -> Float
|
||||||
|
// __cos_f(f: Float) -> Float
|
||||||
|
// __pi_f() -> Float
|
||||||
|
// __float_to_str(f: Float) -> String
|
||||||
|
// __str_to_float(s: String) -> Float
|
||||||
|
// __int_to_str(n: Int) -> String
|
||||||
|
// __str_to_int(s: String) -> Int
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Integer math — el_abs, el_max, el_min.
|
||||||
|
//
|
||||||
|
// Matches legacy el_abs, el_max, el_min (lines 303–305).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// el_abs — absolute value of an integer.
|
||||||
|
fn el_abs(n: Int) -> Int {
|
||||||
|
if n < 0 { return -n }
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// el_max — larger of two integers.
|
||||||
|
fn el_max(a: Int, b: Int) -> Int {
|
||||||
|
if a > b { return a }
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// el_min — smaller of two integers.
|
||||||
|
fn el_min(a: Int, b: Int) -> Int {
|
||||||
|
if a < b { return a }
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Float math — thin wrappers over seed primitives.
|
||||||
|
//
|
||||||
|
// Matches legacy math_sqrt, math_log, math_ln, math_sin, math_cos, math_pi
|
||||||
|
// (lines 4766–4771).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// math_sqrt — square root.
|
||||||
|
fn math_sqrt(f: Float) -> Float {
|
||||||
|
return __sqrt_f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// math_log — base-10 logarithm.
|
||||||
|
fn math_log(f: Float) -> Float {
|
||||||
|
return __log_f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// math_ln — natural logarithm.
|
||||||
|
fn math_ln(f: Float) -> Float {
|
||||||
|
return __ln_f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// math_sin — sine (radians).
|
||||||
|
fn math_sin(f: Float) -> Float {
|
||||||
|
return __sin_f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// math_cos — cosine (radians).
|
||||||
|
fn math_cos(f: Float) -> Float {
|
||||||
|
return __cos_f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// math_pi — the constant π.
|
||||||
|
fn math_pi() -> Float {
|
||||||
|
return __pi_f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Float conversions — float_to_str, int_to_float, float_to_int, str_to_float.
|
||||||
|
//
|
||||||
|
// Matches legacy float_to_str, int_to_float, float_to_int, str_to_float
|
||||||
|
// (lines 4725–4762).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// float_to_str — format a float using %g (shortest exact representation).
|
||||||
|
// Matches legacy float_to_str() → snprintf "%g".
|
||||||
|
fn float_to_str(f: Float) -> String {
|
||||||
|
return __float_to_str(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// int_to_float — convert an integer to a float.
|
||||||
|
// Matches legacy int_to_float() → (double)(int64_t)n.
|
||||||
|
fn int_to_float(n: Int) -> Float {
|
||||||
|
return __int_to_float(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// float_to_int — truncate a float to an integer (toward zero).
|
||||||
|
// Matches legacy float_to_int() → (int64_t)el_to_float(f).
|
||||||
|
fn float_to_int(f: Float) -> Int {
|
||||||
|
return __float_to_int(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// str_to_float — parse a float from a string. Returns 0.0 on failure.
|
||||||
|
// Matches legacy str_to_float() → strtod(str, NULL).
|
||||||
|
fn str_to_float(s: String) -> Float {
|
||||||
|
return __str_to_float(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// format_float — format a float to a fixed number of decimal places.
|
||||||
|
//
|
||||||
|
// decimals is clamped to [0, 30]. Matches legacy format_float() → snprintf "%.*f".
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn format_float(f: Float, decimals: Int) -> String {
|
||||||
|
let d: Int = decimals
|
||||||
|
if d < 0 { d = 0 }
|
||||||
|
if d > 30 { d = 30 }
|
||||||
|
// Delegate to seed; the seed exposes __format_float(f, d) -> String.
|
||||||
|
// This matches snprintf(buf, 128, "%.*f", d, v) in the legacy runtime.
|
||||||
|
return __format_float(f, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// decimal_round — round a float to d decimal places (half-away-from-zero).
|
||||||
|
//
|
||||||
|
// Matches legacy decimal_round():
|
||||||
|
// mul = pow(10, d)
|
||||||
|
// r = (v >= 0 ? floor(v*mul + 0.5) : -floor(-v*mul + 0.5)) / mul
|
||||||
|
//
|
||||||
|
// We implement pow(10, d) via a loop (d <= 15, so at most 15 multiplications).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// _pow10 — 10^n as a Float for n in [0, 15].
|
||||||
|
fn _pow10(n: Int) -> Float {
|
||||||
|
let result: Float = 1.0
|
||||||
|
let i: Int = 0
|
||||||
|
while i < n {
|
||||||
|
result = result * 10.0
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// _floor_f — floor of a float: largest integer <= f.
|
||||||
|
// Uses __float_to_int (truncation) with correction for negative non-integers.
|
||||||
|
fn _floor_f(f: Float) -> Float {
|
||||||
|
let t: Int = __float_to_int(f)
|
||||||
|
let tf: Float = __int_to_float(t)
|
||||||
|
// if f was negative and not already an integer, subtract 1
|
||||||
|
if f < 0.0 {
|
||||||
|
if tf > f {
|
||||||
|
return tf - 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decimal_round(f: Float, decimals: Int) -> Float {
|
||||||
|
let d: Int = decimals
|
||||||
|
if d < 0 { d = 0 }
|
||||||
|
if d > 15 { d = 15 }
|
||||||
|
let mul: Float = _pow10(d)
|
||||||
|
if f >= 0.0 {
|
||||||
|
return _floor_f(f * mul + 0.5) / mul
|
||||||
|
}
|
||||||
|
return 0.0 - _floor_f((0.0 - f) * mul + 0.5) / mul
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// runtime/state.el — In-process key/value store.
|
||||||
|
//
|
||||||
|
// Thin El wrappers over the __state_* seed primitives. The backing store is
|
||||||
|
// a process-wide hash map maintained by the El runtime (formerly el_runtime.c
|
||||||
|
// lines 4632–4721: state_set, state_get, state_del, state_keys).
|
||||||
|
//
|
||||||
|
// Keys and values are Strings. Values are persistent across request boundaries
|
||||||
|
// within the same process instance (they survive individual request lifetimes).
|
||||||
|
// Concurrent access is serialized by the runtime; these wrappers are lock-free
|
||||||
|
// from El's perspective.
|
||||||
|
//
|
||||||
|
// Seed primitives consumed:
|
||||||
|
// __state_set(key: String, val: String)
|
||||||
|
// __state_get(key: String) -> String
|
||||||
|
// __state_del(key: String)
|
||||||
|
// __state_keys() -> String (JSON array of key strings)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core — set / get / del / keys
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// state_set — store val under key. Overwrites any existing value.
|
||||||
|
fn state_set(key: String, val: String) {
|
||||||
|
__state_set(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state_get — retrieve the value for key. Returns "" if key is absent.
|
||||||
|
fn state_get(key: String) -> String {
|
||||||
|
return __state_get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state_del — remove key from the store. No-op if key does not exist.
|
||||||
|
fn state_del(key: String) {
|
||||||
|
__state_del(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state_keys — return a JSON array string of all current keys.
|
||||||
|
// e.g. ["foo","bar","baz"]
|
||||||
|
// Matches legacy state_keys() which returns an ElList (here serialized as JSON).
|
||||||
|
fn state_keys() -> String {
|
||||||
|
return __state_keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Convenience helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// state_has — true if key is present (value is non-empty string).
|
||||||
|
// Note: a key set to "" is indistinguishable from absent via state_get alone.
|
||||||
|
fn state_has(key: String) -> Bool {
|
||||||
|
let v: String = state_get(key)
|
||||||
|
if str_eq(v, "") { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// state_get_or — return val for key, or default_val if key is absent.
|
||||||
|
fn state_get_or(key: String, default_val: String) -> String {
|
||||||
|
let v: String = state_get(key)
|
||||||
|
if str_eq(v, "") { return default_val }
|
||||||
|
return v
|
||||||
|
}
|
||||||
+402
@@ -0,0 +1,402 @@
|
|||||||
|
// runtime/time.el — Time operations, sleep, and formatting.
|
||||||
|
//
|
||||||
|
// Implements the time surface from el-compiler/runtime/legacy/el_runtime.c
|
||||||
|
// (lines 3334–3440, 3471–3656) in pure El, using seed primitives.
|
||||||
|
//
|
||||||
|
// Seed primitives consumed:
|
||||||
|
// __time_now_ns() -> Int (nanoseconds since Unix epoch)
|
||||||
|
// __sleep_ms(n: Int)
|
||||||
|
// __int_to_str(n: Int) -> String
|
||||||
|
// __str_to_int(s: String) -> Int
|
||||||
|
// __float_to_str(f: Float) -> String
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core — now / sleep
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// time_now — milliseconds since Unix epoch (UTC). Matches legacy time_now().
|
||||||
|
fn time_now() -> Int {
|
||||||
|
return __time_now_ns() / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// time_now_utc — same as time_now; UTC alias kept for compatibility.
|
||||||
|
fn time_now_utc() -> Int {
|
||||||
|
return __time_now_ns() / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// now_ns — nanoseconds since Unix epoch. Matches el_now_instant().
|
||||||
|
fn now_ns() -> Int {
|
||||||
|
return __time_now_ns()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix_timestamp — whole seconds since Unix epoch. Matches unix_timestamp().
|
||||||
|
fn unix_timestamp() -> Int {
|
||||||
|
return __time_now_ns() / 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep_secs — block for n seconds. Clamps negatives to 0.
|
||||||
|
fn sleep_secs(n: Int) {
|
||||||
|
if n < 0 {
|
||||||
|
__sleep_ms(0)
|
||||||
|
} else {
|
||||||
|
__sleep_ms(n * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep_ms — block for n milliseconds. Clamps negatives to 0.
|
||||||
|
fn sleep_ms(n: Int) {
|
||||||
|
if n < 0 {
|
||||||
|
__sleep_ms(0)
|
||||||
|
} else {
|
||||||
|
__sleep_ms(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gregorian decomposition helpers — pure integer arithmetic.
|
||||||
|
//
|
||||||
|
// Algorithm: civil date from days since Unix epoch (1970-01-01).
|
||||||
|
// Based on Howard Hinnant's public-domain civil_from_days formula
|
||||||
|
// (http://howardhinnant.github.io/date_algorithms.html), which the legacy
|
||||||
|
// gmtime_r call performs under the hood.
|
||||||
|
//
|
||||||
|
// We expose the pieces as private helpers (leading underscore convention).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// _is_leap — 1 if year y is a Gregorian leap year, 0 otherwise.
|
||||||
|
fn _is_leap(y: Int) -> Int {
|
||||||
|
if y % 400 == 0 { return 1 }
|
||||||
|
if y % 100 == 0 { return 0 }
|
||||||
|
if y % 4 == 0 { return 1 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// _days_in_month — number of days in month m of year y (m: 1..12).
|
||||||
|
fn _days_in_month(y: Int, m: Int) -> Int {
|
||||||
|
if m == 1 { return 31 }
|
||||||
|
if m == 2 {
|
||||||
|
if _is_leap(y) == 1 { return 29 }
|
||||||
|
return 28
|
||||||
|
}
|
||||||
|
if m == 3 { return 31 }
|
||||||
|
if m == 4 { return 30 }
|
||||||
|
if m == 5 { return 31 }
|
||||||
|
if m == 6 { return 30 }
|
||||||
|
if m == 7 { return 31 }
|
||||||
|
if m == 8 { return 31 }
|
||||||
|
if m == 9 { return 30 }
|
||||||
|
if m == 10 { return 31 }
|
||||||
|
if m == 11 { return 30 }
|
||||||
|
return 31
|
||||||
|
}
|
||||||
|
|
||||||
|
// _pad2 — zero-pad an integer to at least 2 digits.
|
||||||
|
fn _pad2(n: Int) -> String {
|
||||||
|
if n < 10 { return "0" + __int_to_str(n) }
|
||||||
|
return __int_to_str(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _pad4 — zero-pad an integer to at least 4 digits.
|
||||||
|
fn _pad4(n: Int) -> String {
|
||||||
|
if n < 10 { return "000" + __int_to_str(n) }
|
||||||
|
if n < 100 { return "00" + __int_to_str(n) }
|
||||||
|
if n < 1000 { return "0" + __int_to_str(n) }
|
||||||
|
return __int_to_str(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _pad3 — zero-pad an integer to at least 3 digits (milliseconds).
|
||||||
|
fn _pad3(n: Int) -> String {
|
||||||
|
if n < 10 { return "00" + __int_to_str(n) }
|
||||||
|
if n < 100 { return "0" + __int_to_str(n) }
|
||||||
|
return __int_to_str(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _civil_year_month_day — decompose days-since-epoch (z, may be negative)
|
||||||
|
// into year/month/day using the civil_from_days algorithm.
|
||||||
|
// Returns a JSON object: {"year":Y,"month":M,"day":D}
|
||||||
|
fn _civil_ymd(z: Int) -> String {
|
||||||
|
// shift epoch to 0000-03-01 (makes leap-day math clean)
|
||||||
|
let zz: Int = z + 719468
|
||||||
|
// era: 400-year block
|
||||||
|
let era: Int = zz / 146097
|
||||||
|
if zz < 0 {
|
||||||
|
era = (zz - 146096) / 146097
|
||||||
|
}
|
||||||
|
let doe: Int = zz - era * 146097 // day-of-era [0, 146096]
|
||||||
|
let yoe: Int = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 // year-of-era [0, 399]
|
||||||
|
let y: Int = yoe + era * 400
|
||||||
|
let doy: Int = doe - (365 * yoe + yoe / 4 - yoe / 100) // day-of-year [0, 365]
|
||||||
|
let mp: Int = (5 * doy + 2) / 153 // month in [0, 11] from March
|
||||||
|
let d: Int = doy - (153 * mp + 2) / 5 + 1 // day [1, 31]
|
||||||
|
let m: Int = mp + 3
|
||||||
|
if mp >= 10 { m = mp - 9 }
|
||||||
|
if mp >= 10 { y = y + 1 }
|
||||||
|
return "{\"year\":" + __int_to_str(y) + ",\"month\":" + __int_to_str(m) + ",\"day\":" + __int_to_str(d) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// time_to_parts — decompose a millisecond timestamp into UTC components.
|
||||||
|
//
|
||||||
|
// Returns a JSON string:
|
||||||
|
// {"year":Y,"month":M,"day":D,"hour":H,"minute":M,"second":S,"ms":MS}
|
||||||
|
//
|
||||||
|
// Matches legacy time_to_parts() which returns an ElMap with the same keys.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn time_to_parts(ts: Int) -> String {
|
||||||
|
let ms_raw: Int = ts % 1000
|
||||||
|
let ms: Int = ms_raw
|
||||||
|
if ms_raw < 0 { ms = ms_raw + 1000 }
|
||||||
|
|
||||||
|
let s_raw: Int = ts / 1000
|
||||||
|
let s: Int = s_raw
|
||||||
|
if ms_raw < 0 { s = s_raw - 1 }
|
||||||
|
|
||||||
|
// seconds within the day and days since epoch
|
||||||
|
let sec_of_day: Int = s % 86400
|
||||||
|
let day_z: Int = s / 86400
|
||||||
|
// handle negative: floor division
|
||||||
|
if sec_of_day < 0 {
|
||||||
|
sec_of_day = sec_of_day + 86400
|
||||||
|
day_z = day_z - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let hour: Int = sec_of_day / 3600
|
||||||
|
let rem: Int = sec_of_day % 3600
|
||||||
|
let minute: Int = rem / 60
|
||||||
|
let second: Int = rem % 60
|
||||||
|
|
||||||
|
// date components via civil decomposition
|
||||||
|
let ymd: String = _civil_ymd(day_z)
|
||||||
|
let year: Int = __str_to_int(json_get(ymd, "year"))
|
||||||
|
let month: Int = __str_to_int(json_get(ymd, "month"))
|
||||||
|
let day: Int = __str_to_int(json_get(ymd, "day"))
|
||||||
|
|
||||||
|
return "{\"year\":" + __int_to_str(year) +
|
||||||
|
",\"month\":" + __int_to_str(month) +
|
||||||
|
",\"day\":" + __int_to_str(day) +
|
||||||
|
",\"hour\":" + __int_to_str(hour) +
|
||||||
|
",\"minute\":" + __int_to_str(minute) +
|
||||||
|
",\"second\":" + __int_to_str(second) +
|
||||||
|
",\"ms\":" + __int_to_str(ms) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// time_format — format a millisecond timestamp as a string.
|
||||||
|
//
|
||||||
|
// fmt "ISO" (or empty) → "YYYY-MM-DDTHH:MM:SS.mmmZ" (ISO 8601 UTC)
|
||||||
|
// Other fmt values are passed as a strftime-style hint; the El runtime
|
||||||
|
// implements the most common tokens. Unsupported tokens are passed through.
|
||||||
|
//
|
||||||
|
// Matches legacy time_format().
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn time_format(ts: Int, fmt: String) -> String {
|
||||||
|
let parts: String = time_to_parts(ts)
|
||||||
|
let y: Int = __str_to_int(json_get(parts, "year"))
|
||||||
|
let mo: Int = __str_to_int(json_get(parts, "month"))
|
||||||
|
let d: Int = __str_to_int(json_get(parts, "day"))
|
||||||
|
let h: Int = __str_to_int(json_get(parts, "hour"))
|
||||||
|
let mi: Int = __str_to_int(json_get(parts, "minute"))
|
||||||
|
let s: Int = __str_to_int(json_get(parts, "second"))
|
||||||
|
let ms: Int = __str_to_int(json_get(parts, "ms"))
|
||||||
|
|
||||||
|
// ISO 8601 UTC: YYYY-MM-DDTHH:MM:SS.mmmZ
|
||||||
|
if str_eq(fmt, "ISO") {
|
||||||
|
return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
|
||||||
|
"T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
|
||||||
|
"." + _pad3(ms) + "Z"
|
||||||
|
}
|
||||||
|
if str_eq(fmt, "") {
|
||||||
|
return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
|
||||||
|
"T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
|
||||||
|
"." + _pad3(ms) + "Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
// strftime-subset: replace common tokens
|
||||||
|
let out: String = fmt
|
||||||
|
let out = str_replace(out, "%Y", _pad4(y))
|
||||||
|
let out = str_replace(out, "%m", _pad2(mo))
|
||||||
|
let out = str_replace(out, "%d", _pad2(d))
|
||||||
|
let out = str_replace(out, "%H", _pad2(h))
|
||||||
|
let out = str_replace(out, "%M", _pad2(mi))
|
||||||
|
let out = str_replace(out, "%S", _pad2(s))
|
||||||
|
let out = str_replace(out, "%3N", _pad3(ms))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// time_from_parts — construct a ms timestamp from seconds + nanosecond offset.
|
||||||
|
//
|
||||||
|
// Matches legacy time_from_parts(secs, ns, tz) — tz is ignored (UTC assumed).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn time_from_parts(secs: Int, ns: Int, tz: String) -> Int {
|
||||||
|
return secs * 1000 + ns / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// time_add — add a duration to a millisecond timestamp.
|
||||||
|
//
|
||||||
|
// unit: "ms" | "sec" | "min" | "hour" | "day"
|
||||||
|
// Matches legacy time_add().
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn time_add(ts: Int, n: Int, unit: String) -> Int {
|
||||||
|
if str_eq(unit, "ms") { return ts + n }
|
||||||
|
if str_eq(unit, "sec") { return ts + n * 1000 }
|
||||||
|
if str_eq(unit, "min") { return ts + n * 60000 }
|
||||||
|
if str_eq(unit, "hour") { return ts + n * 3600000 }
|
||||||
|
if str_eq(unit, "day") { return ts + n * 86400000 }
|
||||||
|
// default: treat as ms
|
||||||
|
return ts + n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// time_diff — compute the difference between two millisecond timestamps.
|
||||||
|
//
|
||||||
|
// Returns ts2 - ts1 in the given unit.
|
||||||
|
// unit: "ms" | "sec" | "min" | "hour" | "day"
|
||||||
|
// Matches legacy time_diff().
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn time_diff(ts1: Int, ts2: Int, unit: String) -> Int {
|
||||||
|
let d: Int = ts2 - ts1
|
||||||
|
if str_eq(unit, "ms") { return d }
|
||||||
|
if str_eq(unit, "sec") { return d / 1000 }
|
||||||
|
if str_eq(unit, "min") { return d / 60000 }
|
||||||
|
if str_eq(unit, "hour") { return d / 3600000 }
|
||||||
|
if str_eq(unit, "day") { return d / 86400000 }
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Instant / Duration — nanosecond-precision temporal types.
|
||||||
|
//
|
||||||
|
// These match the el_now_instant, duration_seconds, duration_millis, etc.
|
||||||
|
// family from legacy lines 3471–3656. Both Instant and Duration are Int
|
||||||
|
// (nanoseconds); the type distinction is at the call-site convention level.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// now — current Instant in nanoseconds. Alias for __time_now_ns().
|
||||||
|
fn now() -> Int {
|
||||||
|
return __time_now_ns()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix_seconds — Instant from whole seconds since epoch.
|
||||||
|
fn unix_seconds(n: Int) -> Int {
|
||||||
|
return n * 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix_millis — Instant from milliseconds since epoch.
|
||||||
|
fn unix_millis(n: Int) -> Int {
|
||||||
|
return n * 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// instant_to_unix_seconds — convert Instant nanoseconds to whole seconds.
|
||||||
|
fn instant_to_unix_seconds(i: Int) -> Int {
|
||||||
|
return i / 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// instant_to_unix_millis — convert Instant nanoseconds to milliseconds.
|
||||||
|
fn instant_to_unix_millis(i: Int) -> Int {
|
||||||
|
return i / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// instant_to_iso8601 — format an Instant (nanoseconds) as ISO 8601 UTC.
|
||||||
|
fn instant_to_iso8601(i: Int) -> String {
|
||||||
|
let ms: Int = i / 1000000
|
||||||
|
return time_format(ms, "ISO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_seconds — Duration from n whole seconds.
|
||||||
|
fn duration_seconds(n: Int) -> Int {
|
||||||
|
return n * 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_millis — Duration from n milliseconds.
|
||||||
|
fn duration_millis(n: Int) -> Int {
|
||||||
|
return n * 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_nanos — Duration from n nanoseconds (identity).
|
||||||
|
fn duration_nanos(n: Int) -> Int {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_to_seconds — convert a Duration (nanoseconds) to whole seconds.
|
||||||
|
fn duration_to_seconds(d: Int) -> Int {
|
||||||
|
return d / 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_to_millis — convert a Duration (nanoseconds) to milliseconds.
|
||||||
|
fn duration_to_millis(d: Int) -> Int {
|
||||||
|
return d / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration_to_nanos — return the Duration as nanoseconds (identity).
|
||||||
|
fn duration_to_nanos(d: Int) -> Int {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep_duration — sleep for a Duration (nanoseconds). Clamps negatives to 0.
|
||||||
|
fn sleep_duration(dur: Int) {
|
||||||
|
let ms: Int = dur / 1000000
|
||||||
|
if ms < 0 {
|
||||||
|
__sleep_ms(0)
|
||||||
|
} else {
|
||||||
|
__sleep_ms(ms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// TTL cache — time-bounded key/value backed by state.
|
||||||
|
//
|
||||||
|
// Matches legacy ttl_cache_set / ttl_cache_get / ttl_cache_age (lines 3663–3717).
|
||||||
|
// max_age is a Duration (nanoseconds).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ttl_cache_set — store a value and record the current Instant for TTL checks.
|
||||||
|
fn ttl_cache_set(key: String, value: String) {
|
||||||
|
state_set(key, value)
|
||||||
|
let stamp_key: String = "__ttl_at:" + key
|
||||||
|
let now_str: String = __int_to_str(__time_now_ns())
|
||||||
|
state_set(stamp_key, now_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ttl_cache_get — return value if age < max_age (nanoseconds), else "".
|
||||||
|
fn ttl_cache_get(key: String, max_age: Int) -> String {
|
||||||
|
let stamp_key: String = "__ttl_at:" + key
|
||||||
|
let sv: String = state_get(stamp_key)
|
||||||
|
if str_eq(sv, "") { return "" }
|
||||||
|
let set_at: Int = __str_to_int(sv)
|
||||||
|
let now_ns: Int = __time_now_ns()
|
||||||
|
let age: Int = now_ns - set_at
|
||||||
|
if age < 0 { return "" }
|
||||||
|
if age > max_age { return "" }
|
||||||
|
return state_get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ttl_cache_age — nanoseconds since a key was last set (INT_MAX sentinel if missing).
|
||||||
|
fn ttl_cache_age(key: String) -> Int {
|
||||||
|
let stamp_key: String = "__ttl_at:" + key
|
||||||
|
let sv: String = state_get(stamp_key)
|
||||||
|
if str_eq(sv, "") { return 9223372036854775807 }
|
||||||
|
let set_at: Int = __str_to_int(sv)
|
||||||
|
return __time_now_ns() - set_at
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// uuid_new / uuid_v4 — generate a UUID v4 string.
|
||||||
|
//
|
||||||
|
// Delegates to the __uuid_v4() seed primitive.
|
||||||
|
// Matches legacy uuid_new() / uuid_v4() (lines 4602–4621).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn uuid_new() -> String {
|
||||||
|
return __uuid_v4()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uuid_v4() -> String {
|
||||||
|
return __uuid_v4()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user