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: date
title: Date
order: 6
---
Calendar days on the proleptic Gregorian calendar (UTC). A <code>Date</code> is stored as a plain <code>int</code>: the count of days since 1970-01-01, so shifting a date by whole days is ordinary integer arithmetic and two dates subtract to a day count. Build one with <code>Date.new</code>, read its parts with <code>year</code>/<code>month</code>/<code>day</code>/<code>weekday</code>. Every operation is deterministic integer math — no leap seconds, no timezone, no floating point.

View file

@ -0,0 +1,22 @@
---
id: date-add_days
name: Date.add_days
category: date
kind: namespace-method
tokens: Date.add_days
sig: Date.add_days(date, n) -> int
tip: The date n days later (n may be negative).
order: 8
ns: Date
member: add_days
---
The <code>Date</code> that is <code>n</code> days after <code>date</code>; pass a negative <code>n</code> to go backward. Rolls across month and year boundaries automatically.
```ludic
program Demo {
handler Step phase Update {
let tomorrow = Date.add_days(Date.new(2026, 12, 31), 1) # 2027-01-01
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-day
name: Date.day
category: date
kind: namespace-method
tokens: Date.day
sig: Date.day(date) -> int
tip: The day of the month (1-31) of a date.
order: 3
ns: Date
member: day
---
The day of the month of a <code>Date</code>, from <code>1</code> to <code>31</code>.
```ludic
program Demo {
handler Step phase Update {
let d = Date.day(Date.new(2026, 8, 30)) # 30
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-days_in_month
name: Date.days_in_month
category: date
kind: namespace-method
tokens: Date.days_in_month
sig: Date.days_in_month(year, month) -> int
tip: Number of days in a given month.
order: 6
ns: Date
member: days_in_month
---
The number of days in <code>month</code> of <code>year</code> (28&ndash;31), accounting for leap Februaries.
```ludic
program Demo {
handler Step phase Update {
let n = Date.days_in_month(2024, 2) # 29
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-diff_days
name: Date.diff_days
category: date
kind: namespace-method
tokens: Date.diff_days
sig: Date.diff_days(a, b) -> int
tip: Whole days from b to a (a - b).
order: 9
ns: Date
member: diff_days
---
The number of whole days from <code>b</code> to <code>a</code> (that is, <code>a - b</code>). Negative when <code>a</code> precedes <code>b</code>.
```ludic
program Demo {
handler Step phase Update {
let gap = Date.diff_days(Date.new(2026, 1, 1), Date.new(2025, 1, 1)) # 365
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-is_leap
name: Date.is_leap
category: date
kind: namespace-method
tokens: Date.is_leap
sig: Date.is_leap(year) -> bool
tip: True if the year is a leap year.
order: 5
ns: Date
member: is_leap
---
Whether <code>year</code> is a leap year on the proleptic Gregorian calendar: divisible by 4, except centuries, which must be divisible by 400.
```ludic
program Demo {
handler Step phase Update {
let leap = Date.is_leap(2024) # true
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-month
name: Date.month
category: date
kind: namespace-method
tokens: Date.month
sig: Date.month(date) -> int
tip: The month (1-12) of a date.
order: 2
ns: Date
member: month
---
The calendar month of a <code>Date</code>, from <code>1</code> (January) to <code>12</code> (December).
```ludic
program Demo {
handler Step phase Update {
let m = Date.month(Date.new(2026, 8, 30)) # 8
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-new
name: Date.new
category: date
kind: namespace-method
tokens: Date.new
sig: Date.new(year, month, day) -> int
tip: A calendar day as days-since-1970.
order: 0
ns: Date
member: new
---
Builds a <code>Date</code> from a calendar <code>year</code>, <code>month</code> (1&ndash;12) and <code>day</code> (1&ndash;31), returning the count of days since 1970-01-01 (UTC). Read the parts back with <code>Date.year</code>/<code>Date.month</code>/<code>Date.day</code>.
```ludic
program Demo {
handler Step phase Update {
let launch = Date.new(2026, 8, 30)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-to_epoch
name: Date.to_epoch
category: date
kind: namespace-method
tokens: Date.to_epoch
sig: Date.to_epoch(date) -> int
tip: Midnight UTC of the day, as a DateTime.
order: 7
ns: Date
member: to_epoch
---
The instant at midnight (00:00:00 UTC) of the given <code>Date</code>, as a <code>DateTime</code> (seconds since 1970). Bridges a calendar day back to the wall-clock timeline.
```ludic
program Demo {
handler Step phase Update {
let t = Date.to_epoch(Date.new(2026, 8, 30))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-weekday
name: Date.weekday
category: date
kind: namespace-method
tokens: Date.weekday
sig: Date.weekday(date) -> int
tip: Day of the week, 0=Sunday..6=Saturday.
order: 4
ns: Date
member: weekday
---
The day of the week of a <code>Date</code>, as <code>0</code> (Sunday) through <code>6</code> (Saturday). Useful for weekly events or daily-reward streaks.
```ludic
program Demo {
handler Step phase Update {
let w = Date.weekday(Date.new(2000, 1, 1)) # 6 (Saturday)
}
}
```

View file

@ -0,0 +1,22 @@
---
id: date-year
name: Date.year
category: date
kind: namespace-method
tokens: Date.year
sig: Date.year(date) -> int
tip: The calendar year of a date.
order: 1
ns: Date
member: year
---
The calendar year of a <code>Date</code>.
```ludic
program Demo {
handler Step phase Update {
let y = Date.year(Date.new(2026, 8, 30)) # 2026
}
}
```