Generated from /std/date.deed and the module's own tests.
A calendar on top of Io.epoch, which only hands back milliseconds since
1970.
This started as an example, written to find out whether
design/04-capabilities.md was still right that the language had no way to
write a calendar. It was not, and the file proved it. What it could not do
was ship: a program elsewhere that wanted a date had to copy it, the same
way std/list and std/table had to be copied before they moved here.
The conversion is Howard Hinnant's civil_from_days: integer arithmetic
only, no lookup table, and no leap-year rule spelled out by name. The rule
falls out of the same division and modulo every other line here uses, which
is why is_leap_year below is written separately rather than being the
thing date_of consults.
Only forward from year zero. civil_from_days shifts the day count by
719468 days before dividing, so the arithmetic holds for any date after
0000-03-01 and stops holding before it. A clock set that far back is a
broken machine rather than an early one, and date_of says so rather than
answering wrongly.
How many whole days a millisecond count covers.
Floor rather than truncation, which is the one place the difference shows:
/ in this language rounds toward zero, so the millisecond before the epoch
would land on day zero and be read as the first of January 1970 rather than
the last of December 1969.
fn days_since_epoch(milliseconds: Int) -> Int
none
pure
pure
assert days_since_epoch(86400000) == 1
assert days_since_epoch(86399999) == 0
assert days_since_epoch(0 - 1) == 0 - 1
The civil date a millisecond count lands on, or a refusal for a clock set before the arithmetic holds.
A Result rather than a refinement on the parameter: a caller holding a
number out of Io.epoch has no way to have proved anything about it, and
making them prove it would push the check to every call site rather than
removing it.
fn date_of(milliseconds: Int) -> Result<Date, String>
none
pure
pure
match date_of(0) {
The civil date a day count lands on, counting from 1970-01-01.
Total for days from 0000-03-01 on, which is where the shift below runs out.
Split out from date_of because the arithmetic is about days and the
refusal is about milliseconds, and keeping them apart is what lets date_of
be the only place that knows the two are related by a constant.
fn civil_of(days: Int) -> Date
none
pure
pure
assert civil_of(0) == Date { year: 1970, month: 1, day: 1 }
Whether a year has a leap day.
Written out rather than derived from civil_of, because a caller asking
this is asking about a year rather than about a date, and going through the
conversion to answer would mean inventing a date to ask about.
fn is_leap_year(year: Int) -> Bool
none
pure
pure
assert is_leap_year(2024)
assert !is_leap_year(2023)
assert !is_leap_year(1900)
assert is_leap_year(2000)
How many days a month has, in a given year.
A month outside 1 to 12 gives back 0, which is the only answer that does not require refusing and is what a caller walking a year would read as "there is no such month".
fn days_in_month(year: Int, month: Int) -> Int
none
pure
pure
assert days_in_month(2024, 2) == 29
assert days_in_month(2023, 2) == 28
assert days_in_month(2024, 1) == 31
assert days_in_month(2024, 4) == 30
assert days_in_month(2024, 13) == 0
A two-digit rendering, for the parts of a date that have two digits.
fn padded(value: Int) -> String
none
pure
pure
assert padded(9) == "09"
assert padded(10) == "10"
A date as YYYY-MM-DD.
ISO 8601 order, which is the one ordering where sorting the text sorts the
dates. That matters here more than in most languages: < on String is the
only ordering the language gives text, so a date format that did not have
this property would leave a program unable to sort dates without taking them
apart again.
fn text(date: Date) -> String
none
pure
pure
assert text(Date { year: 2024, month: 2, day: 9 }) < text(Date { year: 2024, month: 2, day: 10 })
assert text(Date { year: 2024, month: 9, day: 1 }) < text(Date { year: 2024, month: 10, day: 1 })
assert text(Date { year: 1999, month: 12, day: 31 }) < text(Date { year: 2000, month: 1, day: 1 })
The date a millisecond count lands on, as text, or the reason it refused.
fn text_of(milliseconds: Int) -> String
none
pure
pure
assert text_of(0) == "1970-01-01"
assert text_of(951868800000) == "2000-03-01"
assert text_of(1709164800000) == "2024-02-29"
assert text_of(1709251200000) == "2024-03-01"
assert text_of(0 - 1) == "1969-12-31"
assert text_of(0 - 86400000) == "1969-12-31"
assert text_of(0 - 86400001) == "1969-12-30"
assert text_of(0 - 62167219200000 - 86400000) == "this calendar starts at year zero"