hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 926a0d687d34cffe2f142f6399884fcbaba15920
parent 2b0cf28e42e33e775305bd55e52e72ffcecad2c1
Author: Byron Torres <b@torresjrjr.com>
Date:   Mon, 16 May 2022 17:23:36 +0100

time,time::chrono,datetime: improve docs

Signed-off-by: Byron Torres <b@torresjrjr.com>

Diffstat:
Mdatetime/arithmetic.ha | 48++++++++++++++++++++++++++++++------------------
Mdatetime/chronology.ha | 37++++++++++++++++++++-----------------
Mdatetime/date.ha | 59++++++++++++++++++++++++++++++++---------------------------
Mdatetime/datetime.ha | 43++++++++++++++++++++++++++-----------------
Mdatetime/format.ha | 28+++++++++++++++++-----------
Mdatetime/parse.ha | 7++++---
Mdatetime/time.ha | 7+++----
Mdatetime/timezone.ha | 4++--
Mtime/chrono/chronology.ha | 21+++++++++++----------
Mtime/chrono/leapsec.ha | 26++++++++++++++++----------
Mtime/chrono/timescale.ha | 30+++++++++++++++++-------------
Mtime/chrono/timezone.ha | 28+++++++++++++++++-----------
Mtime/chrono/tzdb.ha | 20+++++++++++++-------
Mtime/conv.ha | 5+++--
Mtime/types.ha | 5+++--
15 files changed, 214 insertions(+), 154 deletions(-)

diff --git a/datetime/arithmetic.ha b/datetime/arithmetic.ha @@ -7,8 +7,8 @@ use math; use time; use time::chrono; -// Represents a span of time in the proleptic Gregorian calendar, using relative -// units of time. Used for calendar arithmetic. +// Represents a span of time in the Gregorian chronology, using nominal units of +// time. Used for datetime arithmetic. export type period = struct { eras: int, years: int, @@ -35,7 +35,7 @@ export type calculus = enum int { }; // TODO: ^ Expand this -// The various datetime units that can be used for arithmetic. +// The nominal units of the Gregorian chronology. Used for datetime arithmetic. export type unit = enum int { ERA, YEAR, @@ -48,23 +48,32 @@ export type unit = enum int { NANOSECOND, }; -// Returns true if two dates are numerically equal. +// Returns true if two [[datetime]]s are equivalent. +// +// Equivalence means they represent the same moment in time, +// regardless of their [[locality]] or observed chronological values. export fn eq(a: datetime, b: datetime) bool = { return a.date == b.date && a.time == b.time; }; -// Returns true if the first date is after the second date. +// Returns true if [[datetime]] "a" succeeds [[datetime]] "b". +// +// Temporal order is evaluated in a universal frame of reference, +// regardless of their [[locality]] or observed chronological values. export fn after(a: datetime, b: datetime) bool = { return !eq(a, b) && (a.date > b.date || a.date == b.date && a.time > b.time); }; -// Returns true if the first date is before the second date. +// Returns true if [[datetime]] "a" precedes [[datetime]] "b". +// +// Temporal order is evaluated in a universal frame of reference, +// regardless of their [[locality]] or observed chronological values. export fn before(a: datetime, b: datetime) bool = { return !eq(a, b) && !after(a, b); }; -// Calculates the difference between two datetimes. +// Calculates the [[period]] between two [[datetime]]s. export fn diff(a: datetime, b: datetime) period = { let res = period { ... }; if (eq(a, b)) { @@ -125,8 +134,8 @@ export fn diff(a: datetime, b: datetime) period = { return res; }; -// Calculates the difference between two datetimes using the given unit, -// truncating towards zero. +// Calculates the difference between two [[datetime]]s using the given nominal +// [[unit]], truncating towards zero. export fn unitdiff(a: datetime, b: datetime, u: unit) i64 = { return switch (u) { case unit::ERA => @@ -156,7 +165,7 @@ export fn unitdiff(a: datetime, b: datetime, u: unit) i64 = { }; }; -// Returns true if two periods are numerically equal. +// Returns true if two [[period]]s are numerically equal. export fn period_eq(a: period, b: period) bool = { return a.eras == b.eras && a.years == b.years && @@ -169,9 +178,10 @@ export fn period_eq(a: period, b: period) bool = { a.nanoseconds == b.nanoseconds; }; -// Truncates the given datetime at the provided unit. For example, truncating to -// the nearest [[unit::YEAR]] will set the month, day, hour, minute, seconds, -// and nanoseconds fields to their minimum values. +// Truncates the given [[datetime]] at the provided nominal [[unit]]. +// +// For example, truncating to the nearest [[unit::MONTH]] will set the day, +// hour, minute, seconds, and nanoseconds fields to their minimum values. export fn truncate(dt: datetime, u: unit) datetime = { // TODO: Replace all of the 0s for the zoffset with the actual // zoffset once the API is solidified a bit @@ -223,16 +233,16 @@ export fn truncate(dt: datetime, u: unit) datetime = { }; }; -// Given a datetime and a period, "hops" to the minimum value of each field -// (years, months, days, etc) plus or minus an offset, and returns a new +// Given a [[datetime]] and a [[period]], "hops" to the minimum value of each +// field (years, months, days, etc) plus or minus an offset, and returns a new // datetime. This can be used, for example, to find the start of last year. // // Consults each period's fields from most to least significant (from years to // nanoseconds). // -// If a field's value N is zero, nothing happens. Otherwise, hop will reckon to -// the Nth inter-period point from where last reckoned. This repeats until all -// the given period's fields are exhausted. +// If a period's field's value N is zero, it's a no-op. Otherwise, hop will +// reckon to the Nth inter-period point from where last reckoned. This repeats +// until all the given period's fields are exhausted. // // let dt = ... // 1999-05-13 12:30:45 // datetime::hop(dt, datetime::period { @@ -299,6 +309,7 @@ export fn hop(dt: datetime, pp: period...) datetime = { // months = -1, // 2021-04-13 12:30:45 // days = -4, // 2020-04-09 12:30:45 // }); +// export fn add(dt: datetime, flag: calculus, pp: period...) datetime = { // TODO: Use [[builder]] to simplify some code. let d_year = year(&dt); @@ -406,6 +417,7 @@ export fn add(dt: datetime, flag: calculus, pp: period...) datetime = { // months = -1, // 1977-06-13 12:30:45 // days = -4, // 1977-06-17 12:30:45 // }); +// export fn sub(dt: datetime, flag: calculus, pp: period...) datetime = { for (let i = 0z; i < len(pp); i += 1) { pp[i].eras *= -1; diff --git a/datetime/chronology.ha b/datetime/chronology.ha @@ -4,52 +4,55 @@ use errors; use time; use time::chrono; -// Functions renamed here to avoid namespace conflict, like in the parameters of -// the [[new]] function. +// These functions are renamed to avoid namespace conflicts, like in the +// parameters of the [[new]] function. -// Returns a [[datetime]]'s number of days since the calendar epoch 0000-01-01 +// TODO: For [[epochal]]: Use Hare epoch or Gregorian epoch? Make two function? +// TODO: Create an exported [[zeroweekday]] field function. + +// Returns a [[datetime]]'s number of days since the calendar epoch 0000-01-01. export fn epochal(dt: *datetime) chrono::epochal = _epochal(dt); -// Returns a [[datetime]]'s era +// Returns a [[datetime]]'s era. export fn era(dt: *datetime) int = _era(dt); -// Returns a [[datetime]]'s year +// Returns a [[datetime]]'s year. export fn year(dt: *datetime) int = _year(dt); -// Returns a [[datetime]]'s month of the year +// Returns a [[datetime]]'s month of the year. export fn month(dt: *datetime) int = _month(dt); -// Returns a [[datetime]]'s day of the month +// Returns a [[datetime]]'s day of the month. export fn day(dt: *datetime) int = _day(dt); -// Returns a [[datetime]]'s day of the week +// Returns a [[datetime]]'s day of the week. export fn weekday(dt: *datetime) int = _weekday(dt); -// Returns a [[datetime]]'s ordinal day of the year +// Returns a [[datetime]]'s ordinal day of the year. export fn yearday(dt: *datetime) int = _yearday(dt); -// Returns a [[datetime]]'s ISO week-numbering year +// Returns a [[datetime]]'s ISO week-numbering year. export fn isoweekyear(dt: *datetime) int = _isoweekyear(dt); -// Returns a [[datetime]]'s Gregorian week +// Returns a [[datetime]]'s Gregorian week. export fn week(dt: *datetime) int = _week(dt); -// Returns a [[datetime]]'s Gregorian week starting Sunday +// Returns a [[datetime]]'s Gregorian week starting Sunday. export fn week_starting_sunday(dt: *datetime) int = _week_starting_sunday(dt); -// Returns a [[datetime]]'s ISO week +// Returns a [[datetime]]'s ISO week. export fn isoweek(dt: *datetime) int = _isoweek(dt); -// Returns a [[datetime]]'s hour of the day +// Returns a [[datetime]]'s hour of the day. export fn hour(dt: *datetime) int = _hour(dt); -// Returns a [[datetime]]'s minute of the hour +// Returns a [[datetime]]'s minute of the hour. export fn min(dt: *datetime) int = _min(dt); -// Returns a [[datetime]]'s second of the minute +// Returns a [[datetime]]'s second of the minute. export fn sec(dt: *datetime) int = _sec(dt); -// Returns a [[datetime]]'s nanosecond of the second +// Returns a [[datetime]]'s nanosecond of the second. export fn nsec(dt: *datetime) int = _nsec(dt); diff --git a/datetime/date.ha b/datetime/date.ha @@ -5,16 +5,16 @@ use errors; use time::chrono; // Hare internally uses the Unix epoch (1970-01-01) for calendrical logic. Here -// we provide useful constant for working with the proleptic Gregorian calendar, -// as offsets from the Hare epoch. +// we provide useful constant for working with the astronomically numbered +// proleptic Gregorian calendar, as offsets from the Hare epoch. -// The Hare epoch of the Julian Day Number +// The Hare epoch of the Julian Day Number. export def EPOCHAL_JULIAN: i64 = -2440588; -// The Hare epoch of the Gregorian Common Era +// The Hare epoch of the Gregorian Common Era. export def EPOCHAL_GREGORIAN: i64 = -719164; -// Calculates whether a given year is a leap year. +// Calculates whether a year is a leap year. export fn is_leap_year(y: int) bool = { return if (y % 4 != 0) false else if (y % 100 != 0) true @@ -22,18 +22,18 @@ export fn is_leap_year(y: int) bool = { else true; }; -// Calculates whether a given (year, month, date) is valid +// Calculates whether a given year, month, and day-of-month, is a valid date. fn is_valid_ymd(y: int, m: int, d: int) bool = { return m >= 1 && m <= 12 && d >= 1 && d <= calc_n_days_in_month(y, m); }; -// Returns whether a (year, yearday) date is valid +// Calculates whether a given year, and day-of-year, is a valid date. fn is_valid_yd(y: int, yd: int) bool = { return yd >= 1 && yd <= calc_n_days_in_year(y); }; -// Calculates the number of days in the given month of the given year +// Calculates the number of days in the given month of the given year. fn calc_n_days_in_month(y: int, m: int) int = { const days_per_month: [_]int = [ 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 @@ -49,7 +49,7 @@ fn calc_n_days_in_month(y: int, m: int) int = { }; }; -// Calculates the number of days in a year +// Calculates the number of days in a given year. fn calc_n_days_in_year(y: int) int = { if (is_leap_year(y)) { return 366; @@ -58,7 +58,7 @@ fn calc_n_days_in_year(y: int) int = { }; }; -// Calculates the era, given a year +// Calculates the era, given a year. fn calc_era(y: int) int = { return if (y >= 0) { yield 1; // CE "Common Era" @@ -67,7 +67,7 @@ fn calc_era(y: int) int = { }; }; -// Calculates the (year, month, day), given an epochal day +// Calculates the year, month, and day-of-month, given an epochal day. fn calc_ymd(e: chrono::epochal) (int, int, int) = { // Algorithm adapted from: // https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number @@ -101,7 +101,7 @@ fn calc_ymd(e: chrono::epochal) (int, int, int) = { return (Y: int, M: int, D: int); }; -// Calculates the day of a year, given a (year, month, day) date +// Calculates the day-of-year, given a year, month, and day-of-month. fn calc_yearday(y: int, m: int, d: int) int = { const months_firsts: [_]int = [ 0, 31, 59, @@ -118,7 +118,7 @@ fn calc_yearday(y: int, m: int, d: int) int = { }; // Calculates the ISO week-numbering year, -// given a (year, month, day, weekday) date +// given a year, month, day-of-month, and day-of-week. fn calc_isoweekyear(y: int, m: int, d: int, wd: int) int = { if ( // if the date is within a week whose Thurday @@ -145,7 +145,8 @@ fn calc_isoweekyear(y: int, m: int, d: int, wd: int) int = { }; }; -// Calculates the ISO week, given a (year, week, weekday, yearday) date +// Calculates the ISO week, +// given a year, week, day-of-week, and day-of-year. fn calc_isoweek(y: int, w: int, wd: int, yd: int) int = { const jan1wd = (yd - wd + 7) % 7 + 1; @@ -177,33 +178,34 @@ fn calc_isoweek(y: int, w: int, wd: int, yd: int) int = { }; // Calculates the week within a Gregorian year [0..53], -// given a yearday and Gregorian weekday. -// All days in a new year before the year's first Monday belong to week 0. +// given a day-of-year and day-of-week. +// All days in a year before the year's first Monday belong to week 0. fn calc_week(yd: int, wd: int) int = { return (yd + 7 - wd) / 7; }; // Calculates the week within a Gregorian year [0..53], -// given a yearday and Gregorian weekday. -// All days in a new year before the year's first Sunday belong to week 0. +// given a day-of-year and day-of-week. +// All days in a year before the year's first Sunday belong to week 0. fn calc_week_starting_sunday(yd: int, wd: int) int = { return (yd + 6 - (wd % 7)) / 7; }; -// Calculates the weekday, given a epochal day, -// from Monday=1 to Sunday=7 +// Calculates the day-of-week, given a epochal day, +// from Monday=1 to Sunday=7. fn calc_weekday(e: chrono::epochal) int = { const wd = ((e + 3) % 7 + 1): int; return if (wd > 0) wd else wd + 7; }; -// Calculates the zeroed weekday, given a weekday, -// from Monday=0 to Sunday=6 +// Calculates the zero-indexed day-of-week, given a day-of-week, +// from Monday=0 to Sunday=6. fn calc_zeroweekday(wd: int) int = { return wd - 1; }; -// Calculates the [[chrono::epochal]], given a (year, month, day) date +// Calculates the [[chrono::epochal]], +// given a year, month, and day-of-month. fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | invalid) = { if (!is_valid_ymd(y, m, d)) { return invalid; @@ -211,7 +213,7 @@ fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | invalid) = { // Algorithm adapted from: // https://en.wikipedia.org/wiki/Julian_day // - // Alternate methods of date calculation should be explored. + // TODO: Review, cite, verify, annotate. const jdn = ( (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 @@ -223,7 +225,8 @@ fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | invalid) = { return e; }; -// Calculates the [[chrono::epochal]], given a (year, week, weekday) date +// Calculates the [[chrono::epochal]], +// given a year, week, and day-of-week. fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | invalid) = { const jan1 = calc_epochal_from_ymd(y, 1, 1)?; const jan1_wd = calc_weekday(jan1); @@ -236,7 +239,8 @@ fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | invalid) = return calc_epochal_from_ymd(ymd.0, ymd.1, ymd.2)?; }; -// Calculates a (year, month, day) date given a (year, yearday) date +// Calculates a year, month, and day-of-month, +// given a year and day-of-year. fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | invalid) = { if (!is_valid_yd(y, yd)) { return invalid; @@ -258,7 +262,8 @@ fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | invalid) = { return invalid; }; -// Calculates the [[chrono::epochal]], given a (year, yearday) date +// Calculates the [[chrono::epochal]], +// given a year and day-of-year. fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | invalid) = { const ymd = calc_ymd_from_yd(y, yd)?; return calc_epochal_from_ymd(ymd.0, ymd.1, ymd.2)?; diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -5,7 +5,7 @@ use errors; use time; use time::chrono; -// The given combination of date, time, and locality is invalid. +// Invalid [[datetime]]. export type invalid = !chrono::invalid; export type datetime = struct { @@ -88,7 +88,7 @@ export fn new( offs: (time::duration | void), fields: int... ) (datetime | invalid) = { - // TODO: + // TODO: // - revise examples // - Implement as described. // - fix calls with `years <= -4715`. @@ -157,10 +157,14 @@ export fn new( return dt; }; -// Returns a [[datetime]] for the immediate system time. +// Returns a [[datetime]] of the current system time, +// using [[time::clock::REALTIME]] and [[time::chrono::LOCAL]]. export fn now() datetime = { - // TODO: specify appropriate params like a time::clock and - // chrono::timezone. + // TODO: Consider adding function parameters. + // Should [[now]] specify appropriate params like a time::clock and + // chrono::timezone? Perhaps a separate function, [[from_clock]]. + // + // https://todo.sr.ht/~sircmpwn/hare/645 const i = time::now(time::clock::REALTIME); const m = chrono::from_instant(i, chrono::LOCAL); return from_moment(m); @@ -176,7 +180,8 @@ export fn from_moment(m: chrono::moment) datetime = { return dt; }; -// Creates a [[datetime]] from a [[time::instant]]. +// Creates a [[datetime]] from a [[time::instant]] +// in a [[time::chrono::locality]]. export fn from_instant(i: time::instant, loc: chrono::locality) datetime = { return from_moment(chrono::from_instant(i, loc)); }; @@ -187,7 +192,7 @@ export fn to_instant(dt: datetime) time::instant = { }; // Creates a [[datetime]] from a string, parsed according to a layout, -// using [[strategy::ALL]], or fails otherwise. +// using [[strategy::ALL]], or otherwise fails. export fn from_str(layout: str, s: str) (datetime | insufficient | invalid) = { // XXX: Should we allow the user to specify [[strategy]] for security? const b = newbuilder(); @@ -195,7 +200,7 @@ export fn from_str(layout: str, s: str) (datetime | insufficient | invalid) = { return finish(&b)?; }; -// Creates a [[time::chrono::moment]] from a [[datetime]] +// Creates a [[time::chrono::moment]] from a [[datetime]]. export fn to_moment(dt: datetime) chrono::moment = { return chrono::moment { date = dt.date, @@ -208,23 +213,27 @@ export fn to_moment(dt: datetime) chrono::moment = { // A [[builder]] has insufficient information and cannot create a valid datetime. export type insufficient = !void; -// Constructs a new datetime. Start with [[newbuilder]], then collect enough -// datetime information incrementally by direct field assignments and/or one or -// more calls to [[parse]]. Finish with [[finish]]. +// A pseudo-datetime; a [[datetime]] which may hold invalid values, and does not +// guarantee internal validity or consistency. +// +// This can be used to construct new [[datetime]]s. Start with [[newbuilder]], +// then collect enough datetime information incrementally by direct field +// assignments and/or one or more calls to [[parse]]. Finish with [[finish]]. // // let builder = datetime::newbuilder(); // datetime::parse(&builder, "Year: %Y", "Year: 2038"); // datetime::parse(&builder, "Month: %m", "Month: 01"); // builder.day = 19; // let dt = datetime::finish(&builder, datetime::strategy::YMD); +// export type builder = datetime; -// Creates a new [[builder]] +// Creates a new [[builder]]. export fn newbuilder() builder = init(): builder; -// Returns a datetime from a builder. The provided [[strategy]]s will be tried -// in order until a valid datetime is produced, or fail otherwise. The default -// strategy is [[strategy::ALL]]. +// Returns a [[datetime]] from a [[builder]]. The provided [[strategy]]s will be +// tried in order until a valid datetime is produced, or otherwise fail. The +// default strategy is [[strategy::ALL]]. export fn finish(f: *builder, m: strategy...) (datetime | insufficient | invalid) = { if (len(m) == 0) { m = [strategy::ALL]; @@ -278,8 +287,8 @@ export fn finish(f: *builder, m: strategy...) (datetime | insufficient | invalid return insufficient; }; -// Specifies which [[builder]] fields (and what strategy) to use to calculate the -// epochal, and thus a valid datetime. +// Specifies which [[builder]] fields and what strategy to use to calculate the +// epochal, and thus a valid [[datetime]]. export type strategy = enum uint { // year, month, day YMD = 1 << 0, diff --git a/datetime/format.ha b/datetime/format.ha @@ -11,26 +11,28 @@ use strings; use strio; use time::chrono; -// [[datetime::format]] string for the email date format. +// [[datetime::format]] layout for the email date format. export def EMAIL: str = "%a, %d %b %Y %H:%M:%S %z"; -// [[datetime::format]] string for the email date format, with zone offset and +// [[datetime::format]] layout for the email date format, with zone offset and // zone abbreviation. export def EMAILZ: str = "%a, %d %b %Y %H:%M:%S %z %Z"; -// [[datetime::format]] string used by default for POSIX date(1). +// [[datetime::format]] layout partly compatible with the default layout format +// for the POSIX locale. %d is used in place of POSIX %e. export def POSIX: str = "%a %b %d %H:%M:%S %Z %Y"; +// TODO: Actually implement '%e' and thus the POSIX layout format? -// [[datetime::format]] string compatible with RFC 3339. +// [[datetime::format]] layout compatible with RFC 3339. export def RFC3339: str = "%Y-%m-%dT%H:%M:%S%z"; -// [[datetime::format]] string for a simple timestamp. +// [[datetime::format]] layout for a simple timestamp. export def STAMP: str = "%Y-%m-%d %H:%M:%S"; -// [[datetime::format]] string for a simple timestamp with nanoseconds. +// [[datetime::format]] layout for a simple timestamp with nanoseconds. export def STAMP_NANO: str = "%Y-%m-%d %H:%M:%S.%N"; -// [[datetime::format]] string for a simple timestamp with nanoseconds, +// [[datetime::format]] layout for a simple timestamp with nanoseconds, // zone offset, zone abbreviation, and locality. export def STAMP_NOZL: str = "%Y-%m-%d %H:%M:%S.%N %z %Z %L"; @@ -72,9 +74,11 @@ def MONTHS_SHORT: [_]str = [ // "intervals" standard representation provided by ISO 8601? // // See https://en.wikipedia.org/wiki/ISO_8601#Time_intervals +// +// Ticket: https://todo.sr.ht/~sircmpwn/hare/650 -// Formats a [[datetime]] and writes it into a caller supplied buffer. The -// returned string is borrowed from this buffer. +// Formats a [[datetime]] and writes it into a caller supplied buffer. +// The returned string is borrowed from this buffer. export fn bsformat( buf: []u8, layout: str, @@ -85,8 +89,8 @@ export fn bsformat( return strio::string(&sink); }; -// Formats a [[datetime]] and writes it into a heap-allocated string. The caller -// must free the return value. +// Formats a [[datetime]] and writes it into a heap-allocated string. +// The caller must free the return value. export fn asformat(layout: str, dt: *datetime) (str | invalid | io::error) = { let sink = strio::dynamic(); format(&sink, layout, dt)?; @@ -358,6 +362,8 @@ fn hour12(dt: *datetime) int = { }; // TODO: Refactor this once the rest of the parse() refactoring is done +// Ticket: https://todo.sr.ht/~sircmpwn/hare/648 + // @test fn parse() void = { // let dt = datetime {...}; diff --git a/datetime/parse.ha b/datetime/parse.ha @@ -5,12 +5,13 @@ use errors; use strings; -// Parses a datetime string into a [[builder]], using a "layout" format string -// with a set of specifiers as documented under [[format]]. Partial, incremental -// parsing is possible. +// Parses a date/time string into a [[builder]], according to a layout format +// string with specifiers as documented under [[format]]. Partial, sequential, +// aggregative parsing is possible. // // datetime::parse(&builder, "%Y-%m-%d", "2038-01-19"); // datetime::parse(&builder, "%H:%M:%S", "03:14:07"); +// export fn parse(build: *builder, layout: str, s: str) (void | invalid) = { const format_iter = strings::iter(layout); const s_iter = strings::iter(s); diff --git a/datetime/time.ha b/datetime/time.ha @@ -4,10 +4,9 @@ use errors; use time; // Calculates the wall clock (hour, minute, second, nanosecond), -// given a time since the start of a day -// -// TODO: implement special case for leap seconds where this function is called. +// given a time since the start of a day. fn calc_hmsn(t: time::duration) (int, int, int, int) = { + // TODO: Special case for leap seconds, 61st second? const hour = (t / time::HOUR): int; const min = ((t / time::MINUTE) % 60): int; const sec = ((t / time::SECOND) % 60): int; @@ -16,7 +15,7 @@ fn calc_hmsn(t: time::duration) (int, int, int, int) = { }; // Calculates the time since the start of a day, -// given a wall clock (hour, minute, second, nanosecond) +// given a wall clock (hour, minute, second, nanosecond). fn calc_time_from_hmsn( hour: int, min: int, diff --git a/datetime/timezone.ha b/datetime/timezone.ha @@ -4,7 +4,7 @@ use time; use time::chrono; -// Transforms and creates a new [[datetime]] in a different +// Creates an equivalent [[datetime]] with a different // [[time::chrono::locality]]. export fn in(loc: chrono::locality, dt: datetime) datetime = { const old = to_moment(dt); @@ -13,7 +13,7 @@ export fn in(loc: chrono::locality, dt: datetime) datetime = { return new_dt; }; -// Finds and returns a [[datetime]]'s currently observed zone. +// Finds, sets and returns a [[datetime]]'s currently observed zone. export fn lookupzone(dt: *datetime) chrono::zone = { const m = to_moment(*dt); const z = chrono::lookupzone(&m); diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha @@ -1,9 +1,11 @@ +// License: MPL-2.0 +// (c) 2021-2022 Byron Torres <b@torresjrjr.com> use time; -// This date, time, and locality combination is invalid. +// Invalid [[moment]]. export type invalid = !void; -// A date & time, within a locality, intepreted via a chronology +// A moment in time, within a [[locality]], interpreted via a chronology. export type moment = struct { // The ordinal day (on Earth or otherwise) // since the Hare epoch (zeroth day) 1970-01-01 @@ -20,10 +22,10 @@ export type moment = struct { }; // An ordinal day (on Earth or otherwise) since the Hare epoch (zeroth day) -// 1970-01-01 +// 1970-01-01. export type epochal = i64; -// Creates a new [[moment]] +// Creates a new [[moment]]. export fn new(date: epochal, time: time::duration, loc: locality) (moment | invalid) = { if (time > loc.daylength) { return invalid; @@ -38,7 +40,7 @@ export fn new(date: epochal, time: time::duration, loc: locality) (moment | inva return m; }; -// Creates a new [[moment]] from a [[time::instant]] in a [[locality]] +// Creates a new [[moment]] from a [[time::instant]] in a [[locality]]. export fn from_instant(i: time::instant, loc: locality) moment = { const daysec = (loc.daylength / time::SECOND); const d = i.sec / daysec; @@ -47,7 +49,7 @@ export fn from_instant(i: time::instant, loc: locality) moment = { return new(d, t, loc)!; }; -// Creates a new [[time::instant]] from a [[moment]] +// Creates a new [[time::instant]] from a [[moment]]. export fn to_instant(m: moment) time::instant = { const daysec = (m.loc.daylength / time::SECOND); const i = time::instant { @@ -57,12 +59,11 @@ export fn to_instant(m: moment) time::instant = { return i; }; -// The temporal length of a day on Earth. -// Interpreted with an appropriate timescale like utc, tai, gps. +// The duration of a day on Earth, in terrestrial (SI) seconds. export def EARTH_DAY: time::duration = 86400 * time::SECOND; -// The temporal length of a solar day on Marth, in Martian seconds +// The duration of a solar day on Mars, in Martian seconds. export def MARS_SOL_MARTIAN: time::duration = 86400 * time::SECOND; -// The temporal length of a solar day on Marth, in Earth (SI) seconds +// The duration of a solar day on Mars, in terrestrial (SI) seconds. export def MARS_SOL_TERRESTRIAL: time::duration = (88775.244147 * time::SECOND: f64): time::duration; diff --git a/time/chrono/leapsec.ha b/time/chrono/leapsec.ha @@ -1,3 +1,5 @@ +// License: MPL-2.0 +// (c) 2021-2022 Byron Torres <b@torresjrjr.com> use bufio; use fmt; use fs; @@ -6,31 +8,35 @@ use os; use strconv; use strings; -// Hare uses raw leap second informtion when dealing with the UTC and TAI +// Hare uses raw leap second information when dealing with the UTC and TAI // timescales. This information is source from a standard file installed at // /usr/share/zoneinfo/leap-seconds.list, which itself is fetched from and -// periodically maintained at: +// periodically maintained at various observatories. // -// <ftp://ftp.nist.gov/pub/time/leap-seconds.list> -// <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list> +// https://data.iana.org/time-zones/code/leap-seconds.list +// https://www.ietf.org/timezones/data/leap-seconds.list +// ftp://ftp.nist.gov/pub/time/leap-seconds.list +// ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list // -// This is in contrast to legacy systems which rely on TZif files, which are +// This is in contrast to previous systems which rely on TZif files, which are // installed typically at /usr/share/zoneinfo, as part of the "Olson" IANA -// Timezone databse. These files couple timezone and leap second information -// into one datablock. +// Timezone database. These files couple timezone and leap second data. // // Depending on a system's installation, leap second information may be // deliberately left out of the TZif files, or duplicated throughout. This // design also inhibits our ambitions for dealing with multiple, dynamic // timescales. Therefore, we have decided to take an alternative approach. -// The number of seconds between the years 1900 and 1970. +// The number of seconds between the years 1900 and 1970. This number is +// deliberately hypothetical since timekeeping before atomic clocks was not +// accurate enough to account for small changes in time. export def SECS_1900_1970: i64 = 2208988800; -// The filepath of the leap-seconds.list file +// The filepath of the system's leap-seconds.list file. export def UTC_LEAPSECS_FILE: str = "/usr/share/zoneinfo/leap-seconds.list"; -// UTC timestamps and their TAI offsets, sourced from leap-seconds.list +// UTC timestamps and their offsets from TAI, sourced from the system's +// leap-seconds.list file. let utc_leapsecs: [](i64, i64) = []; @init fn init_utc_leapsecs() void = { diff --git a/time/chrono/timescale.ha b/time/chrono/timescale.ha @@ -1,6 +1,8 @@ +// License: MPL-2.0 +// (c) 2021-2022 Byron Torres <b@torresjrjr.com> use time; -// Represents a scale of time; a time standard +// Represents a scale of time; a time standard. export type timescale = struct { name: str, abbr: str, @@ -8,7 +10,7 @@ export type timescale = struct { from_tai: *ts_converter, }; -// Converts one [[time::instant]] from one [[timescale]] to another +// Converts one [[time::instant]] from one [[timescale]] to another. export type ts_converter = fn(i: time::instant) (time::instant | time::error); // International Atomic Time @@ -28,15 +30,17 @@ fn conv_tai_tai(i: time::instant) (time::instant | time::error) = { // TODO: Write proper conversion functions for all timescales. -// Functions are to return two or no instants, depending on any leap second -// events, and use a proper leap second table. -// https://www.ietf.org/timezones/data/leap-seconds.list +// +// Ticket: https://todo.sr.ht/~sircmpwn/hare/642 +// +// For UTC, conversion functions are to return two or no instants, depending on +// any leap second events, and use a proper leap second table. See leapsec.ha. // Coordinated Universal Time // // Used as the basis of civil timekeeping. -// Based on TAI, with an offset, changed roughly biannually. +// Based on TAI; time-dependent offset. // Discontinuous (has leap seconds). export const utc: timescale = timescale { name = "Coordinated Universal Time", @@ -102,7 +106,7 @@ fn lookup_leaps(list: [](i64, i64), t: i64) size = { // Global Positioning System Time // // Used for GPS coordination. -// Based on TAI, constant -19 second offset. +// Based on TAI; constant -19 second offset. // Continuous (no leap seconds). export const gps: timescale = timescale { name = "Global Positioning System Time", @@ -131,7 +135,7 @@ fn conv_gps_tai(a: time::instant) (time::instant | time::error) = { // Terrestrial Time // // Used for astronomical timekeeping. -// Based on TAI, with a constant offset. +// Based on TAI; constant +32.184 offset. // Continuous (no leap seconds). export const TT: timescale = timescale { name = "Terrestrial Time", @@ -159,16 +163,16 @@ fn conv_tt_tai(tt: time::instant) (time::instant | time::error) = { }; -// Authur David Olson had expressed for Martian time support in this database -// project <https://data.iana.org/time-zones/theory.html>: +// Arthur David Olson had expressed support for Martian time in his timezone +// database project <https://data.iana.org/time-zones/theory.html>: // // > The tz database does not currently support Mars time, but it is documented -// > here in the hopes that support will be added eventually. 8 +// > here in the hopes that support will be added eventually. // Coordinated Mars Time // -// Used for local solar time on Mars. -// Based on TT, with a constant factor. +// Used for timekeeping on Mars. +// Based on TT; constant factor. // Continuous (no leap seconds). export const mtc: timescale = timescale { name = "Coordinated Mars Time", diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha @@ -1,14 +1,17 @@ +// License: MPL-2.0 +// (c) 2021-2022 Byron Torres <b@torresjrjr.com> use bufio; use io; use os; use path; use time; -// The virtual region a moment is interpreted in +// The locality of a [[moment]]. Contains information about how to present a +// moment's chronological values. export type locality = *timezone; -// A timezone; a political region with a ruleset regarding offsets for -// calculating localized civil time +// A timezone; a political or general region with a ruleset regarding offsets +// for calculating localized civil time. export type timezone = struct { // The textual identifier ("Europe/Amsterdam") name: str, @@ -33,7 +36,7 @@ export type timezone = struct { posix_extend: str, }; -// A timezone state, with an offset for calculating localized civil time +// A [[timezone]] state, with an offset for calculating localized civil time. export type zone = struct { // The offset from the normal timezone (2 * time::HOUR) zoffset: time::duration, @@ -48,7 +51,7 @@ export type zone = struct { dst: bool, // true }; -// A timezone transition between two zones +// A [[timezone]] transition between two [[zone]]s. export type transition = struct { when: time::instant, zoneindex: int, @@ -66,7 +69,7 @@ type tzname = struct { dst_endtime: str, }; -// Converts a [[moment]] to one in a different [[locality]] +// Creates an equivalent [[moment]] with a different [[locality]]. export fn in(loc: locality, m: moment) moment = { assert(m.time < loc.daylength, "Internal error: time excedes daylength"); return new(m.date, m.time, loc)!; // resets .zone @@ -86,8 +89,9 @@ export fn transform(m: moment, zo: time::duration) moment = { return m; }; -// Finds, sets and returns a [[moment]]'s currently observed zone +// Finds, sets and returns a [[moment]]'s currently observed zone. export fn lookupzone(m: *moment) zone = { + // TODO: https://todo.sr.ht/~sircmpwn/hare/643 if (len(m.loc.zones) == 0) { // TODO: what to do? not ideal to assume UTC abort("lookup(): timezones should have at least one zone"); @@ -161,7 +165,9 @@ export fn fixedzone(ts: *timescale, daylen: time::duration, z: zone) timezone = }; }; -// The system's local timezone, set during initialisation +// The system's [[locality]]; the system's local [[timezone]]. +// +// This is set during initialisation. export const LOCAL: locality = &TZ_local; @init fn set_local_timezone() void = { @@ -214,7 +220,7 @@ let TZ_local: timezone = timezone { posix_extend = "", }; -// The UTC (Coordinated Universal Time) "Zulu" timezone +// The UTC (Coordinated Universal Time) "Zulu" [[timezone]] as a [[locality]]. export const UTC: locality = &TZ_UTC; const TZ_UTC: timezone = timezone { @@ -233,7 +239,7 @@ const TZ_UTC: timezone = timezone { posix_extend = "", }; -// The TAI (International Atomic Time) "Zulu" timezone +// The TAI (International Atomic Time) "Zulu" [[timezone]] as a [[locality]]. export const TAI: locality = &TZ_TAI; const TZ_TAI: timezone = timezone { @@ -252,7 +258,7 @@ const TZ_TAI: timezone = timezone { posix_extend = "", }; -// The MTC (Coordinated Mars Time) "Zulu" timezone +// The MTC (Coordinated Mars Time) "Zulu" [[timezone]] as a [[locality]]. export const MTC: locality = &TZ_MTC; const TZ_MTC: timezone = timezone { diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha @@ -1,3 +1,5 @@ +// License: MPL-2.0 +// (c) 2021-2022 Byron Torres <b@torresjrjr.com> use bufio; use endian; use errors; @@ -8,16 +10,18 @@ use path; use strings; use time; -// Some TZif data is invalid +// Invalid TZif data. export type invalidtzif = !void; // Possible errors returned from [[tz]]. export type error = !(fs::error | io::error | invalidtzif); +// TODO: Move to an appropriate file, and add other chrono error types. // Converts an [[error]] to a human-friendly representation. export fn strerror(err: error) const str = { match (err) { case invalidtzif => + // TODO: Improve error string. return "Invalid TZif data in time zone"; case let err: fs::error => return fs::strerror(err); @@ -26,10 +30,11 @@ export fn strerror(err: error) const str = { }; }; -// Parses and retrieves a [[timezone]] from the system's zoneinfo database, or -// if applicable, from an internal selection of timezones. All timezones -// provided default to the [[utc]] timescale and [[EARTH_DAY]] daylength. +// Finds and loads a [[timezone]] from the system's Timezone database, normally +// located at /usr/share/zoneinfo. All timezones provided default to the [[utc]] +// [[timescale]] and [[EARTH_DAY]] day-length. export fn tz(name: str) (timezone | fs::error | io::error | invalidtzif) = { +// TODO: Consolidate errors (use chrono::error?). const filepath = path::init(); path::add(&filepath, ZONEINFO_PREFIX, name)!; const fpath = path::string(&filepath); @@ -58,13 +63,13 @@ export fn tz(name: str) (timezone | fs::error | io::error | invalidtzif) = { }; }; -// Parses data in the TZif format, and returns the given timezone with the -// fields "zones", "transitions", and "posix_extend" filled. +// Parses data in the TZif "Time Zone Information Format", and initialises the +// fields "zones", "transitions", and "posix_extend" of the given [[timezone]]. // // See: https://datatracker.ietf.org/doc/html/rfc8536 fn parse_tzif( h: io::handle, - tz: timezone, + tz: timezone, // TODO: Make tz a pointer. ) (timezone | invalidtzif | io::error) = { const buf1: [1]u8 = [0...]; const buf4: [4]u8 = [0...]; @@ -73,6 +78,7 @@ fn parse_tzif( // test for magic "TZif" mustread(h, buf4)?; + // TODO: Use non-aborting bytes comparison. if (strings::fromutf8(buf4) != "TZif") { return invalidtzif; }; diff --git a/time/conv.ha b/time/conv.ha @@ -1,7 +1,8 @@ -// Converts the given [[instant]] to a Unix timestamp. +// Converts a given [[instant]] to a Unix timestamp. +// Note, nanosecond information is lost during conversion. export fn unix(a: instant) i64 = a.sec; -// Returns a [[instant]] from a Unix timestamp. +// Converts a given Unix timestamp to an [[instant]]. export fn from_unix(u: i64) instant = instant { sec = u, nsec = 0, diff --git a/time/types.ha b/time/types.ha @@ -26,13 +26,14 @@ export def HOUR: duration = 60 * MINUTE; // Represents a specific instant in time as seconds (+nanoseconds) since an // arbitrary epoch. Instants may only be meaningfully compared with other -// instants sourced from the same clock. +// instants sourced from the same clock, or handled by the same +// [[time::chrono::timescale]] export type instant = struct { sec: i64, nsec: i64, }; -// Represents a unique interval of time between two instants. +// Represents a unique interval of time between two [[instant]]s. export type interval = (instant, instant); // All error types which are concerned with the handling of [[instant]]s.