nandi/frqpublic Fork 0
a32699ed51bfbcc605737c045b0f1b5673a05a7c
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

clock.jolt · 96 lines · 4.3 KBMySQL Blame HistoryRaw
Say when each thing was said dbf3b86 nandi 20d ago1(ns frq.clock
2 "Wall-clock time, in the reader's own zone.
3
4 Messages carry an IRCv3 `time` tag in UTC; a channel is read in local time.
5 jolt's time library works in epoch days and nanoseconds-of-day, and the
6 offset needs a zone name, which no single call hands over — so the zone is
7 found once here and everything else is arithmetic."
8 (:require [clojure.string :as str]
9 [jolt.host :as host]
10 [jolt.time.local :as local]))
11
12(def ^:private zone
13 ;; TZ if it is set, otherwise whatever /etc/localtime points at, otherwise
14 ;; UTC which is wrong by hours but never wrong by a day's worth of parsing.
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago15 ;;
16 ;; /etc/localtime is not always a symlink. In a container it is often the
17 ;; bytes themselves — distrobox's Arch has it as an overlay-mounted regular
18 ;; file and then `readlink -f` answers with the path it was given and there
19 ;; is no zone name to read out of it at all. That fell through to UTC, so a
20 ;; channel read seven hours ahead of the clock on the same screen.
21 ;;
22 ;; The colon form is what libc has for exactly this: `TZ=:/etc/localtime`
23 ;; names the tzfile by path rather than by zone, and tzset reads it with the
24 ;; transitions intact the offset it gives is DST-correct per instant, not a
25 ;; fixed one. Nothing downstream cares that this is a path: the zone is only
26 ;; ever handed back to tz-offset-seconds, never shown.
Say when each thing was said dbf3b86 nandi 20d ago27 (delay
28 (or (let [tz (host/getenv "TZ")] (when (seq tz) tz))
29 (try
30 (second (re-find #"/zoneinfo/(.+)$"
31 (str/trim (str (host/sh-out "readlink -f /etc/localtime")))))
32 (catch Exception _ nil))
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago33 (when (host/file-exists? "/etc/localtime") ":/etc/localtime")
Read the clock in the reader's zone on Android too 0daacbb nandi 18d ago34 ;; Android has neither: no TZ in the environment and no /etc/localtime
35 ;; to point at or read, so both branches above come up empty and the
36 ;; APK read every channel in UTC. The zone is a system property there,
37 ;; and bionic resolves the name against its own tzdata just as glibc
38 ;; resolves it against /usr/share/zoneinfo so the name is all we need.
39 (try
40 (let [tz (str/trim (str (host/sh-out "getprop persist.sys.timezone")))]
41 (when (seq tz) tz))
42 (catch Exception _ nil))
Say when each thing was said dbf3b86 nandi 20d ago43 "UTC")))
44
45(defn now-ms [] (quot (host/wall-nanos) 1000000))
46
47(defn parse-time-tag
48 "The `time=` value of an IRCv3 tag string as epoch milliseconds, or nil.
49
50 Fixed format, always UTC: `2026-08-30T07:05:09.000Z`. Read by hand rather
51 than through a parser, since this runs once per message of a hundred-message
52 backlog."
53 [tags]
54 (when-let [[_ y mo d h mi s] (re-find #"time=(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})"
55 (or tags ""))]
56 (let [[y mo d h mi s] (map parse-long [y mo d h mi s])
57 ;; days from the civil date, by Howard Hinnant's算 — the same one
58 ;; jolt's own local-date uses in reverse.
59 y (if (<= mo 2) (dec y) y)
60 era (quot (if (>= y 0) y (- y 399)) 400)
61 yoe (- y (* era 400))
62 doy (+ (quot (+ (* 153 (+ mo (if (> mo 2) -3 9))) 2) 5) (dec d))
63 doe (+ (* yoe 365) (quot yoe 4) (- (quot yoe 100)) doy)
64 days (+ (* era 146097) doe -719468)]
65 (* 1000 (+ (* days 86400) (* h 3600) (* mi 60) s)))))
66
67(defn- local-parts
68 "`[date hour minute]` in the reader's zone, the hour on a 24-clock."
69 [ms]
70 (let [secs (quot ms 1000)
71 secs (+ secs (host/tz-offset-seconds @zone secs))
72 text (str (local/local-dt (Math/floorDiv secs 86400)
73 (* (Math/floorMod secs 86400) 1000000000)))]
74 [(subs text 0 10) (parse-long (subs text 11 13)) (subs text 14 16)]))
75
76(defn clock-time
77 "A twelve-hour time: `9:05 AM`, `12:30 PM`."
78 [ms]
79 (let [[_ hour minute] (local-parts ms)
80 display (cond (zero? hour) 12
81 (> hour 12) (- hour 12)
82 :else hour)]
83 (str display ":" minute " " (if (< hour 12) "AM" "PM"))))
84
85(defn day [ms] (first (local-parts ms)))
86
87(defn day-label
88 "The heading for a day's messages: today and yesterday by name, anything
89 older by date."
90 [ms]
91 (let [d (day ms)
92 today (day (now-ms))
93 yesterday (day (- (now-ms) 86400000))]
94 (cond (= d today) "Today"
95 (= d yesterday) "Yesterday"
96 :else d)))