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: datetime
title: DateTime
order: 6
---
Instants on the wall-clock timeline (UTC), stored as a plain <code>int</code>: the count of seconds since 1970-01-01, matching <code>Time.now</code>. Build one with <code>DateTime.from</code>, shift it by a <code>Duration</code> with <code>DateTime.add</code>, and pull it apart with the <code>year</code>/<code>month</code>/<code>day</code>/<code>hour</code>/<code>minute</code>/<code>second</code> readers. Deterministic integer math throughout; v1 is UTC-only with no leap seconds, and assumes instants at or after 1970.

View file

@ -0,0 +1,22 @@
---
id: datetime-add
name: DateTime.add
category: datetime
kind: namespace-method
tokens: DateTime.add
sig: DateTime.add(dt, span) -> int
tip: The instant `span` seconds later.
order: 2
ns: DateTime
member: add
---
The instant <code>span</code> seconds after <code>dt</code>. Pair it with the <code>Duration</code> constructors to express the offset in real units.
```ludic
program Demo {
handler Step phase Update {
let ready_at = DateTime.add(Time.now(), Duration.hours(3))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-date
name: DateTime.date
category: datetime
kind: namespace-method
tokens: DateTime.date
sig: DateTime.date(dt) -> int
tip: The calendar day the instant falls on.
order: 1
ns: DateTime
member: date
---
The <code>Date</code> (days since 1970) that the instant <code>dt</code> falls on, dropping the time of day.
```ludic
program Demo {
handler Step phase Update {
let today = DateTime.date(Time.now())
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-day
name: DateTime.day
category: datetime
kind: namespace-method
tokens: DateTime.day
sig: DateTime.day(dt) -> int
tip: The day of the month (1-31) of an instant.
order: 5
ns: DateTime
member: day
---
The day of the month of the instant <code>dt</code> (UTC), <code>1</code>&ndash;<code>31</code>.
```ludic
program Demo {
handler Step phase Update {
let d = DateTime.day(DateTime.from(2000, 3, 9, 0, 0, 0)) # 9
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-from
name: DateTime.from
category: datetime
kind: namespace-method
tokens: DateTime.from
sig: DateTime.from(year, month, day, hour, minute, second) -> int
tip: Build an instant from its calendar parts.
order: 0
ns: DateTime
member: from
---
Builds a <code>DateTime</code> (seconds since 1970, UTC) from calendar and clock parts. The inverse of the component readers below.
```ludic
program Demo {
handler Step phase Update {
let t = DateTime.from(2000, 1, 1, 12, 30, 15)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-hour
name: DateTime.hour
category: datetime
kind: namespace-method
tokens: DateTime.hour
sig: DateTime.hour(dt) -> int
tip: The hour of day (0-23) of an instant.
order: 7
ns: DateTime
member: hour
---
The hour of the day of the instant <code>dt</code> (UTC), <code>0</code>&ndash;<code>23</code>. Drives day/night cycles from a real clock.
```ludic
program Demo {
handler Step phase Update {
let h = DateTime.hour(DateTime.from(2000, 1, 1, 12, 30, 15)) # 12
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-minute
name: DateTime.minute
category: datetime
kind: namespace-method
tokens: DateTime.minute
sig: DateTime.minute(dt) -> int
tip: The minute of the hour (0-59) of an instant.
order: 8
ns: DateTime
member: minute
---
The minute within the hour of the instant <code>dt</code> (UTC), <code>0</code>&ndash;<code>59</code>.
```ludic
program Demo {
handler Step phase Update {
let mi = DateTime.minute(DateTime.from(2000, 1, 1, 12, 30, 15)) # 30
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-month
name: DateTime.month
category: datetime
kind: namespace-method
tokens: DateTime.month
sig: DateTime.month(dt) -> int
tip: The month (1-12) of an instant.
order: 4
ns: DateTime
member: month
---
The calendar month of the instant <code>dt</code> (UTC), <code>1</code>&ndash;<code>12</code>.
```ludic
program Demo {
handler Step phase Update {
let m = DateTime.month(DateTime.from(2000, 3, 9, 0, 0, 0)) # 3
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-second
name: DateTime.second
category: datetime
kind: namespace-method
tokens: DateTime.second
sig: DateTime.second(dt) -> int
tip: The second of the minute (0-59) of an instant.
order: 9
ns: DateTime
member: second
---
The second within the minute of the instant <code>dt</code> (UTC), <code>0</code>&ndash;<code>59</code>.
```ludic
program Demo {
handler Step phase Update {
let s = DateTime.second(DateTime.from(2000, 1, 1, 12, 30, 15)) # 15
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-weekday
name: DateTime.weekday
category: datetime
kind: namespace-method
tokens: DateTime.weekday
sig: DateTime.weekday(dt) -> int
tip: Day of the week, 0=Sunday..6=Saturday.
order: 6
ns: DateTime
member: weekday
---
The day of the week of the instant <code>dt</code> (UTC), <code>0</code> (Sunday) through <code>6</code> (Saturday).
```ludic
program Demo {
handler Step phase Update {
let w = DateTime.weekday(DateTime.from(2000, 1, 1, 12, 0, 0)) # 6
}
}
```

View file

@ -0,0 +1,22 @@
---
id: datetime-year
name: DateTime.year
category: datetime
kind: namespace-method
tokens: DateTime.year
sig: DateTime.year(dt) -> int
tip: The calendar year of an instant.
order: 3
ns: DateTime
member: year
---
The calendar year of the instant <code>dt</code> (UTC).
```ludic
program Demo {
handler Step phase Update {
let y = DateTime.year(DateTime.from(2000, 1, 1, 12, 30, 15)) # 2000
}
}
```