tatara_lisp_script/stdlib/
time.rs1use std::thread;
12use std::time::{Duration, SystemTime, UNIX_EPOCH};
13
14use std::sync::Arc;
15use tatara_lisp_eval::{Arity, Interpreter, Value};
16
17use crate::script_ctx::ScriptCtx;
18use crate::stdlib::env::int_arg;
19
20pub fn install(interp: &mut Interpreter<ScriptCtx>) {
21 interp.register_fn(
22 "now",
23 Arity::Exact(0),
24 |_args: &[Value], _ctx: &mut ScriptCtx, _sp| {
25 Ok(Value::Int(
26 SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .map(|d| d.as_secs() as i64)
29 .unwrap_or(0),
30 ))
31 },
32 );
33
34 interp.register_fn(
35 "now-ms",
36 Arity::Exact(0),
37 |_args: &[Value], _ctx: &mut ScriptCtx, _sp| {
38 Ok(Value::Int(
39 SystemTime::now()
40 .duration_since(UNIX_EPOCH)
41 .map(|d| d.as_millis() as i64)
42 .unwrap_or(0),
43 ))
44 },
45 );
46
47 interp.register_fn(
48 "now-ns",
49 Arity::Exact(0),
50 |_args: &[Value], _ctx: &mut ScriptCtx, _sp| {
51 Ok(Value::Int(
52 SystemTime::now()
53 .duration_since(UNIX_EPOCH)
54 .map(|d| d.as_nanos() as i64)
55 .unwrap_or(0),
56 ))
57 },
58 );
59
60 interp.register_fn(
61 "now-rfc3339",
62 Arity::Exact(0),
63 |_args: &[Value], _ctx: &mut ScriptCtx, _sp| {
64 let now = SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .map(|d| d.as_secs() as i64)
67 .unwrap_or(0);
68 Ok(Value::Str(Arc::from(format_rfc3339_utc(now))))
69 },
70 );
71
72 interp.register_fn(
73 "sleep",
74 Arity::Exact(1),
75 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
76 let secs = int_arg(&args[0], "sleep", sp)?;
77 if secs > 0 {
78 thread::sleep(Duration::from_secs(secs as u64));
79 }
80 Ok(Value::Nil)
81 },
82 );
83
84 interp.register_fn(
85 "sleep-ms",
86 Arity::Exact(1),
87 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
88 let ms = int_arg(&args[0], "sleep-ms", sp)?;
89 if ms > 0 {
90 thread::sleep(Duration::from_millis(ms as u64));
91 }
92 Ok(Value::Nil)
93 },
94 );
95
96 interp.register_fn(
97 "elapsed-since",
98 Arity::Exact(1),
99 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
100 let start_ns = int_arg(&args[0], "elapsed-since", sp)?;
101 let now_ns = SystemTime::now()
102 .duration_since(UNIX_EPOCH)
103 .map(|d| d.as_nanos() as i64)
104 .unwrap_or(0);
105 Ok(Value::Int(now_ns - start_ns))
106 },
107 );
108}
109
110fn format_rfc3339_utc(unix_secs: i64) -> String {
113 let (mut y, mut m, mut d, h, mi, s) = seconds_to_datetime(unix_secs);
115 if d == 0 {
116 d = 1;
117 }
118 if m == 0 {
119 m = 1;
120 }
121 if y == 0 {
122 y = 1970;
123 }
124 format!("{y:04}-{m:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z")
125}
126
127fn seconds_to_datetime(unix_secs: i64) -> (i64, u32, u32, u32, u32, u32) {
128 let days = unix_secs / 86_400;
129 let rem = unix_secs.rem_euclid(86_400);
130 let h = (rem / 3600) as u32;
131 let mi = ((rem % 3600) / 60) as u32;
132 let s = (rem % 60) as u32;
133 let (y, m, d) = days_to_ymd(days);
134 (y, m, d, h, mi, s)
135}
136
137fn days_to_ymd(days: i64) -> (i64, u32, u32) {
139 let z = days + 719_468;
141 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
142 let doe = (z - era * 146_097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
144 let y = yoe as i64 + era * 400;
145 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
146 let mp = (5 * doy + 2) / 153;
147 let d = doy - (153 * mp + 2) / 5 + 1;
148 let m = if mp < 10 { mp + 3 } else { mp - 9 };
149 let y = if m <= 2 { y + 1 } else { y };
150 (y, m, d)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn rfc3339_epoch() {
159 assert_eq!(format_rfc3339_utc(0), "1970-01-01T00:00:00Z");
160 }
161
162 #[test]
163 fn rfc3339_y2k() {
164 assert_eq!(format_rfc3339_utc(946_684_800), "2000-01-01T00:00:00Z");
166 }
167}