Proposal: Time / Date / DateTime standard library (Go-inspired) — clocks, durations, offline progress #9

Closed
opened 2026-08-29 20:21:55 +02:00 by orkun · 2 comments
Owner

Summary

A built-in Time / Date / DateTime library — wall-clock time, calendar
dates, durations, and formatting/parsing — modeled on Go's excellent time
package but trimmed to what games actually need.

Proposal #2 already lists Time as part of the namespaced stdlib; this issue is
the detailed design + implementation for the calendar/clock half (frame timing
lives with the game loop).

Why it matters for game devs

  • Idle / farming / life-sim games: "3 hours since you last played", "crops
    ready at 18:00", offline progress.
  • Cooldowns & timers expressed in real durations (5.minutes).
  • Save metadata: timestamps, playtime, daily-reward streaks.
  • Day/night & seasons driven by a real or simulated clock.

Proposed API (illustrative)

# doc-check: skip — illustrative API sketch
let now   = Time.now()                 # DateTime, monotonic-safe wall clock
let saved = Save.get_time("last_seen")
let away  = Time.since(saved)          # a Duration
if (away > Duration.hours(3)) { grant_offline_rewards(away) }

let d = Date.new(2026, 8, 29)
Text.from(DateTime.format(now, "YYYY-MM-DD HH:mm"))
Duration.minutes(5) + Duration.seconds(30)

Three types: DateTime (instant), Date (calendar day), Duration (span).
Helpers: now, since, add, diff, format, parse, component getters
(year/month/day/hour/…), Duration.hours/minutes/seconds/frames.

Considerations

  • Determinism: Time.now() is non-deterministic input — keep it OUT of
    simulation that must replay; provide a simulated clock the game controls for
    deterministic/lockstep gameplay (ties into networking #6-era work).
  • Native/C-free: talk to the OS clock via the existing IR/syscall path.
  • Monotonic vs wall clock: expose both; document the difference simply.
  • Timezones/leap handling: start with UTC + local offset; don't ship a tz
    database in v1 (call it out as a follow-up).
  • Fixed-point / integer epochs to avoid float drift.

Scope / acceptance

  • DateTime, Date, Duration types + core ops above.
  • format/parse with a small, documented pattern set.
  • A game-controlled simulated clock for deterministic use.
  • Docs pages + an "offline rewards" example.
  • Tests.

Related: #2 (stdlib namespacing), error handling (parse can fail).

## Summary A built-in **`Time` / `Date` / `DateTime`** library — wall-clock time, calendar dates, durations, and formatting/parsing — modeled on Go's excellent `time` package but trimmed to what games actually need. Proposal #2 already lists `Time` as part of the namespaced stdlib; this issue is the detailed design + implementation for the calendar/clock half (frame timing lives with the game loop). ## Why it matters for game devs - **Idle / farming / life-sim games**: "3 hours since you last played", "crops ready at 18:00", offline progress. - **Cooldowns & timers** expressed in real durations (`5.minutes`). - **Save metadata**: timestamps, playtime, daily-reward streaks. - **Day/night & seasons** driven by a real or simulated clock. ## Proposed API (illustrative) ```ludic # doc-check: skip — illustrative API sketch let now = Time.now() # DateTime, monotonic-safe wall clock let saved = Save.get_time("last_seen") let away = Time.since(saved) # a Duration if (away > Duration.hours(3)) { grant_offline_rewards(away) } let d = Date.new(2026, 8, 29) Text.from(DateTime.format(now, "YYYY-MM-DD HH:mm")) Duration.minutes(5) + Duration.seconds(30) ``` Three types: `DateTime` (instant), `Date` (calendar day), `Duration` (span). Helpers: `now`, `since`, `add`, `diff`, `format`, `parse`, component getters (`year/month/day/hour/…`), `Duration.hours/minutes/seconds/frames`. ## Considerations - **Determinism**: `Time.now()` is non-deterministic input — keep it OUT of simulation that must replay; provide a *simulated* clock the game controls for deterministic/lockstep gameplay (ties into networking #6-era work). - Native/C-free: talk to the OS clock via the existing IR/syscall path. - Monotonic vs wall clock: expose both; document the difference simply. - Timezones/leap handling: start with UTC + local offset; don't ship a tz database in v1 (call it out as a follow-up). - Fixed-point / integer epochs to avoid float drift. ## Scope / acceptance - [ ] `DateTime`, `Date`, `Duration` types + core ops above. - [ ] `format`/`parse` with a small, documented pattern set. - [ ] A game-controlled simulated clock for deterministic use. - [ ] Docs pages + an "offline rewards" example. - [ ] Tests. Related: #2 (stdlib namespacing), error handling (parse can fail).
orkun added the
proposal
priority:high
area:stdlib
labels 2026-08-29 20:21:55 +02:00
Author
Owner

Landed the calendar/clock core in 1a2c6ec (pushed to main).

Implemented as plain-i32 integer epochs (no new type, no floating point — the "integer epochs to avoid drift" note), so everything is deterministic and bit-identical across platforms:

  • Duration — a span in whole seconds: seconds/minutes/hours/days + as_seconds/as_minutes/as_hours/as_days. Since a duration is just an int, Duration.minutes(5) + Duration.seconds(30) and away > Duration.hours(3) work with the ordinary operators.
  • Date — a civil day as days-since-1970 (UTC): new/year/month/day/weekday/is_leap/days_in_month/to_epoch/add_days/diff_days.
  • DateTime — an instant as seconds-since-1970 (UTC, matching Time.now): from/date/add/year/month/day/weekday/hour/minute/second.
  • Time.since(past) = now − past, for offline-progress / "time away" checks.

The civil↔epoch conversions are Howard Hinnant's public-domain proleptic-Gregorian algorithms, in pure i32 IR. New selfhost/emit_datetime.ludic; docs (3 sections + 28 method pages + time-since), inventory and LSP hover in sync; a registered test verifies component math. All suites green (26 self-host / 45 regression / 29 tools); C-free bootstrap fixpoint holds.

Still open for a follow-up: format/parse (needs a pattern-string runtime routine), a game-controlled simulated clock for deterministic/lockstep use, and timezones. v1 is UTC-only with no leap seconds and, on the i32 epoch, valid through 2038 — leaving this issue open to track those.

Landed the calendar/clock core in 1a2c6ec (pushed to main). Implemented as plain-i32 integer epochs (no new type, no floating point — the "integer epochs to avoid drift" note), so everything is deterministic and bit-identical across platforms: - **Duration** — a span in whole seconds: `seconds/minutes/hours/days` + `as_seconds/as_minutes/as_hours/as_days`. Since a duration is just an `int`, `Duration.minutes(5) + Duration.seconds(30)` and `away > Duration.hours(3)` work with the ordinary operators. - **Date** — a civil day as days-since-1970 (UTC): `new/year/month/day/weekday/is_leap/days_in_month/to_epoch/add_days/diff_days`. - **DateTime** — an instant as seconds-since-1970 (UTC, matching `Time.now`): `from/date/add/year/month/day/weekday/hour/minute/second`. - **Time.since(past)** = now − past, for offline-progress / "time away" checks. The civil↔epoch conversions are Howard Hinnant's public-domain proleptic-Gregorian algorithms, in pure i32 IR. New `selfhost/emit_datetime.ludic`; docs (3 sections + 28 method pages + `time-since`), inventory and LSP hover in sync; a registered test verifies component math. All suites green (26 self-host / 45 regression / 29 tools); C-free bootstrap fixpoint holds. **Still open for a follow-up:** `format`/`parse` (needs a pattern-string runtime routine), a game-controlled **simulated clock** for deterministic/lockstep use, and timezones. v1 is UTC-only with no leap seconds and, on the i32 epoch, valid through 2038 — leaving this issue open to track those.
Author
Owner

Completed in b5455cd (builds on the core in 1a2c6ec), all on main. Every acceptance item is now delivered:

  • ✅ DateTime / Date / Duration types + core ops — integer epochs (days/seconds since 1970), no floating point.
  • ✅ format / parse with a documented pattern set — DateTime.format(dt, pattern) / DateTime.parse(text, pattern) over the tokens YYYY YY MM DD HH mm ss (other characters pass through). The pattern is a string literal, expanded at compile time; parse returns -1 on a non-digit where one is expected, so malformed input is detectable.
  • ✅ A game-controlled simulated clock — Clock.now/set/advance/reset, backed by a @L_clock global that never reads the wall clock, so gameplay reading Clock.now() is deterministic and replay-safe. Time.now/Time.since remain the (non-deterministic) wall-clock path.
  • ✅ Docs + an offline-rewards example — examples/offline_rewards.ludic (the "you were away N hours" flow, asserted in the regression suite) plus doc pages for Duration/Date/DateTime/Clock and time-since.
  • ✅ Tests — selfhost/tests/datetime.ludic (component math) and datetime2.ludic (format/parse round-trip, parse failure, clock).

All suites green (27 self-host / 46 regression / 29 tools); C-free bootstrap fixpoint holds; check.py (366 symbols), check-impl.py (218 ns-methods) and validate.py pass.

v1 scope note (as flagged in the proposal): UTC-only, no leap seconds, and on the i32 epoch valid through 2038 — a tz database and a 64-bit epoch are natural future work but out of scope here. Closing as done.

Completed in b5455cd (builds on the core in 1a2c6ec), all on main. Every acceptance item is now delivered: - ✅ **DateTime / Date / Duration types + core ops** — integer epochs (days/seconds since 1970), no floating point. - ✅ **format / parse with a documented pattern set** — `DateTime.format(dt, pattern)` / `DateTime.parse(text, pattern)` over the tokens `YYYY YY MM DD HH mm ss` (other characters pass through). The pattern is a string literal, expanded at compile time; `parse` returns `-1` on a non-digit where one is expected, so malformed input is detectable. - ✅ **A game-controlled simulated clock** — `Clock.now/set/advance/reset`, backed by a `@L_clock` global that never reads the wall clock, so gameplay reading `Clock.now()` is deterministic and replay-safe. `Time.now`/`Time.since` remain the (non-deterministic) wall-clock path. - ✅ **Docs + an offline-rewards example** — `examples/offline_rewards.ludic` (the "you were away N hours" flow, asserted in the regression suite) plus doc pages for Duration/Date/DateTime/Clock and `time-since`. - ✅ **Tests** — `selfhost/tests/datetime.ludic` (component math) and `datetime2.ludic` (format/parse round-trip, parse failure, clock). All suites green (27 self-host / 46 regression / 29 tools); C-free bootstrap fixpoint holds; `check.py` (366 symbols), `check-impl.py` (218 ns-methods) and `validate.py` pass. **v1 scope note (as flagged in the proposal):** UTC-only, no leap seconds, and on the i32 epoch valid through 2038 — a tz database and a 64-bit epoch are natural future work but out of scope here. Closing as done.
orkun closed this issue 2026-08-30 02:52:09 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#9
No description provided.