feat(stdlib): Time/Date/Duration calendar-clock core (issue #9)
Some checks are pending
docs / build-and-deploy (push) Waiting to run

Implement the calendar/clock half of #9 as plain-i32 integer epochs — no
new type, no floating point (the issue's "integer epochs to avoid drift") —
so every operation is deterministic and bit-identical on every platform:

  Duration — a span in whole seconds; seconds/minutes/hours/days build one,
             as_seconds/as_minutes/as_hours/as_days read it back. Because a
             duration is just an int, `+` and `>` work with no extra machinery
             (Duration.minutes(5) + Duration.seconds(30), away > Duration.hours(3)).
  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.

New selfhost/emit_datetime.ludic (is_/emit_ for the three namespaces, wired
into emit_ns_call + the frag list). The two civil<->epoch conversions are
Howard Hinnant's public-domain proleptic-Gregorian algorithms, emitted once
per program as the @fn_days_from_civil / @fn_civil_from_days prelude and gated
by g_uses_datert; days_in_month is next-month-day-0 (no lookup table). Time
gains `since`. Docs (Duration/Date/DateTime sections, 28 method pages +
time-since), inventory, and LSP hover kept in sync; a registered test checks
component math against hand-computed values. Reseeded; C-free fixpoint holds;
all suites green (26 self-host / 45 regression / 29 tools); check.py,
check-impl.py and validate.py OK.

format/parse, a game-controlled simulated clock, and timezones are tracked
follow-ups; v1 is UTC-only and, on the i32 epoch, valid through 2038.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 03:37:38 +03:00
parent 121053e179
commit 1a2c6ec2c7
43 changed files with 10670 additions and 7626 deletions

View file

@ -0,0 +1,7 @@
---
id: duration
title: Duration
order: 6
---
Spans of real time, measured in whole seconds and carried in a plain <code>int</code> — so a duration adds, subtracts and compares with the ordinary operators (<code>Duration.minutes(5) + Duration.seconds(30)</code>, <code>away &gt; Duration.hours(3)</code>). The constructors build a span from a unit; the <code>as_*</code> readers convert a span back to whole units (truncating toward zero). Integer-only, so no floating-point drift.

View file

@ -0,0 +1,22 @@
---
id: duration-as_days
name: Duration.as_days
category: duration
kind: namespace-method
tokens: Duration.as_days
sig: Duration.as_days(span) -> int
tip: The span in whole days.
order: 7
ns: Duration
member: as_days
---
The span expressed in whole days, truncated toward zero (<code>span / 86400</code>).
```ludic
program Demo {
handler Step phase Update {
let d = Duration.as_days(Duration.hours(50)) # 2
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-as_hours
name: Duration.as_hours
category: duration
kind: namespace-method
tokens: Duration.as_hours
sig: Duration.as_hours(span) -> int
tip: The span in whole hours.
order: 6
ns: Duration
member: as_hours
---
The span expressed in whole hours, truncated toward zero (<code>span / 3600</code>). Useful for turning an elapsed-time gap into a reward count.
```ludic
program Demo {
handler Step phase Update {
let h = Duration.as_hours(Duration.days(3)) # 72
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-as_minutes
name: Duration.as_minutes
category: duration
kind: namespace-method
tokens: Duration.as_minutes
sig: Duration.as_minutes(span) -> int
tip: The span in whole minutes.
order: 5
ns: Duration
member: as_minutes
---
The span expressed in whole minutes, truncated toward zero (<code>span / 60</code>).
```ludic
program Demo {
handler Step phase Update {
let m = Duration.as_minutes(Duration.hours(2)) # 120
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-as_seconds
name: Duration.as_seconds
category: duration
kind: namespace-method
tokens: Duration.as_seconds
sig: Duration.as_seconds(span) -> int
tip: The span in whole seconds.
order: 4
ns: Duration
member: as_seconds
---
The span expressed in whole seconds. The inverse of <code>Duration.seconds</code>; also the identity, provided for symmetry with the other readers.
```ludic
program Demo {
handler Step phase Update {
let s = Duration.as_seconds(Duration.minutes(3)) # 180
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-days
name: Duration.days
category: duration
kind: namespace-method
tokens: Duration.days
sig: Duration.days(n) -> int
tip: A span of n days, in seconds.
order: 3
ns: Duration
member: days
---
A span of <code>n</code> days, returned in seconds (<code>n * 86400</code>).
```ludic
program Demo {
handler Step phase Update {
let week = Duration.days(7)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-hours
name: Duration.hours
category: duration
kind: namespace-method
tokens: Duration.hours
sig: Duration.hours(n) -> int
tip: A span of n hours, in seconds.
order: 2
ns: Duration
member: hours
---
A span of <code>n</code> hours, returned in seconds (<code>n * 3600</code>). Handy as a threshold for offline-progress checks.
```ludic
program Demo {
handler Step phase Update {
let full_day = Duration.hours(24)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-minutes
name: Duration.minutes
category: duration
kind: namespace-method
tokens: Duration.minutes
sig: Duration.minutes(n) -> int
tip: A span of n minutes, in seconds.
order: 1
ns: Duration
member: minutes
---
A span of <code>n</code> minutes, returned in seconds (<code>n * 60</code>).
```ludic
program Demo {
handler Step phase Update {
let respawn = Duration.minutes(2)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: duration-seconds
name: Duration.seconds
category: duration
kind: namespace-method
tokens: Duration.seconds
sig: Duration.seconds(n) -> int
tip: A span of n whole seconds.
order: 0
ns: Duration
member: seconds
---
A span of <code>n</code> whole seconds. Since a duration is just an <code>int</code> count of seconds, this is the identity — it exists so the unit is explicit at the call site.
```ludic
program Demo {
handler Step phase Update {
let cooldown = Duration.seconds(45)
}
}
```