hare

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

commit 59c4862afd51dc9b4fccdd142f2777247260d455
parent 7398edc63b6204bd60f0d109c43e99bec3cba412
Author: Byron Torres <b@torresjrjr.com>
Date:   Tue,  4 Jan 2022 14:33:54 +0000

update docstrings, comments, READMEs

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

Diffstat:
Mdatetime/README | 22++++++++++++++++++----
Mdatetime/calendar.ha | 7+++----
Mdatetime/datetime.ha | 43+++++++++++++++++++++++++++++--------------
Mdatetime/format.ha | 115++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mtime/chrono/README | 7++++---
Mtime/chrono/chronology.ha | 3++-
Mtime/chrono/timezone.ha | 6++++++
7 files changed, 118 insertions(+), 85 deletions(-)

diff --git a/datetime/README b/datetime/README @@ -1,5 +1,19 @@ -The datetime module implements the international standard proleptic -Gregorian calendar, as per ISO 8601. +The datetime module implements the international de facto "Gregorian" +chronology, based on the astronomically numbered, proleptic Gregorian +calendar, as per ISO 8601. It provides [[datetime]], a representation of +civil date/time and an extension of the [[chrono::moment]] type, +optimized for dealing with the Gregorian chronology. -This module provides datetimes, timezones, offsets, and the arithmetics, -formatting, parsing, serialization, and conversion thereof. +Datetimes are created with [[new]] or [[now]], or be constructed piece +by piece via a [[builder]]. + +For arithmetics, use [[diff]], [[add]] and [[hop]]. Note that +calendrical arithmetic is highly irregular with many edge cases, so +think carefully about what you want. + +[[datetime]] instances are designed to always be valid and internally +consistent. The one exception is during timezone transitions. +Non-existant datetimes should be tested for by attempting to converting +them to their "normal" timezone (usually UTC0). + +Both formatting and parsing use POSIX specifiers (see strptime(3)). diff --git a/datetime/calendar.ha b/datetime/calendar.ha @@ -2,10 +2,9 @@ use errors; use time; use time::chrono; -// TODO: reconcile what epochs to use for time::chrono and datetime:: -// -// 1970-01-01 "Hare epoch" -// 0000-01-01 Gregorian epoch +// 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. // The Hare epoch of the Julian Day Number export def EPOCH_JULIAN: i64 = -2440588; diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -2,8 +2,12 @@ use errors; use time; use time::chrono; -// Represents a datetime; a single, reasonably unique moment in time, specified -// by a calendar date and a wallclock time, contextualised within a locality. +// A valid date & time, localized in a timezone; an extended chrono::moment, +// optimized for use with the Gregorian chronology. +// +// Fields of type (void | int) should be treated as private. Their values are +// produced and cached via their respectively named functions ([[year]], etc). +// Direct field assignements will result in errors; see [[builder]]. export type datetime = struct { chrono::moment, @@ -48,8 +52,8 @@ fn init() datetime = datetime { // Creates a new datetime // -// // 2038 January 19th 03:14:07.000 -// datetime::new(2038, 01, 19, 03, 14, 07, 0, 0, &chrono::local) +// // 2038 January 19th 03:14:07.618 +0000 +// datetime::new(2038, 01, 19, 03, 14, 07, 618 0, &chrono::local) // export fn new( year: int, @@ -129,21 +133,24 @@ export fn validate(dt: datetime) bool = { // A builder has insufficient information and cannot create a valid datetime. export type insufficient = !void; -// Constructs a new datetime, from assigning values to its fields, or multiple -// calls to [[parse]]. A valid datetime is returned via [[build]]. +// Constructs a new datetime. Start with [[newbuilder]]. Collect enough datetime +// information incrementally by direct field assignments or multiple calls to +// [[parse]]. Finish with [[build]]. +// +// let builder = datetime::newbuilder(); +// datetime::parse(&builder, "Year: %Y", "Year: 2038"); +// datetime::parse(&builder, "Month: %m", "Month: 01"); +// builder.day = 19; +// let dt = datetime::build(&builder, datetime::method::YMD); +// export type builder = datetime; // Creates a new [[builder]] export fn newbuilder() builder = init(): builder; -// Returns a valid datetime from a builder. The following approaches will be -// tried in order until a valid datetime is produced, or fail otherwise: -// - year, month, day -// - year, yearday -// - year, week, weekday -// - isoyear, isoweek, weekday -// -// TODO: Should we use an enum to select the approach? +// Returns a datetime from a builder. The provided methods will be tried in +// order until a valid datetime is produced, or fail otherwise. The default +// method is [[method::ALL]]. export fn build(f: *builder, m: method...) (datetime | insufficient | errors::invalid) = { if (len(m) == 0) { m = [method::ALL]; @@ -197,10 +204,18 @@ export fn build(f: *builder, m: method...) (datetime | insufficient | errors::in return insufficient; }; +// Specifies which [[builder]] fields (and what method) to use to calculate the +// epochal, and thus a valid datetime. export type method = enum uint { + // year, month, day YMD = 1 << 0, + // year, yearday YD = 1 << 1, + // year, week, weekday YWD = 1 << 2, + // isoyear, isoweek, weekday ISOYWD = 1 << 4, + + // all methods, in order as presented here ALL = YMD | YD | YWD | ISOYWD, }; diff --git a/datetime/format.ha b/datetime/format.ha @@ -41,17 +41,14 @@ def MONTHS_SHORT: [_]str = [ "Oct", "Nov", "Dec", ]; -// TODO: docstr, reconcile fn names -export fn parse(b: *builder, layout: str, s: str) (void | errors::invalid) = { - strptime(b, layout, s)?; -}; - -// Parses a datetime string into a [[datetime::datetime]]. +// Parses a datetime string into a [[builder]], using a "layout" format string +// with specifiers from POSIX strptime(3). Partial, incremental parsing is +// allowed. // -// The resulting [[datetime::datetime]] may not contain sufficient information -// to be valid. Incremental parsing of data is possible, but the caller should -// validate the [[datetime::datetime]] when appropriate. -export fn strptime(b: *builder, layout: str, s: str) (void | errors::invalid) = { +// datetime::parse(&b, "%Y-%m-%d", "2038-01-19"); +// datetime::parse(&b, "%H:%M:%S", "03:14:07"); +// +export fn parse(b: *builder, layout: str, s: str) (void | errors::invalid) = { const format_iter = strings::iter(layout); const s_iter = strings::iter(s); let escaped = false; @@ -365,22 +362,22 @@ export fn strptime(b: *builder, layout: str, s: str) (void | errors::invalid) = // Formats a [[datetime]] and writes it into a caller supplied buffer. // The returned string is borrowed from this buffer. -export fn bstrftime(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid | io::error) = { +export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid | io::error) = { let sink = strio::fixed(buf); - format(&sink, layout, dt)?; + fmtstream(&sink, layout, dt)?; return strio::string(&sink); }; // Formats a [[datetime]] and writes it into a heap-allocated string. // The caller must free the return value. -export fn strftime(layout: str, dt: *datetime) (str | errors::invalid | io::error) = { +export fn format(layout: str, dt: *datetime) (str | errors::invalid | io::error) = { let sink = strio::dynamic(); - format(&sink, layout, dt)?; + fmtstream(&sink, layout, dt)?; return strio::string(&sink); }; -// Formats a [[datetime]] and writes it into a [[io::handle]]. -export fn format(h: io::handle, layout: str, dt: *datetime) (size | errors::invalid | io::error) = { +// Formats a [[datetime]] and writes to an [[io::handle]]. +export fn fmtstream(h: io::handle, layout: str, dt: *datetime) (size | errors::invalid | io::error) = { const iter = strings::iter(layout); let escaped = false; let n = 0z; @@ -418,17 +415,17 @@ export fn format(h: io::handle, layout: str, dt: *datetime) (size | errors::inva yield MONTHS[month(dt) - 1]; case 'c' => // TODO: Localization - yield strftime("%a %b %e %H:%M:%S %Y", dt)?; + yield format("%a %b %e %H:%M:%S %Y", dt)?; case 'C' => yield strconv::itos(year(dt) / 100); case 'D' => - yield strftime("%m/%d/%y", dt)?; + yield format("%m/%d/%y", dt)?; case 'd' => yield fmt::asprintf("{:02}", day(dt)); case 'e' => yield fmt::asprintf("{:2}", day(dt)); case 'F' => - yield strftime("%Y-%m-%d", dt)?; + yield format("%Y-%m-%d", dt)?; case 'g' => let year_str = strconv::itos(isoweekyear(dt)); yield strings::sub(year_str, @@ -477,15 +474,15 @@ export fn format(h: io::handle, layout: str, dt: *datetime) (size | errors::inva }; case 'r' => // TODO: Localization - yield strftime("%I:%M:%S %p", dt)?; + yield format("%I:%M:%S %p", dt)?; case 'R' => - yield strftime("%H:%M", dt)?; + yield format("%H:%M", dt)?; case 'S' => yield fmt::asprintf("{:02}", sec(dt)); case 't' => yield "\t"; case 'T' => - yield strftime("%H:%M:%S", dt)?; + yield format("%H:%M:%S", dt)?; case 'u' => yield strconv::itos(weekday(dt)); case 'U' => @@ -500,10 +497,10 @@ export fn format(h: io::handle, layout: str, dt: *datetime) (size | errors::inva yield fmt::asprintf("{:02}", week(dt)); case 'x' => // TODO: Localization - yield strftime("%m/%d/%y", dt)?; + yield format("%m/%d/%y", dt)?; case 'X' => // TODO: Localization - yield strftime("%H:%M:%S", dt)?; + yield format("%H:%M:%S", dt)?; case 'y' => let year_str = strconv::itos(year(dt)); yield strings::sub(year_str, @@ -602,12 +599,12 @@ fn clamp_int(i: int, min: int, max: int) int = { }; }; -// TODO: Refactor this once the rest of the strptime() refactoring is done -// @test fn strptime() void = { +// TODO: Refactor this once the rest of the parse() refactoring is done +// @test fn parse() void = { // let dt = datetime {...}; // // General tests -// strptime("%Y-%m-%d %H:%M:%S.%N", "1994-08-27 11:01:02.123", &dt)!; +// parse("%Y-%m-%d %H:%M:%S.%N", "1994-08-27 11:01:02.123", &dt)!; // assert(dt.year as int == 1994 && // dt.month as int == 08 && // dt.day as int == 27 && @@ -616,91 +613,91 @@ fn clamp_int(i: int, min: int, max: int) int = { // dt.sec as int == 02 && // dt.nsec as int == 123, "invalid parsing results"); -// strptime("%k:%M:%S.%N%n%t%%", " 9:01:02.123\n\t%", &dt)!; +// parse("%k:%M:%S.%N%n%t%%", " 9:01:02.123\n\t%", &dt)!; // assert(dt.hour as int == 9 && // dt.min as int == 01 && // dt.sec as int == 02 && // dt.nsec as int == 123, "invalid parsing results"); -// strptime("%G-%m-%e", "994-8- 9", &dt)!; +// parse("%G-%m-%e", "994-8- 9", &dt)!; // assert(dt.isoweekyear as int == 994 && // dt.month as int == 8 && // dt.day as int == 9, "invalid parsing results"); // // General errors -// assert(strptime("%Y-%m-%d", "1a94-08-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "1a94-08-27", &dt) is errors::invalid, // "invalid datetime string did not throw error"); -// assert(strptime("%Y-%m-%d", "1994-123-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "1994-123-27", &dt) is errors::invalid, // "invalid datetime string did not throw error"); -// assert(strptime("%Y-%m-%d", "a994-08-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "a994-08-27", &dt) is errors::invalid, // "invalid datetime string did not throw error"); // // Basic specifiers -// strptime("%a", "Tue", &dt)!; +// parse("%a", "Tue", &dt)!; // assert(dt.weekday as int == 2, "invalid parsing results"); -// strptime("%a %d", "Tue 27", &dt)!; +// parse("%a %d", "Tue 27", &dt)!; // assert(dt.weekday as int == 2 && // dt.day as int == 27, "invalid parsing results"); -// strptime("%A", "Tuesday", &dt)!; +// parse("%A", "Tuesday", &dt)!; // assert(dt.weekday as int == 2, "invalid parsing results"); -// strptime("%b", "Feb", &dt)!; +// parse("%b", "Feb", &dt)!; // assert(dt.month as int == 2, "invalid parsing results"); -// strptime("%h", "Feb", &dt)!; +// parse("%h", "Feb", &dt)!; // assert(dt.month as int == 2, "invalid parsing results"); -// strptime("%B", "February", &dt)!; +// parse("%B", "February", &dt)!; // assert(dt.month as int == 2, "invalid parsing results"); -// strptime("%I", "14", &dt)!; +// parse("%I", "14", &dt)!; // assert(dt.hour as int == 2, "invalid parsing results"); -// strptime("%j", "123", &dt)!; +// parse("%j", "123", &dt)!; // assert(dt.yearday as int == 123, "invalid parsing results"); -// strptime("%l", " 9", &dt)!; +// parse("%l", " 9", &dt)!; // assert(dt.hour as int == 9, "invalid parsing results"); -// strptime("%H %p", "6 AM", &dt)!; +// parse("%H %p", "6 AM", &dt)!; // assert(dt.hour as int == 6, "invalid parsing results"); -// strptime("%H %p", "6 PM", &dt)!; +// parse("%H %p", "6 PM", &dt)!; // assert(dt.hour as int == 18, "invalid parsing results"); -// assert(strptime("%H %p", "13 PM", &dt) is errors::invalid, +// assert(parse("%H %p", "13 PM", &dt) is errors::invalid, // "invalid parsing results"); -// assert(strptime("%H %p", "PM 6", &dt) is errors::invalid, +// assert(parse("%H %p", "PM 6", &dt) is errors::invalid, // "invalid parsing results"); -// strptime("%H %P", "6 am", &dt)!; +// parse("%H %P", "6 am", &dt)!; // assert(dt.hour as int == 6, "invalid parsing results"); -// strptime("%u", "7", &dt)!; +// parse("%u", "7", &dt)!; // assert(dt.weekday as int == 7, "invalid parsing results"); -// strptime("%U", "2", &dt)!; +// parse("%U", "2", &dt)!; // assert(dt.week as int == 2, "invalid parsing results"); -// strptime("%U", "99", &dt)!; +// parse("%U", "99", &dt)!; // assert(dt.week as int == 53, "invalid parsing results"); -// strptime("%V", "12", &dt)!; +// parse("%V", "12", &dt)!; // assert(dt.isoweek as int == 12, "invalid parsing results"); -// strptime("%w", "0", &dt)!; +// parse("%w", "0", &dt)!; // assert(dt.weekday as int == 7, "invalid parsing results"); -// strptime("%W", "2", &dt)!; +// parse("%W", "2", &dt)!; // assert(dt.week as int == 2, "invalid parsing results"); // // Expansion specifiers -// strptime("%c", "Tue Feb 2 22:12:50 1994", &dt)!; +// parse("%c", "Tue Feb 2 22:12:50 1994", &dt)!; // assert(dt.day as int == 2 && // dt.month as int == 2 && // dt.year as int == 1994 && @@ -709,31 +706,31 @@ fn clamp_int(i: int, min: int, max: int) int = { // dt.min as int == 12 && // dt.sec as int == 50, "invalid parsing results"); -// strptime("%D", "08/2/1994", &dt)!; +// parse("%D", "08/2/1994", &dt)!; // assert(dt.day as int == 2 && // dt.month as int == 8 && // dt.year as int == 1994, "invalid parsing results"); -// strptime("%F", "1994-08-27", &dt)!; +// parse("%F", "1994-08-27", &dt)!; // assert(dt.day as int == 27 && // dt.month as int == 08 && // dt.year as int == 1994, "invalid parsing results"); -// strptime("%r", "04:20:12 PM", &dt)!; +// parse("%r", "04:20:12 PM", &dt)!; // assert(dt.hour as int == 16 && // dt.min as int == 20 && // dt.sec as int == 12, "invalid parsing results"); -// strptime("%r", "04:20:12 AM", &dt)!; +// parse("%r", "04:20:12 AM", &dt)!; // assert(dt.hour as int == 04 && // dt.min as int == 20 && // dt.sec as int == 12, "invalid parsing results"); -// strptime("%R", "12:2", &dt)!; +// parse("%R", "12:2", &dt)!; // assert(dt.hour as int == 12 && // dt.min as int == 2, "invalid parsing results"); -// strptime("%T", "12:2:12", &dt)!; +// parse("%T", "12:2:12", &dt)!; // assert(dt.hour as int == 12 && // dt.min as int == 2 && // dt.sec as int == 12, "invalid parsing results"); diff --git a/time/chrono/README b/time/chrono/README @@ -1,5 +1,6 @@ The time::chrono submodule provides the basis for chronology in Hare, -namely timescales, leap seconds, and "moments", an abstracted datetime -for calendars to interface with. +namely [[timescale]]s (leap second handling), [[timezone]]s, and the +[[moment]] type, an abstracted datetime for calendars to interface with. -For working with the ISO calendar, see the [[datetime]] submodule. +For working with the ISO 8601 Gregorian calendar, see the [[datetime]] +submodule. diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha @@ -8,5 +8,6 @@ export type moment = struct { loc: locality, }; -// An ordinal day on earth since the calendar epoch (zeroth day) 1970-01-01 +// An ordinal day (on Earth or otherwise) since the Hare epoch (zeroth day) +// 1970-01-01 export type epochal = i64; diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha @@ -51,6 +51,12 @@ type tzname = struct { dst_endtime: str, }; +@init set_local_timezone() void { + // TODO + // set time::chrono::local to a correct timezone + return; +}; + export const local: timezone = timezone { scale = &UTC, zones = [