std::time
Dates and times as people write them, rather than as a machine counts them.
Clock gives unix_millis and monotonic_millis and that is the whole of
time in std until this module. An instant is enough to measure something
and not enough to say when it happened: reconciliation is bucketed by value
date and settlement date, a report covers a month, and a log line is read by
somebody who wants to know what day it was. None of that is expressible in
milliseconds since 1970.
What is here and what is deliberately not
Section titled “What is here and what is deliberately not”The types are here — a civil date, a time of day, the two together, and
a fixed offset from UTC — because they are vocabulary. Two libraries that
disagree about what a date is cannot exchange one, and
The ecosystem design note reserves exactly that case for std.
The time zone database is not, and cannot be. It is a dataset: Egypt
changes its rules, IANA cuts a release, and every binary already shipped is
wrong about the future. Nothing behind this document’s compatibility promise
can be updated four times a year. So std owns Offset — the answer — and
a package or the host owns the rules that produce one.
That is not a limitation dressed up. A program that stores UTC and renders with an offset it was handed is correct forever; a program that bakes in zone rules is wrong the next time a government moves a clock.
The proleptic Gregorian calendar, everywhere
Section titled “The proleptic Gregorian calendar, everywhere”One calendar, extended backwards past its adoption, which is what ISO 8601 and every database mean by a date. Years before 1 are negative and year zero exists — again ISO’s convention, and the one that makes arithmetic work without a special case nobody remembers.
API at a glance
Section titled “API at a glance”Types: Date · Time · DateTime · Offset
Methods: Date · Time · Offset · DateTime
Trait implementations: Eq for Date · Ord for Date · Eq for Time · Ord for Time · Show for Date · Show for Time · Show for DateTime · Show for Offset
Functions: days_in_month · is_leap
pub type Date = { year: Int, month: Int, day: Int,};A date on the proleptic Gregorian calendar.
month is 1 to 12 and day is 1 to the length of that month. Construct
through Date::of, which checks; the fields are public because a date is
its three numbers and hiding them would only mean writing three accessors.
pub type Time = { hour: Int, minute: Int, second: Int, milli: Int,};A time of day, to the millisecond.
No leap seconds. second is 0 to 59, and a minute is sixty seconds
here as it is in every database and every wire format worth naming. A
calendar that admitted 23:59:60 would push that into every comparison and
every subtraction to be correct about something almost nothing observes.
DateTime
Section titled “DateTime”pub type DateTime = { date: Date, time: Time,};A date and a time, with no zone attached.
Not an instant. “The fourteenth at nine in the morning” is a different kind of thing from “one and a half billion seconds after 1970”: one names a position on a calendar and the other names a moment, and turning either into the other needs an offset. Keeping them apart is what stops a program silently treating local wall time as UTC — the mistake that puts a midnight-boundary report a day out twice a year.
Offset
Section titled “Offset”pub type Offset = { minutes: Int,};Minutes east of UTC.
Offset::utc() is zero, India is +330, and -300 is New York in winter.
A fixed number, not a zone: a zone is a rule that says which offset
applies on a given date, and that rule is a dataset this module refuses to
carry. A caller gets one of these from a package or from the host and hands
it here.
Methods
Section titled “Methods”impl Datepub fn of(year: Int, month: Int, day: Int) -> Option<Date>A date, or None if there is no such day.
The thirty-first of February is refused rather than rolled forward into March. Rolling is a policy — some systems clamp, some wrap, some raise — and a library that picked silently would be picking for a caller who nearly always has bad input rather than an interesting question.
of_string
Section titled “of_string”pub fn of_string(text: String) -> Option<Date>A date written 2026-08-25, or None.
Exactly four digits, two, two, hyphen-separated, and the day has to exist
– 2026-02-30 is None for the same reason Date::of refuses it.
What Show prints for the years 0000 to 9999, read back, and only
those. Date holds an Int year, so Show will print -0044-03-15 or
a five-digit year quite happily; ten bytes of digits and hyphens cannot
say either, and this answers None for both. The round trip holds over
the range a date is normally written in and stops at its edges.
to_days
Section titled “to_days”pub fn to_days(self) -> IntDays since the first of January 1970, which may be negative.
Howard Hinnant’s days_from_civil, which is the algorithm every modern
date library uses: it is exact for the whole range of an Int, it needs
no table, and its shifts of the year and month are what remove the leap
day from the middle of the calculation by moving March to the start of
the year.
of_days
Section titled “of_days”pub fn of_days(days: Int) -> DateThe date days after the first of January 1970.
The inverse of to_days, and the other half of Hinnant’s pair.
add_days
Section titled “add_days”pub fn add_days(self, days: Int) -> DateThe date days later, which may be earlier if days is negative.
days_until
Section titled “days_until”pub fn days_until(self, other: Date) -> IntHow many days from self to other, negative if other is earlier.
weekday
Section titled “weekday”pub fn weekday(self) -> IntMonday is 1 and Sunday is 7, which is ISO 8601’s numbering.
The first of January 1970 was a Thursday, which is where the 3 comes from: it is day zero and Thursday is 4, so the offset that lands Monday on 1 is three.
impl Timepub fn of(hour: Int, minute: Int, second: Int, milli: Int) -> Option<Time>A time of day, or None if there is no such moment.
of_string
Section titled “of_string”pub fn of_string(text: String) -> Option<Time>A time written 09:05:00 or 09:05:00.123, or None.
The fraction is optional and everything else is not, which is ISO
8601’s own rule rather than a leniency: HH:MM:SS is a complete time and
the fraction is an extension of it. Show always prints three digits, so
what it writes reads back; what arrives from somewhere else usually has
no fraction at all.
More than three fractional digits are refused rather than rounded. This type holds milliseconds, and a library that silently dropped the microseconds off a timestamp would be losing data in a way nothing later could notice.
midnight
Section titled “midnight”pub fn midnight() -> TimeMidnight.
to_millis
Section titled “to_millis”pub fn to_millis(self) -> IntMilliseconds since midnight.
of_millis
Section titled “of_millis”pub fn of_millis(millis: Int) -> Option<Time>The time millis after midnight, or None if that is not inside one
day.
The range is checked here, because the arithmetic cannot refuse on its
own. / and % truncate toward zero, so without the check
of_millis(-1) would hold milli: -1 and print 00:00:00.-001 — not
ISO 8601, and not something Time::of_string reads back — while
of_millis(86400000) would print 24:00:00.000. Either would then
travel into a DateTime with nothing there to notice it.
Offset
Section titled “Offset”impl Offsetpub fn utc() -> OffsetNo offset at all.
of_minutes
Section titled “of_minutes”pub fn of_minutes(minutes: Int) -> Option<Offset>minutes east of UTC, or None if that is not less than a day.
Positive is ahead, negative is behind.
The bound is the one Offset::of_string already enforces – two digits
of hours and two of minutes, so -1439 to 1439 – rather than the fourteen
hours governments have actually reached for, because a wire format may
carry a historical offset this module has no list of. What it will not
carry is 100000, which Show would write as +1666:40 and of_string
refuses to read back.
of_string
Section titled “of_string”pub fn of_string(text: String) -> Option<Offset>An offset written +05:30, -05:00 or Z, or None.
Z is accepted although Show prints +00:00. It is not a second
format – it is ISO 8601’s own spelling of this exact value, and it is
what every wire format in the world writes. Refusing it would mean a
parser that cannot read the timestamps it is for.
DateTime
Section titled “DateTime”impl DateTimeof_unix_millis
Section titled “of_unix_millis”pub fn of_unix_millis(millis: Int) -> DateTimeThe moment millis after 1970, as UTC says it.
Flooring, not truncating, which is the whole of what makes dates
before 1970 come out right: -1 millisecond is the last millisecond of
1969, not the first of 1970 with a minus sign. Truncating division puts
it a whole day out, and it is the bug this function exists to not have.
to_unix_millis
Section titled “to_unix_millis”pub fn to_unix_millis(self) -> IntMilliseconds since 1970, reading self as UTC.
of_string
Section titled “of_string”pub fn of_string(text: String) -> Option<DateTime>A timestamp written 2026-08-25T09:05:00 or ...T09:05:00.123, or
None.
No zone, because this type has none. A trailing Z or +05:30 is
refused here rather than ignored – silently dropping it would turn a
moment into a different moment, which is the one mistake a date library
must not make quietly. DateTime::instant_of_string reads the zoned form
and gives back the instant it names.
instant_of_string
Section titled “instant_of_string”pub fn instant_of_string(text: String) -> Option<Int>The instant a zoned timestamp names, as milliseconds since 1970.
2026-08-25T09:05:00Z, 2026-08-25T09:05:00.123+05:30. The zone is
required: this is the function for reading what somebody else wrote, and
a timestamp with no zone does not name an instant. Use of_string for
that, which gives back the wall clock it actually is.
An Int rather than a DateTime because that is what the moment is –
converting it back to a wall clock needs a zone, and the caller has one
in mind or they would not be asking.
of_unix_seconds
Section titled “of_unix_seconds”pub fn of_unix_seconds(seconds: Int) -> DateTimeThe moment seconds after 1970, as UTC says it.
Beside the millisecond pair because a Unix timestamp is usually
seconds: that is what extract(epoch) gives, what a JSON API sends,
and what a log line carries. Multiplying by a thousand at every call site
is the kind of thing that is right until once it is not.
to_unix_seconds
Section titled “to_unix_seconds”pub fn to_unix_seconds(self) -> IntSeconds since 1970, reading self as UTC.
Floored, so a moment before 1970 rounds towards the past and not
towards zero. -1 millisecond is second -1, which is the second that
contains it, and truncating division would answer 0 – a whole second
on the wrong side of the epoch.
at_offset
Section titled “at_offset”pub fn at_offset(millis: Int, offset: Offset) -> DateTimeThe same moment, seen from offset.
The one place a zone enters, and it enters as a number somebody else worked out.
from_offset
Section titled “from_offset”pub fn from_offset(self, offset: Offset) -> IntMilliseconds since 1970, reading self as a wall clock at offset.
Trait implementations
Section titled “Trait implementations”Eq for Date
Section titled “Eq for Date”impl Eq for Datefn eq(self, other: Date) -> BoolOrd for Date
Section titled “Ord for Date”impl Ord for Datefn cmp(self, other: Date) -> OrderingEq for Time
Section titled “Eq for Time”impl Eq for Timefn eq(self, other: Time) -> BoolOrd for Time
Section titled “Ord for Time”impl Ord for Timefn cmp(self, other: Time) -> OrderingShow for Date
Section titled “Show for Date”impl Show for Datefn show(self) -> StringISO 8601: 2026-08-25. Zero-padded, because a date that is sometimes
nine characters and sometimes ten does not sort as text and half the
world sorts dates as text.
Show for Time
Section titled “Show for Time”impl Show for Timefn show(self) -> StringISO 8601: 09:05:00.000. The milliseconds are always there, for the same
reason the zeros are.
Show for DateTime
Section titled “Show for DateTime”impl Show for DateTimefn show(self) -> StringISO 8601 with a T, and no zone — because there is none. A Z here
would be a claim this type does not make.
Show for Offset
Section titled “Show for Offset”impl Show for Offsetfn show(self) -> String+00:00, +05:30, -05:00.
Functions
Section titled “Functions”days_in_month
Section titled “days_in_month”pub fn days_in_month(year: Int, month: Int) -> Option<Int>Days in month of year, or None if month is not 1 to 12.
Only interesting for February, and only an Option because of the range
check. 31 is the worst possible answer for a month that does not exist:
the main use of this function is clamping a day to the end of a month, and
a clamp that answers 31 does not clamp. Date::of refuses a month outside
1 to 12, and so does this, so the two agree about whether a month index is
checked.
is_leap
Section titled “is_leap”pub fn is_leap(year: Int) -> BoolWhether year has a twenty-ninth of February.
Divisible by four, except centuries, except every fourth century. The rule is written out rather than looked up because it is three comparisons and every reader can check it.
Khora 0.2.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource