Skip to main content

axsys_noun/
sept.rs

1//! Ported standard library functions for working with Noun data
2use std::str::Utf8Error;
3
4#[derive(Debug)]
5pub enum ParseError {
6    Invalid,
7    Utf8(Utf8Error),
8}
9
10#[derive(Debug)]
11pub enum FormatError {
12    TooLarge,
13    Utf8(Utf8Error),
14}
15
16pub enum Aura {
17    /// A utf-8 string
18    T,
19    /// A utf-8 string
20    Ta,
21    /// A utf-8 string composed of alphanumeric characters and hyphens
22    Tas,
23    /// Unsigned Integer
24    U,
25    /// Unsigned Integer, decimal
26    Ud,
27    /// Unsigned Integer, hexadecimal
28    Ux,
29    /// Unsigned Integer, base32
30    Uv,
31    /// Unsigned Integer, base64
32    Uw,
33    /// Urbit date
34    Da,
35    /// Urbit time interval
36    Dr,
37}
38
39pub mod date {
40    use std::str::FromStr;
41
42    use crate::sept::ParseError;
43
44    #[derive(Debug)]
45    struct Dat {
46        pos: bool,
47        year: u64,
48        month: u64,
49        time: Tarp,
50    }
51
52    #[derive(Debug, PartialEq)]
53    struct Tarp {
54        day: u64,
55        hour: u64,
56        minute: u64,
57        second: u64,
58        ms: Vec<u64>,
59    }
60
61    const DA_UNIX_EPOCH: u128 = 170141184475152167957503069145530368000; // `@ud` ~1970.1.1
62    const DA_SECOND: u128 = 18446744073709551616; // `@ud` ~s1
63    const EPOCH: u64 = 292277024400;
64
65    const MOH_YO: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
66    const MOY_YO: [u64; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
67    const DAY_YO: u64 = 86400;
68    const HOR_YO: u64 = 3600;
69    const MIT_YO: u64 = 60;
70    const ERA_YO: u64 = 146097;
71    const CET_YO: u64 = 36524;
72
73    fn is_leap_year(year: u64) -> bool {
74        (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
75    }
76
77    fn year(det: &Dat) -> u128 {
78        let yer = if det.pos {
79            EPOCH + det.year
80        } else {
81            EPOCH - (det.year - 1)
82        };
83
84        let day = {
85            let cah = if is_leap_year(yer) {
86                MOY_YO.to_vec()
87            } else {
88                MOH_YO.to_vec()
89            };
90            let mut d = det.time.day - 1;
91            let mut m = det.month - 1;
92            let mut idx = 0;
93            while m != 0 {
94                d += cah[idx];
95                m -= 1;
96                idx += 1;
97            }
98
99            let mut loop_continue = true;
100            let mut y = yer;
101
102            while loop_continue {
103                if y % 4 != 0 {
104                    y -= 1;
105                    d += if is_leap_year(y) { 366 } else { 365 };
106                } else if y % 100 != 0 {
107                    y -= 4;
108                    d += if is_leap_year(y) { 1461 } else { 1460 };
109                } else if y % 400 != 0 {
110                    y -= 100;
111                    d += if is_leap_year(y) { 36525 } else { 36524 };
112                } else {
113                    let eras = y / 400;
114                    d += eras * (4 * 36524 + 1);
115                    loop_continue = false;
116                }
117            }
118            d
119        };
120
121        let sec = det.time.second as u128
122            + (DAY_YO as u128 * day as u128)
123            + (HOR_YO as u128 * det.time.hour as u128)
124            + (MIT_YO as u128 * det.time.minute as u128);
125
126        let mut fac: u128 = 0;
127        let mut muc = 3;
128
129        for &ms in &det.time.ms {
130            fac += (ms as u128) << (16 * muc);
131            muc -= 1;
132        }
133
134        fac | (sec << 64)
135    }
136
137    pub fn parse_da(x: &str) -> Result<u128, ParseError> {
138        let parts: Vec<&str> = x.split("..").collect();
139        let date_parts: Vec<&str> = parts[0][1..].split('.').collect();
140        let time_parts: Vec<&str> = parts[1].split('.').collect();
141        let ms_parts: Vec<&str> = parts[2].split('.').collect();
142
143        let millis: Vec<u64> = ms_parts
144            .iter()
145            .map(|m| u64::from_str_radix(m, 16).map_err(|e| ParseError::Invalid))
146            .collect::<Result<Vec<u64>, _>>()?;
147
148        fn parse<F: FromStr>(s: &str) -> Result<F, ParseError> {
149            F::from_str(s).map_err(|_e| ParseError::Invalid)
150        }
151
152        Ok(year(&Dat {
153            pos: true,
154            year: parse(date_parts[0])?,
155            month: parse(date_parts[1])?,
156            time: Tarp {
157                day: parse(date_parts[2])?,
158                hour: parse(time_parts[0])?,
159                minute: parse(time_parts[1])?,
160                second: parse(time_parts[2])?,
161                ms: millis,
162            },
163        }))
164    }
165    fn yell(x: u128) -> Tarp {
166        let sec = x >> 64;
167        let raw = x & ((1 << 64) - 1); // Get lower 64 bits
168
169        // Process milliseconds similar to Hoon's implementation
170        let mut ms = Vec::new();
171        let mut muc = 4;
172        let mut current_raw = raw;
173
174        while current_raw != 0 && muc > 0 {
175            muc -= 1;
176            // Cut 4 bytes (32 bits) at position muc
177            let value = (current_raw >> (muc * 16)) & 0xFFFF;
178            if value != 0 {
179                ms.push(value as u64);
180            }
181            // Only keep the lower bits for next iteration
182            current_raw &= (1 << (muc * 16)) - 1;
183        }
184
185        let day = sec / DAY_YO as u128;
186        let mut sec = sec % DAY_YO as u128;
187        let hor = sec / HOR_YO as u128;
188        sec %= HOR_YO as u128;
189        let mit = sec / MIT_YO as u128;
190        sec %= MIT_YO as u128;
191
192        Tarp {
193            ms,
194            day: day as u64,
195            minute: mit as u64,
196            hour: hor as u64,
197            second: sec as u64,
198        }
199    }
200
201    fn yall(mut day: u64) -> (u64, u64, u64) {
202        let era = day / ERA_YO;
203        day %= ERA_YO;
204
205        let (cet, mut lep) = if day < (CET_YO + 1) {
206            (0, true)
207        } else {
208            let mut cet = 1;
209            day -= CET_YO + 1;
210            cet += day / CET_YO;
211            day %= CET_YO;
212            (cet, false)
213        };
214
215        let mut yer = (era * 400) + (cet * 100);
216        let mut loop_continue = true;
217
218        while loop_continue {
219            let dis = if lep { 366 } else { 365 };
220            if day >= dis {
221                yer += 1;
222                day -= dis;
223                lep = yer % 4 == 0;
224            } else {
225                loop_continue = false;
226                let mut mot = 0;
227                let cah = if lep { &MOY_YO } else { &MOH_YO };
228
229                while mot < 12 {
230                    let zis = cah[mot as usize];
231                    if day < zis {
232                        return (yer, mot + 1, day + 1);
233                    }
234                    mot += 1;
235                    day -= zis;
236                }
237            }
238        }
239
240        (0, 0, 0)
241    }
242
243    fn yore(x: u128) -> Dat {
244        let time = yell(x);
245        let (y, month, d) = yall(time.day);
246        let pos = y > EPOCH;
247        let year = if pos { y - EPOCH } else { EPOCH + 1 - y };
248
249        Dat {
250            pos,
251            year,
252            month,
253            time: Tarp { day: d, ..time },
254        }
255    }
256
257    fn format_ms(ms: &[u64]) -> String {
258        let foo = ms
259            .iter()
260            .rev()
261            .skip_while(|x| **x == 0)
262            .collect::<Vec<&u64>>();
263        let ms_part = foo
264            .iter()
265            .rev()
266            .map(|x| format!("{:04x}", x))
267            .collect::<Vec<String>>()
268            .join(".");
269        ms_part
270    }
271
272    pub fn format_da(x: u128) -> String {
273        let dat = yore(x);
274        let ms_part = format_ms(&dat.time.ms);
275
276        format!(
277            "~{}.{}.{}..{}.{}.{}..{}",
278            dat.year,
279            dat.month,
280            dat.time.day,
281            dat.time.hour,
282            dat.time.minute,
283            dat.time.second,
284            ms_part
285        )
286    }
287
288    pub fn format_date(x: u128) -> String {
289        let dat = yore(x);
290        format!("~{}.{}.{}", dat.year, dat.month, dat.time.day)
291    }
292
293    pub fn format_time(x: u128) -> String {
294        let dat = yore(x);
295        format!(
296            "~{}.{}.{}..{}.{}.{}",
297            dat.year, dat.month, dat.time.day, dat.time.hour, dat.time.minute, dat.time.second
298        )
299    }
300
301    pub fn da_to_unix(da: u128) -> u64 {
302        let offset = DA_SECOND / 2000;
303        let epoch_adjusted = offset + (da - DA_UNIX_EPOCH);
304
305        ((epoch_adjusted * 1000 / DA_SECOND) as f64).round() as u64
306    }
307
308    pub fn unix_to_da(unix: u64) -> u128 {
309        let time_since_epoch = (unix as u128) * DA_SECOND / 1000;
310        DA_UNIX_EPOCH + time_since_epoch
311    }
312
313    pub fn get_time() -> u128 {
314        let now = std::time::SystemTime::now();
315        let since_the_epoch = now.duration_since(std::time::UNIX_EPOCH).unwrap();
316
317        unix_to_da(since_the_epoch.as_millis() as u64)
318    }
319
320    #[cfg(test)]
321    mod tests {
322        use super::*;
323
324        #[test]
325        fn test_format_da() {
326            let word = 170141184507102559553699322435033104384u128;
327            let str = format_da(word);
328            let tarp = yell(word);
329            let expected = Tarp {
330                ms: vec![1175],
331                day: 106751991823991,
332                hour: 16,
333                minute: 36,
334                second: 39,
335            };
336            assert_eq!(tarp, expected);
337        }
338
339        #[test]
340        fn test_parse_da() {
341            let word = 170141184507102559553699322435033104384u128;
342            let str = "~2024.11.19..16.36.39..0497";
343            let da = parse_da(str).unwrap();
344            assert_eq!(da, word);
345        }
346    }
347}