hare

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

commit 603b0ee10143a9bd5abfe189f05264ba26d638ee
parent 101287c697c47f91b3da461dfd6658d3337997bc
Author: Byron Torres <b@torresjrjr.com>
Date:   Tue,  1 Feb 2022 22:34:59 +0000

new types {chrono,datetime}::invalid

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

Diffstat:
Mdatetime/date.ha | 22+++++++++++-----------
Mdatetime/datetime.ha | 27++++++++++++++++-----------
Mdatetime/format.ha | 34+++++++++++++++++-----------------
Mdatetime/parse.ha | 18+++++++++---------
Mdatetime/time.ha | 2+-
Mtime/chrono/chronology.ha | 15+++++++++------
Mtime/chrono/timezone.ha | 3++-
7 files changed, 65 insertions(+), 56 deletions(-)

diff --git a/datetime/date.ha b/datetime/date.ha @@ -194,9 +194,9 @@ fn calc_zeroweekday(wd: int) int = { }; // Calculates the [[chrono::epochal]], given a (year, month, day) date -fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = { +fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | invalid) = { if (!is_valid_ymd(y, m, d)) { - return errors::invalid; + return invalid; }; // Algorithm adapted from: // https://en.wikipedia.org/wiki/Julian_day @@ -214,7 +214,7 @@ fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::inva }; // Calculates the [[chrono::epochal]], given a (year, week, weekday) date -fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | errors::invalid) = { +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); const yd = if (w == 0) { @@ -227,16 +227,16 @@ fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | errors::inv }; // Calculates a (year, month, day) date given a (year, yearday) date -fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | errors::invalid) = { +fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | invalid) = { if (!is_valid_yd(y, yd)) { - return errors::invalid; + return invalid; }; let m: int = 1; let monthdays = calc_n_days_in_month(y, m); let d = yd; for (true) { if (m > 12) { - return errors::invalid; + return invalid; }; if (d <= monthdays) { return (y, m, d); @@ -245,11 +245,11 @@ fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | errors::invalid) = { m += 1; monthdays = calc_n_days_in_month(y, m); }; - return errors::invalid; + return invalid; }; // Calculates the [[chrono::epochal]], given a (year, yearday) date -fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = { +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)?; }; @@ -295,7 +295,7 @@ fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = { ); if (should_error) { - assert(actual is errors::invalid, "invalid date accepted"); + assert(actual is invalid, "invalid date accepted"); } else { assert(actual is chrono::epochal, "valid date not accepted"); assert(actual as chrono::epochal == expect, "epochal miscalculation"); @@ -426,9 +426,9 @@ fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = { assert(expected == actual, "error in epochal calculation from yd"); }; - assert(calc_epochal_from_yd(2020, 0) is errors::invalid, + assert(calc_epochal_from_yd(2020, 0) is invalid, "calc_epochal_from_yd() did not reject invalid yearday"); - assert(calc_epochal_from_yd(2020, 400) is errors::invalid, + assert(calc_epochal_from_yd(2020, 400) is invalid, "calc_epochal_from_yd() did not reject invalid yearday"); }; diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -2,6 +2,9 @@ use errors; use time; use time::chrono; +// The given combination of date, time, and locality is invalid. +export type invalid = !chrono::invalid; + export type datetime = struct { chrono::moment, @@ -103,7 +106,7 @@ export fn new( // TODO: improve variety of errors. // `invaliddatetime = !void` ? // `invaliddatetime = !datetime::mock` ? -) (datetime | errors::invalid) = { +) (datetime | invalid) = { let defaults: [_]int = [ 0, 1, 1, // year month day 0, 0, 0, 0, // hour min sec nsec @@ -111,7 +114,7 @@ export fn new( if (len(fields) > len(defaults)) { // cannot specify more than 7 fields - return errors::invalid; + return invalid; }; for (let i = 0z; i < len(fields); i += 1) { @@ -126,14 +129,16 @@ export fn new( const sec = defaults[5]; const nsec = defaults[6]; + let m = chrono::moment { + date = calc_epochal_from_ymd(year, month, day)?, + time = calc_time_from_hmsn(hour, min, sec, nsec)?, + loc = if (loc is void) chrono::LOCAL else loc: *chrono::timezone, + zone = chrono::zone { ... }, + }; + // TODO: Set the correct values according to the given zo and // locality/timezone. - let m = chrono::new( - calc_epochal_from_ymd(year, month, day)?, - calc_time_from_hmsn(hour, min, sec, nsec)?, - if (loc is void) chrono::LOCAL else loc: *chrono::timezone, - ); - + // // figuring out what zone this moment observes if (zo is time::duration) { // Transform inversely to the moment that would transform back @@ -161,7 +166,7 @@ export fn new( ) { void; } else { - return errors::invalid; + return invalid; }; return dt; }; @@ -194,7 +199,7 @@ export fn from_moment(m: chrono::moment) datetime = { // using [[strategy::ALL]], or fails otherwise. // // TODO: allow the user to specify [[strategy]] for security? -export fn from_str(layout: str, s: str) (datetime | insufficient | errors::invalid) = { +export fn from_str(layout: str, s: str) (datetime | insufficient | invalid) = { const b = newmock(); parse(&b, layout, s)?; return finish(&b)?; @@ -231,7 +236,7 @@ export fn newmock() mock = init(): mock; // Returns a datetime from a mock. The provided [[strategy]]s will be tried in // order until a valid datetime is produced, or fail otherwise. The default // strategy is [[strategy::ALL]]. -export fn finish(f: *mock, m: strategy...) (datetime | insufficient | errors::invalid) = { +export fn finish(f: *mock, m: strategy...) (datetime | insufficient | invalid) = { if (len(m) == 0) { m = [strategy::ALL]; }; diff --git a/datetime/format.ha b/datetime/format.ha @@ -57,7 +57,7 @@ def MONTHS_SHORT: [_]str = [ // Formats a [[datetime]] and writes it into a caller supplied buffer. // The returned string is borrowed from this buffer. -export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid | io::error) = { +export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | invalid | io::error) = { let sink = strio::fixed(buf); fmtstream(&sink, layout, dt)?; return strio::string(&sink); @@ -65,13 +65,13 @@ export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid // Formats a [[datetime]] and writes it into a heap-allocated string. // The caller must free the return value. -export fn format(layout: str, dt: *datetime) (str | errors::invalid | io::error) = { +export fn format(layout: str, dt: *datetime) (str | invalid | io::error) = { let sink = strio::dynamic(); fmtstream(&sink, layout, dt)?; return strio::string(&sink); }; -fn fmt_specifier(r: rune, dt: *datetime) (str | errors::invalid | io::error) = { +fn fmt_specifier(r: rune, dt: *datetime) (str | invalid | io::error) = { return switch (r) { case 'a' => yield WEEKDAYS_SHORT[weekday(dt) - 1]; @@ -147,7 +147,7 @@ export fn fmtstream( h: io::handle, layout: str, dt: *datetime -) (size | errors::invalid | io::error) = { +) (size | invalid | io::error) = { const iter = strings::iter(layout); let escaped = false; let n = 0z; @@ -173,10 +173,10 @@ export fn fmtstream( return n; }; -fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int | errors::invalid) = { +fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int | invalid) = { const name = strings::iter_str(iter); if (len(name) == 0) { - return errors::invalid; + return invalid; }; for(let i = 0z; i < len(list); i += 1) { if (strings::hasprefix(name, list[i])) { @@ -187,10 +187,10 @@ fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int | return (i: int) + 1; }; }; - return errors::invalid; + return invalid; }; -fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) = { +fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | invalid) = { let buf: [64]u8 = [0...]; let bufstr = strio::fixed(buf); for (let i = 0z; i < n; i += 1) { @@ -206,7 +206,7 @@ fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) = }; match (strio::appendrune(&bufstr, r)) { case io::error => - return errors::invalid; + return invalid; case => void; }; @@ -215,14 +215,14 @@ fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) = case let res: int => yield res; case => - yield errors::invalid; + yield invalid; }; }; -fn eat_one_rune(iter: *strings::iterator, needle: rune) (uint | errors::invalid) = { +fn eat_one_rune(iter: *strings::iterator, needle: rune) (uint | invalid) = { let s_r = match (strings::next(iter)) { case void => - return errors::invalid; + return invalid; case let r: rune => yield r; }; @@ -319,13 +319,13 @@ fn hour12(dt: *datetime) int = { // dt.nsec as int == 123, "invalid parsing results"); // // General errors -// assert(parse("%Y-%m-%d", "1a94-08-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "1a94-08-27", &dt) is invalid, // "invalid datetime string did not throw error"); -// assert(parse("%Y-%m-%d", "1994-123-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "1994-123-27", &dt) is invalid, // "invalid datetime string did not throw error"); -// assert(parse("%Y-%m-%d", "a994-08-27", &dt) is errors::invalid, +// assert(parse("%Y-%m-%d", "a994-08-27", &dt) is invalid, // "invalid datetime string did not throw error"); // // Basic specifiers @@ -357,10 +357,10 @@ fn hour12(dt: *datetime) int = { // parse("%H %p", "6 PM", &dt)!; // assert(dt.hour as int == 18, "invalid parsing results"); -// assert(parse("%H %p", "13 PM", &dt) is errors::invalid, +// assert(parse("%H %p", "13 PM", &dt) is invalid, // "invalid parsing results"); -// assert(parse("%H %p", "PM 6", &dt) is errors::invalid, +// assert(parse("%H %p", "PM 6", &dt) is invalid, // "invalid parsing results"); // parse("%u", "7", &dt)!; diff --git a/datetime/parse.ha b/datetime/parse.ha @@ -8,7 +8,7 @@ use strings; // datetime::parse(&mok, "%Y-%m-%d", "2038-01-19"); // datetime::parse(&mok, "%H:%M:%S", "03:14:07"); // -export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = { +export fn parse(mok: *mock, layout: str, s: str) (void | invalid) = { const format_iter = strings::iter(layout); const s_iter = strings::iter(s); let escaped = false; @@ -28,12 +28,12 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = { if (!escaped) { let s_r = match (strings::next(&s_iter)) { case void => - return errors::invalid; + return invalid; case let r: rune => yield r; }; if (s_r != format_r) { - return errors::invalid; + return invalid; }; continue; }; @@ -72,7 +72,7 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = { yield clamp_int(hour, 1, 12); }; case => - return errors::invalid; + return invalid; }; case 'j' => mok.yearday = clamp_int( @@ -90,26 +90,26 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = { if (mok.hour is void) { // We can't change the hour's am/pm because we // have no hour. - return errors::invalid; + return invalid; }; const rest = strings::iter_str(&s_iter); if (strings::hasprefix(rest, "AM")) { if (mok.hour as int > 12) { // 13 AM? - return errors::invalid; + return invalid; } else if (mok.hour as int == 12) { mok.hour = 0; }; } else if (strings::hasprefix(rest, "PM")) { if (mok.hour as int > 12) { // 13 PM? - return errors::invalid; + return invalid; } else if (mok.hour as int < 12) { mok.hour = (mok.hour as int) + 12; }; } else { - return errors::invalid; + return invalid; }; strings::next(&s_iter); strings::next(&s_iter); @@ -129,7 +129,7 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = { yield clamp_int(i, 1, 7); }; case => - return errors::invalid; + return invalid; }; case 'U', 'W' => mok.week = clamp_int( diff --git a/datetime/time.ha b/datetime/time.ha @@ -20,7 +20,7 @@ fn calc_time_from_hmsn( min: int, sec: int, nsec: int, -) (time::duration | errors::invalid) = { +) (time::duration | invalid) = { const t = ( (hour * time::HOUR) + (min * time::MINUTE) + diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha @@ -1,5 +1,8 @@ use time; +// This date, time, and locality combination is invalid. +export type invalid = !void; + // A date & time, within a locality, intepreted via a chronology export type moment = struct { // The ordinal day (on Earth or otherwise) @@ -26,11 +29,10 @@ export type moment = struct { export type epochal = i64; // Creates a new [[moment]] -// -// TODO: Check for `time` value bounds overflow. Should we: -// a. Return an error? -// b. "Correct" by overflowing? See datetime::new(); -export fn new(date: epochal, time: time::duration, loc: locality) moment = { +export fn new(date: epochal, time: time::duration, loc: locality) (moment | invalid) = { + if (time > loc.daylength) { + return invalid; + }; const m = moment { date = date, time = time, @@ -46,7 +48,8 @@ export fn from_instant(i: time::instant, loc: locality) moment = { const daysec = (loc.daylength / time::SECOND); const d = i.sec / daysec; const t = (i.sec % daysec) * time::SECOND + i.nsec * time::NANOSECOND; - return new(d, t, loc); + assert(t < loc.daylength, "Internal error: time excedes daylength"); + return new(d, t, loc)!; }; // Creates a new [[time::instant]] from a [[moment]] diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha @@ -64,7 +64,8 @@ type tzname = struct { // Converts a [[moment]] to one in a different [[locality]] export fn in(loc: locality, m: moment) moment = { - return new(m.date, m.time, loc); // resets .zone + assert(m.time < loc.daylength, "Internal error: time excedes daylength"); + return new(m.date, m.time, loc)!; // resets .zone }; export fn transform(m: moment, zo: time::duration) moment = {