Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
90 linesCodeBlameRaw
1//! When the person asking's day began, in their own time zone (their
2//! profile's IANA name, such as `America/Denver`): what "today" means in a
3//! question to Ask. The runtime's `Intl` says the wall-clock time there;
4//! the rest is arithmetic, tested on its own.
5
6use g1t_contracts::time::{parse_rfc3339, rfc3339};
7
8/// Their day: its date, and when it began as UTC milliseconds.
9#[derive(Debug, PartialEq, Eq)]
10pub struct Day {
11 pub date: String,
12 pub started: u64,
13}
14
15/// The day in `zone` at `now`, or none when the zone is unknown.
16pub fn today(zone: &str, now: u64) -> Option<Day> {
17 from_wallclock(&wallclock(zone, now)?, now)
18}
19
20/// From the wall-clock time in the zone at `now`, written
21/// `MM/DD/YYYY, HH:MM:SS` (en-US, 24-hour), the day it is there and when it
22/// began.
23pub fn from_wallclock(text: &str, now: u64) -> Option<Day> {
24 let (date, time) = text.split_once(", ")?;
25 let mut day = date.split('/');
26 let (month, day_of, year) = (day.next()?, day.next()?, day.next()?);
27 let date = format!("{year:0>4}-{month:0>2}-{day_of:0>2}");
28 let wall = parse_rfc3339(&format!("{date}T{}Z", time.trim()))?;
29 // The zone's offset from UTC, to the minute.
30 let offset = (wall as i64 - now as i64) / 60_000 * 60_000;
31 let midnight = parse_rfc3339(&format!("{date}T00:00:00Z"))? as i64;
32 let started = u64::try_from(midnight - offset).ok()?;
33 Some(Day { date, started })
34}
35
36/// The day's start as RFC 3339, to the second.
37pub fn started_at(day: &Day) -> String {
38 format!("{}Z", rfc3339(day.started).chars().take(19).collect::<String>())
39}
40
41#[cfg(target_arch = "wasm32")]
42fn wallclock(zone: &str, now: u64) -> Option<String> {
43 use worker::js_sys::{Array, Date, Function, Object, Reflect, global};
44 use worker::wasm_bindgen::{JsCast, JsValue};
45 let options = Object::new();
46 for (key, value) in [
47 ("timeZone", zone),
48 ("hourCycle", "h23"),
49 ("year", "numeric"),
50 ("month", "2-digit"),
51 ("day", "2-digit"),
52 ("hour", "2-digit"),
53 ("minute", "2-digit"),
54 ("second", "2-digit"),
55 ] {
56 Reflect::set(&options, &key.into(), &value.into()).ok()?;
57 }
58 // Constructed through Reflect so an unknown zone is an error, not a trap.
59 let intl = Reflect::get(&global(), &"Intl".into()).ok()?;
60 let class: Function = Reflect::get(&intl, &"DateTimeFormat".into()).ok()?.dyn_into().ok()?;
61 let formatter = Reflect::construct(&class, &Array::of2(&"en-US".into(), &options)).ok()?;
62 let format: Function = Reflect::get(&formatter, &"format".into()).ok()?.dyn_into().ok()?;
63 format.call1(&formatter, &Date::new(&JsValue::from_f64(now as f64))).ok()?.as_string()
64}
65
66#[cfg(not(target_arch = "wasm32"))]
67fn wallclock(_zone: &str, _now: u64) -> Option<String> {
68 None
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn a_day_begins_at_local_midnight() {
77 // 05:20 UTC on the 13th is 23:20 on the 12th in Denver (UTC−6).
78 let now = parse_rfc3339("2026-10-13T05:20:00Z").unwrap();
79 let day = from_wallclock("10/12/2026, 23:20:00", now).unwrap();
80 assert_eq!(day.date, "2026-10-12");
81 assert_eq!(started_at(&day), "2026-10-12T06:00:00Z");
82 // East of UTC: 05:20 UTC is 14:20 in Tokyo (UTC+9), whose day began at 15:00 UTC the day before.
83 let day = from_wallclock("10/13/2026, 14:20:00", now).unwrap();
84 assert_eq!((day.date.as_str(), started_at(&day).as_str()), ("2026-10-13", "2026-10-12T15:00:00Z"));
85 // Half-hour zones keep their minutes: India (UTC+5:30).
86 let day = from_wallclock("10/13/2026, 10:50:00", now).unwrap();
87 assert_eq!(started_at(&day), "2026-10-12T18:30:00Z");
88 assert!(from_wallclock("nonsense", now).is_none());
89 }
90}