tai64.fnl (4227B)
1 (local { : base64 : assoc } (require :anoia)) 2 (import-macros { : expect= : define-tests } :anoia.assert) 3 4 (local 5 leap-seconds-list 6 (let [tbl 7 [ 8 ;; https://data.iana.org/time-zones/data/leap-seconds.list 9 ;; comments are the _start_ of the day where the second was 10 ;; added at the end of the previous day 11 [2272060800 10] ; 1 jan 1972 ; baseline, not a leap second 12 [2287785600 11] ; 1 jul 1972 13 [2303683200 12] ; 1 jan 1973 14 [2335219200 13] ; 1 jan 1974 15 [2366755200 14] ; 1 Jan 1975 16 [2398291200 15] ; 1 Jan 1976 17 [2429913600 16] ; 1 Jan 1977 18 [2461449600 17] ; 1 Jan 1978 19 [2492985600 18] ; 1 Jan 1979 20 [2524521600 19] ; 1 Jan 1980 21 [2571782400 20] ; 1 Jul 1981 22 [2603318400 21] ; 1 Jul 1982 23 [2634854400 22] ; 1 Jul 1983 24 [2698012800 23] ; 1 Jul 1985 25 [2776982400 24] ; 1 Jan 1988 26 [2840140800 25] ; 1 Jan 1990 27 [2871676800 26] ; 1 Jan 1991 28 [2918937600 27] ; 1 Jul 1992 29 [2950473600 28] ; 1 Jul 1993 30 [2982009600 29] ; 1 Jul 1994 31 [3029443200 30] ; 1 Jan 1996 32 [3076704000 31] ; 1 Jul 1997 33 [3124137600 32] ; 1 Jan 1999 34 [3345062400 33] ; 1 Jan 2006 35 [3439756800 34] ; 1 Jan 2009 36 [3550089600 35] ; 1 Jul 2012 37 [3644697600 36] ; 1 Jul 2015 38 [3692217600 37] ; 1 Jan 2017 39 ]] 40 { 41 :tai 42 (icollect [_ [ts dtai] (ipairs tbl)] 43 [(+ (- ts 2208988800) dtai) dtai]) 44 :unix 45 (icollect [_ [ts dtai] (ipairs tbl)] 46 [(- ts 2208988800) dtai]) 47 })) 48 49 50 (fn leap-seconds [timestamp list] 51 (accumulate [secs 10 52 _ [epoch leap-seconds] (ipairs list) 53 &until (> epoch timestamp)] 54 leap-seconds)) 55 56 (fn leap-seconds-tai [tai] (leap-seconds tai leap-seconds-list.tai)) 57 (fn leap-seconds-unix [unix] (leap-seconds unix leap-seconds-list.unix)) 58 59 (define-tests :leap-seconds 60 (expect= (leap-seconds-tai 104694412) 12) 61 (expect= (leap-seconds-tai 23) 10) 62 (expect= (leap-seconds-tai (+ 3692217600 60)) 37) 63 (expect= (leap-seconds-tai (+ 10 773020829)) 29) 64 (expect= (leap-seconds-tai 362793520) 19)) 65 66 (fn from-timestamp [str] 67 (if (= (string.sub str 1 1) "@") 68 (let [s (tonumber (string.sub str 2 17) 16) 69 two_62 (lshift 1 62) 70 sec (if (>= s two_62) 71 (- s two_62) 72 (- two_62 s)) 73 nano (tonumber (string.sub str 18 25) 16)] 74 {:s sec :n nano}) 75 nil)) 76 77 (fn to-timestamp [{: s : n }] 78 (let [label (+ s (lshift 1 62))] 79 (string.format "@%16x%08x" label n))) 80 81 (fn to-unix [tai] 82 (values (- tai.s (leap-seconds-tai tai.s)) tai.n)) 83 84 (fn from-unix [seconds nanos] 85 (let [s (math.floor seconds) 86 ;; subsecond value may be given as fraction or as a separate 87 ;; param, but not both 88 n (or nanos (* (- seconds s) 1e9))] 89 { :s 90 (+ s (leap-seconds-unix s)) 91 :n n })) 92 93 (define-tests 94 (expect= 95 (from-timestamp "@4000000068e2f0d3257dc09b") 96 {:s 1759703251 :n 628998299}) 97 98 (expect= 99 (to-timestamp {:s 1759703251 :n 628998299}) 100 "@4000000068e2f0d3257dc09b") 101 102 (expect= 103 (to-timestamp {:s -43200 :n 0}) 104 "@3fffffffffff574000000000") 105 106 (let [(s n) (to-unix (from-timestamp "@4000000068e2f0d3257dc09b"))] 107 (expect= [s n] [1759703214 628998299])) 108 109 (let [{ : s : n } (from-unix 1759703214 1e6)] 110 (expect= [s n] [1759703251 1e6])) 111 112 (let [{ : s : n } (from-unix 1759703214.5)] 113 (expect= [s n] [1759703251 5e8])) 114 115 ;; check the leap second count is looked up correctly around a 116 ;; boundary 117 (let [unix (os.time {:year 2016 :month 12 :day 31 :hour 23 :min 59 :sec 58}) 118 tai (from-unix unix)] 119 (expect= (- tai.s unix) 36)) 120 (let [unix (os.time {:year 2016 :month 12 :day 31 :hour 23 :min 59 :sec 63}) 121 tai (from-unix unix)] 122 (expect= (- tai.s unix) 37)) 123 ) 124 125 126 { : from-timestamp : to-timestamp : to-unix : from-unix }