hare

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

commit 3970e887b6943d0e796b0c1e04e483a90acc4a84
parent d360c181d81ed23430ccc547694d4ab885732e93
Author: Byron Torres <b@torresjrjr.com>
Date:   Sat,  8 Jan 2022 19:11:09 +0000

rename calendar.ha -> chronology.ha, some comments

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

Diffstat:
Mdatetime/arithmetic.ha | 56++++++++++++++++++++++++++++----------------------------
Ddatetime/calendar.ha | 259-------------------------------------------------------------------------------
Adatetime/chronology.ha | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdatetime/date.ha | 22++++++++++++++++------
Mdatetime/datetime.ha | 93+++++++++++++++++++++++++++++++++++--------------------------------------------
Mdatetime/time.ha | 4++++
Mscripts/gen-stdlib | 4++--
Mstdlib.mk | 4++--
8 files changed, 332 insertions(+), 349 deletions(-)

diff --git a/datetime/arithmetic.ha b/datetime/arithmetic.ha @@ -48,14 +48,6 @@ export fn eq(a: *datetime, b: *datetime) bool = { return a.date == b.date && a.time == b.time; }; -@test fn eq() void = { - const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_neq = new(2022, 02, 04, 03, 14, 07, 01, 0, chrono::local)!; - assert(eq(&d0, &d_eq), "equal dates erroneously treated as unequal"); - assert(!eq(&d0, &d_neq), "unequal dates erroneously treated as equal"); -}; - // Returns whether or not the first date is after the second date export fn is_after(a: *datetime, b: *datetime) bool = { // TODO: Factor timezones into this @@ -63,31 +55,11 @@ export fn is_after(a: *datetime, b: *datetime) bool = { (a.date > b.date || a.date == b.date && a.time > b.time); }; -@test fn is_after() void = { - const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_gt = new(2022, 02, 04, 04, 01, 01, 01, 0, chrono::local)!; - const d_lt = new(2020, 02, 04, 33, 14, 07, 01, 0, chrono::local)!; - assert(is_after(&d0, &d_lt), "incorrect date ordering in is_after()"); - assert(!is_after(&d0, &d_eq), "incorrect date ordering in is_after()"); - assert(!is_after(&d0, &d_gt), "incorrect date ordering in is_after()"); -}; - // Returns whether or not the first date is before the second date export fn is_before(a: *datetime, b: *datetime) bool = { return !eq(a, b) && !is_after(a, b); }; -@test fn is_before() void = { - const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; - const d_gt = new(2022, 02, 04, 04, 01, 01, 01, 0, chrono::local)!; - const d_lt = new(2020, 02, 04, 33, 14, 07, 01, 0, chrono::local)!; - assert(!is_before(&d0, &d_lt), "incorrect date ordering in is_before()"); - assert(!is_before(&d0, &d_eq), "incorrect date ordering in is_before()"); - assert(is_before(&d0, &d_gt), "incorrect date ordering in is_before()"); -}; - // Calculates the difference between two datetimes export fn diff(a: datetime, b: datetime) period = { // TODO @@ -155,3 +127,31 @@ export fn add(dt: datetime, flag: int, pp: period...) datetime = { }; return dt; }; + +@test fn eq() void = { + const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_neq = new(2022, 02, 04, 03, 14, 07, 01, 0, chrono::local)!; + assert(eq(&d0, &d_eq), "equal dates erroneously treated as unequal"); + assert(!eq(&d0, &d_neq), "unequal dates erroneously treated as equal"); +}; + +@test fn is_after() void = { + const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_gt = new(2022, 02, 04, 04, 01, 01, 01, 0, chrono::local)!; + const d_lt = new(2020, 02, 04, 33, 14, 07, 01, 0, chrono::local)!; + assert(is_after(&d0, &d_lt), "incorrect date ordering in is_after()"); + assert(!is_after(&d0, &d_eq), "incorrect date ordering in is_after()"); + assert(!is_after(&d0, &d_gt), "incorrect date ordering in is_after()"); +}; + +@test fn is_before() void = { + const d0 = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_eq = new(2022, 02, 04, 03, 14, 07, 00, 0, chrono::local)!; + const d_gt = new(2022, 02, 04, 04, 01, 01, 01, 0, chrono::local)!; + const d_lt = new(2020, 02, 04, 33, 14, 07, 01, 0, chrono::local)!; + assert(!is_before(&d0, &d_lt), "incorrect date ordering in is_before()"); + assert(!is_before(&d0, &d_eq), "incorrect date ordering in is_before()"); + assert(is_before(&d0, &d_gt), "incorrect date ordering in is_before()"); +}; diff --git a/datetime/calendar.ha b/datetime/calendar.ha @@ -1,259 +0,0 @@ -use errors; -use time; -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. - -// The Hare epoch of the Julian Day Number -export def EPOCHAL_JULIAN: i64 = -2440588; - -// The Hare epoch of the Gregorian Common Era -export def EPOCHAL_GREGORIAN: i64 = -719164; - - -// -// date-like -// - -// Evaluates a [[datetime]]'s number of days since the calendar epoch 0000-01-01 -export fn epochal(dt: *datetime) chrono::epochal = { - return dt.date - EPOCHAL_GREGORIAN; -}; - -// Evaluates a [[datetime]]'s era -export fn era(dt: *datetime) int = { - match (dt.era) { - case void => - if (dt.year is void) { - dt.year = year(dt); - }; - dt.era = calc_era(dt.year: int); - return dt.era: int; - case let a: int => - return a; - }; -}; - -// Evaluates a [[datetime]]'s year -export fn year(dt: *datetime) int = { - match (dt.year) { - case void => - const ymd = calc_ymd(dt.date: chrono::epochal); - dt.year = ymd.0; - dt.month = ymd.1; - dt.day = ymd.2; - return dt.year: int; - case let y: int => - return y; - }; -}; - -// Evaluates a [[datetime]]'s month of the year -export fn month(dt: *datetime) int = { - match (dt.month) { - case void => - const ymd = calc_ymd(dt.date: chrono::epochal); - dt.year = ymd.0; - dt.month = ymd.1; - dt.day = ymd.2; - return dt.month: int; - case let y: int => - return y; - }; -}; - -// Evaluates a [[datetime]]'s day of the month -export fn day(dt: *datetime) int = { - match (dt.day) { - case void => - const ymd = calc_ymd(dt.date: chrono::epochal); - dt.year = ymd.0; - dt.month = ymd.1; - dt.day = ymd.2; - return dt.day: int; - case let y: int => - return y; - }; -}; - -// Evaluates a [[datetime]]'s day of the week -export fn weekday(dt: *datetime) int = { - match (dt.weekday) { - case void => - dt.weekday = calc_weekday(dt.date: chrono::epochal); - return dt.weekday: int; - case let y: int => - return y; - }; -}; - -// Evaluates a [[datetime]]'s ordinal day of the year -export fn yearday(dt: *datetime) int = { - match (dt.yearday) { - case void => - if (dt.year is void) { - year(dt); - }; - if (dt.month is void) { - month(dt); - }; - if (dt.day is void) { - day(dt); - }; - dt.yearday = calc_yearday( - dt.year: int, - dt.month: int, - dt.day: int, - ); - return dt.yearday: int; - case let yd: int => - return yd; - }; -}; - -// Evaluates a [[datetime]]'s ISO week-numbering year -export fn isoweekyear(dt: *datetime) int = { - match (dt.isoweekyear) { - case void => - if (dt.year is void) { - year(dt); - }; - if (dt.month is void) { - month(dt); - }; - if (dt.day is void) { - day(dt); - }; - if (dt.weekday is void) { - weekday(dt); - }; - dt.isoweekyear = calc_isoweekyear( - dt.year: int, - dt.month: int, - dt.day: int, - dt.weekday: int, - ); - return dt.isoweekyear: int; - case let iwy: int => - return iwy; - }; -}; - -// Evaluates a [[datetime]]'s Gregorian week -export fn week(dt: *datetime) int = { - match (dt.week) { - case void => - if (dt.yearday is void) { - yearday(dt); - }; - if (dt.weekday is void) { - weekday(dt); - }; - dt.week = calc_week( - dt.yearday: int, - dt.weekday: int, - ); - return dt.week: int; - case let w: int => - return w; - }; -}; - -// Evaluates a [[datetime]]'s ISO week -export fn isoweek(dt: *datetime) int = { - match (dt.isoweek) { - case void => - if (dt.year is void) { - year(dt); - }; - if (dt.week is void) { - week(dt); - }; - if (dt.weekday is void) { - weekday(dt); - }; - if (dt.yearday is void) { - yearday(dt); - }; - dt.isoweek = calc_isoweek( - dt.year: int, - dt.week: int, - dt.weekday: int, - dt.yearday: int, - ); - return dt.isoweek: int; - case let iw: int => - return iw; - }; -}; - - -// -// time-like -// - -// Evaluates a [[datetime]]'s hour of the day -export fn hour(dt: *datetime) int = { - match (dt.hour) { - case void => - const hmsn = calc_hmsn(dt.time: time::duration); - dt.hour = hmsn.0; - dt.min = hmsn.1; - dt.sec = hmsn.2; - dt.nsec = hmsn.3; - return dt.hour: int; - case let h: int => - return h; - }; -}; - -// Evaluates a [[datetime]]'s minute of the hour -export fn min(dt: *datetime) int = { - match (dt.min) { - case void => - const hmsn = calc_hmsn(dt.time: time::duration); - dt.hour = hmsn.0; - dt.min = hmsn.1; - dt.sec = hmsn.2; - dt.nsec = hmsn.3; - return dt.min: int; - case let m: int => - return m; - }; -}; - -// Evaluates a [[datetime]]'s second of the minute -export fn sec(dt: *datetime) int = { - // TODO: localize datetimes for all functions here. Use localised date - // and time in place of the given datetime's date and time. - const ldt = chrono::localize(to_moment(*dt)); - - match (dt.sec) { - case void => - const hmsn = calc_hmsn(dt.time: time::duration); - dt.hour = hmsn.0; - dt.min = hmsn.1; - dt.sec = hmsn.2; - dt.nsec = hmsn.3; - return dt.sec: int; - case let s: int => - return s; - }; -}; - -// Evaluates a [[datetime]]'s nanosecond of the second -export fn nsec(dt: *datetime) int = { - match (dt.nsec) { - case void => - const hmsn = calc_hmsn(dt.time: time::duration); - dt.hour = hmsn.0; - dt.min = hmsn.1; - dt.sec = hmsn.2; - dt.nsec = hmsn.3; - return dt.nsec: int; - case let n: int => - return n; - }; -}; diff --git a/datetime/chronology.ha b/datetime/chronology.ha @@ -0,0 +1,239 @@ +use errors; +use time; +use time::chrono; + +// Returns a [[datetime]]'s number of days since the calendar epoch 0000-01-01 +export fn epochal(dt: *datetime) chrono::epochal = { + return dt.date - EPOCHAL_GREGORIAN; +}; + +// Returns a [[datetime]]'s era +export fn era(dt: *datetime) int = { + match (dt.era) { + case void => + if (dt.year is void) { + dt.year = year(dt); + }; + dt.era = calc_era(dt.year: int); + return dt.era: int; + case let a: int => + return a; + }; +}; + +// Returns a [[datetime]]'s year +export fn year(dt: *datetime) int = { + match (dt.year) { + case void => + const ymd = calc_ymd(dt.date: chrono::epochal); + dt.year = ymd.0; + dt.month = ymd.1; + dt.day = ymd.2; + return dt.year: int; + case let y: int => + return y; + }; +}; + +// Returns a [[datetime]]'s month of the year +export fn month(dt: *datetime) int = { + match (dt.month) { + case void => + const ymd = calc_ymd(dt.date: chrono::epochal); + dt.year = ymd.0; + dt.month = ymd.1; + dt.day = ymd.2; + return dt.month: int; + case let y: int => + return y; + }; +}; + +// Returns a [[datetime]]'s day of the month +export fn day(dt: *datetime) int = { + match (dt.day) { + case void => + const ymd = calc_ymd(dt.date: chrono::epochal); + dt.year = ymd.0; + dt.month = ymd.1; + dt.day = ymd.2; + return dt.day: int; + case let y: int => + return y; + }; +}; + +// Returns a [[datetime]]'s day of the week +export fn weekday(dt: *datetime) int = { + match (dt.weekday) { + case void => + dt.weekday = calc_weekday(dt.date: chrono::epochal); + return dt.weekday: int; + case let y: int => + return y; + }; +}; + +// Returns a [[datetime]]'s ordinal day of the year +export fn yearday(dt: *datetime) int = { + match (dt.yearday) { + case void => + if (dt.year is void) { + year(dt); + }; + if (dt.month is void) { + month(dt); + }; + if (dt.day is void) { + day(dt); + }; + dt.yearday = calc_yearday( + dt.year: int, + dt.month: int, + dt.day: int, + ); + return dt.yearday: int; + case let yd: int => + return yd; + }; +}; + +// Returns a [[datetime]]'s ISO week-numbering year +export fn isoweekyear(dt: *datetime) int = { + match (dt.isoweekyear) { + case void => + if (dt.year is void) { + year(dt); + }; + if (dt.month is void) { + month(dt); + }; + if (dt.day is void) { + day(dt); + }; + if (dt.weekday is void) { + weekday(dt); + }; + dt.isoweekyear = calc_isoweekyear( + dt.year: int, + dt.month: int, + dt.day: int, + dt.weekday: int, + ); + return dt.isoweekyear: int; + case let iwy: int => + return iwy; + }; +}; + +// Returns a [[datetime]]'s Gregorian week +export fn week(dt: *datetime) int = { + match (dt.week) { + case void => + if (dt.yearday is void) { + yearday(dt); + }; + if (dt.weekday is void) { + weekday(dt); + }; + dt.week = calc_week( + dt.yearday: int, + dt.weekday: int, + ); + return dt.week: int; + case let w: int => + return w; + }; +}; + +// Returns a [[datetime]]'s ISO week +export fn isoweek(dt: *datetime) int = { + match (dt.isoweek) { + case void => + if (dt.year is void) { + year(dt); + }; + if (dt.week is void) { + week(dt); + }; + if (dt.weekday is void) { + weekday(dt); + }; + if (dt.yearday is void) { + yearday(dt); + }; + dt.isoweek = calc_isoweek( + dt.year: int, + dt.week: int, + dt.weekday: int, + dt.yearday: int, + ); + return dt.isoweek: int; + case let iw: int => + return iw; + }; +}; + +// Returns a [[datetime]]'s hour of the day +export fn hour(dt: *datetime) int = { + match (dt.hour) { + case void => + const hmsn = calc_hmsn(dt.time: time::duration); + dt.hour = hmsn.0; + dt.min = hmsn.1; + dt.sec = hmsn.2; + dt.nsec = hmsn.3; + return dt.hour: int; + case let h: int => + return h; + }; +}; + +// Returns a [[datetime]]'s minute of the hour +export fn min(dt: *datetime) int = { + match (dt.min) { + case void => + const hmsn = calc_hmsn(dt.time: time::duration); + dt.hour = hmsn.0; + dt.min = hmsn.1; + dt.sec = hmsn.2; + dt.nsec = hmsn.3; + return dt.min: int; + case let m: int => + return m; + }; +}; + +// Returns a [[datetime]]'s second of the minute +export fn sec(dt: *datetime) int = { + // TODO: localize datetimes for all functions here. Use localised date + // and time in place of the given datetime's date and time. + const ldt = chrono::localize(to_moment(*dt)); + + match (dt.sec) { + case void => + const hmsn = calc_hmsn(dt.time: time::duration); + dt.hour = hmsn.0; + dt.min = hmsn.1; + dt.sec = hmsn.2; + dt.nsec = hmsn.3; + return dt.sec: int; + case let s: int => + return s; + }; +}; + +// Returns a [[datetime]]'s nanosecond of the second +export fn nsec(dt: *datetime) int = { + match (dt.nsec) { + case void => + const hmsn = calc_hmsn(dt.time: time::duration); + dt.hour = hmsn.0; + dt.min = hmsn.1; + dt.sec = hmsn.2; + dt.nsec = hmsn.3; + return dt.nsec: int; + case let n: int => + return n; + }; +}; diff --git a/datetime/date.ha b/datetime/date.ha @@ -1,6 +1,22 @@ 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. + +// The Hare epoch of the Julian Day Number +export def EPOCHAL_JULIAN: i64 = -2440588; + +// The Hare epoch of the Gregorian Common Era +export def EPOCHAL_GREGORIAN: i64 = -719164; + +// Calculates whether a given (year, month, date) is valid +export 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); +}; + // Calculates whether a given year is a leap year. export fn is_leap_year(y: int) bool = { return if (y % 4 != 0) false @@ -34,12 +50,6 @@ fn calc_n_days_in_year(y: int) int = { }; }; -// Calculates whether a given (year, month, date) is valid -export 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); -}; - // Calculates the era, given a year fn calc_era(y: int) int = { return if (y >= 0) { diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -27,7 +27,6 @@ export type datetime = struct { nsec: (void | int), }; - fn init() datetime = datetime { date = 0, time = 0, @@ -88,43 +87,6 @@ export fn new( return dt; }; -// Creates a copy of a datetime -export fn clone(dt: datetime) datetime = { - return datetime { - date = dt.date, - time = dt.time, - loc = dt.loc, - - era = dt.era, - year = dt.year, - month = dt.month, - day = dt.day, - isoweekyear = dt.isoweekyear, - isoweek = dt.isoweek, - week = dt.week, - weekday = dt.weekday, - yearday = dt.yearday, - - hour = dt.hour, - min = dt.min, - sec = dt.sec, - nsec = dt.nsec, - }; -}; - -@test fn clone() void = { - let d0 = datetime::new(2038, 01, 19, 03, 14, 07, 0, 0, chrono::local)!; - let d1 = clone(d0); - assert(d0.year as int == d1.year as int && - d0.month as int == d1.month as int && - d0.day as int == d1.day as int && - d0.hour as int == d1.hour as int && - d0.min as int == d1.min as int && - d0.sec as int == d1.sec as int && - d0.nsec as int == d1.nsec as int, - "cloned date not equal to original date"); -}; - // Returns the current datetime export fn now() datetime = { const i = time::now(time::clock::REALTIME); @@ -133,10 +95,6 @@ export fn now() datetime = { const dt = datetime { date = date, time = ((i.sec / 86400) * time::NANOSECOND + i.nsec), - - // TODO: What to do here? How to get the timezone from - // /etc/localtime or $TZ? How to determine the system's - // timescale? Assuming UTC may be sufficient. loc = chrono::local, era = void, @@ -157,12 +115,38 @@ export fn now() datetime = { return dt; }; -// Validates a datetime's internal date & time values -export fn validate(dt: datetime) bool = { - // TODO - return true; +// Creates a copy of a datetime +export fn clone(dt: datetime) datetime = { + return datetime { + date = dt.date, + time = dt.time, + loc = dt.loc, + + era = dt.era, + year = dt.year, + month = dt.month, + day = dt.day, + isoweekyear = dt.isoweekyear, + isoweek = dt.isoweek, + week = dt.week, + weekday = dt.weekday, + yearday = dt.yearday, + + hour = dt.hour, + min = dt.min, + sec = dt.sec, + nsec = dt.nsec, + }; }; +// Converts a [[datetime]] to a [[chrono::moment]] +export fn to_moment(dt: datetime) chrono::moment = { + return chrono::moment { + date = dt.date, + time = dt.time, + loc = dt.loc, + }; +}; // A builder has insufficient information and cannot create a valid datetime. export type insufficient = !void; @@ -254,10 +238,15 @@ export type method = enum uint { ALL = YMD | YD | YWD | ISOYWD, }; -export fn to_moment(dt: datetime) chrono::moment = { - return chrono::moment { - date = dt.date, - time = dt.time, - loc = dt.loc, - }; +@test fn clone() void = { + let d0 = datetime::new(2038, 01, 19, 03, 14, 07, 0, 0, chrono::local)!; + let d1 = clone(d0); + assert(d0.year as int == d1.year as int && + d0.month as int == d1.month as int && + d0.day as int == d1.day as int && + d0.hour as int == d1.hour as int && + d0.min as int == d1.min as int && + d0.sec as int == d1.sec as int && + d0.nsec as int == d1.nsec as int, + "cloned date not equal to original date"); }; diff --git a/datetime/time.ha b/datetime/time.ha @@ -1,6 +1,8 @@ use errors; use time; +// Calculates the wall clock (hour, minute, second, nanosecond), +// given a time since the start of a day fn calc_hmsn(t: time::duration) (int, int, int, int) = { const hour = (t / time::HOUR): int; const min = ((t / time::MINUTE) % 60): int; @@ -9,6 +11,8 @@ fn calc_hmsn(t: time::duration) (int, int, int, int) = { return (hour, min, sec, nsec); }; +// Calculates the time since the start of a day, +// given a wall clock (hour, minute, second, nanosecond) fn calc_time_from_hmsn( hour: int, min: int, diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -186,7 +186,7 @@ datetime() { then gen_srcs datetime \ arithmetic.ha \ - calendar.ha \ + chronology.ha \ date.ha \ datetime.ha \ format.ha \ @@ -195,7 +195,7 @@ datetime() { else gen_srcs datetime \ arithmetic.ha \ - calendar.ha \ + chronology.ha \ date.ha \ datetime.ha \ format.ha \ diff --git a/stdlib.mk b/stdlib.mk @@ -930,7 +930,7 @@ $(HARECACHE)/crypto/curve25519/crypto_curve25519-any.ssa: $(stdlib_crypto_curve2 # datetime (+any) stdlib_datetime_any_srcs= \ $(STDLIB)/datetime/arithmetic.ha \ - $(STDLIB)/datetime/calendar.ha \ + $(STDLIB)/datetime/chronology.ha \ $(STDLIB)/datetime/date.ha \ $(STDLIB)/datetime/datetime.ha \ $(STDLIB)/datetime/format.ha \ @@ -2894,7 +2894,7 @@ $(TESTCACHE)/crypto/curve25519/crypto_curve25519-any.ssa: $(testlib_crypto_curve # datetime (+any) testlib_datetime_any_srcs= \ $(STDLIB)/datetime/arithmetic.ha \ - $(STDLIB)/datetime/calendar.ha \ + $(STDLIB)/datetime/chronology.ha \ $(STDLIB)/datetime/date.ha \ $(STDLIB)/datetime/datetime.ha \ $(STDLIB)/datetime/format.ha \