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:
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.