hare

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

commit 09087b54346b22ee1524366180ca9bd2c2c7607a
parent 05eb7293847b1be053a932e5d7c47384ac903703
Author: Byron Torres <b@torresjrjr.com>
Date:   Mon, 15 Nov 2021 01:30:45 +0000

add calendrical calc fns with some tests

Some calendrical calculation functions were adapted from a patch sent by
Vlad-Stefan Harbuz <vlad@vladh.net>.

Signed-off-by: Byron Torres <b@torresjrjr.com>
Signed-off-by: Vlad-Stefan Harbuz <vlad@vladh.net>

Diffstat:
Mdatetime/calendar.ha | 36+++++++++++++++++-------------------
Adatetime/date+test.ha | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdatetime/date.ha | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdatetime/datetime.ha | 3++-
Mscripts/gen-stdlib | 28++++++++++++++++++++--------
Mstdlib.mk | 1+
6 files changed, 325 insertions(+), 101 deletions(-)

diff --git a/datetime/calendar.ha b/datetime/calendar.ha @@ -61,44 +61,42 @@ export fn era(m: chrono::moment) int = { // Calculates a moment's year export fn year(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_ymd(m.date, &ld); - return ld.year as int; + return 0; // TODO }; // Calculates a moment's month export fn month(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_ymd(m.date, &ld); - return ld.month as int; + return 0; // TODO }; // Calculates a moment's day of the month export fn day(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_ymd(m.date, &ld); - return ld.day as int; + return 0; // TODO }; // Calculates a moment's ISO week calendar year -export fn weekyear(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_ywd(m.date, &ld); - return ld.weekyear as int; +export fn isoweekyear(m: chrono::moment) int = { + return 0; // TODO }; // Calculates a moment's ISO week +export fn isoweek(m: chrono::moment) int = { + return 0; // TODO +}; + +// Calculates a moment's Gregorian week export fn week(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_ywd(m.date, &ld); - return ld.week as int; + return 0; // TODO +}; + +// Calculates a moment's day of the week +export fn weekday(m: chrono::moment) int = { + return 0; // TODO }; // Calculates a moment's ordinal day of the year export fn yearday(m: chrono::moment) int = { - const ld = localdate { ... }; - calc_yd(m.date, &ld); - return ld.yearday as int; + return 0; // TODO }; // diff --git a/datetime/date+test.ha b/datetime/date+test.ha @@ -0,0 +1,98 @@ +@test fn calc_ymd() void = { + const cases = [ + (-999999, (-0768, 02, 05)), + (-719529, (-0001, 12, 31)), + (-719528, ( 0000, 01, 01)), + (-719527, ( 0000, 01, 02)), + (-719163, ( 0000, 12, 31)), + (-719162, ( 0001, 01, 01)), + (-719161, ( 0001, 01, 02)), + ( -1745, ( 1965, 03, 23)), + ( -1, ( 1969, 12, 31)), + ( 0, ( 1970, 01, 01)), + ( 1, ( 1970, 01, 02)), + ( 10956, ( 1999, 12, 31)), + ( 10957, ( 2000, 01, 01)), + ( 10958, ( 2000, 01, 02)), + ( 24854, ( 2038, 01, 18)), + ( 24855, ( 2038, 01, 19)), + ( 24856, ( 2038, 01, 20)), + ( 100000, ( 2243, 10, 17)), + ( 999999, ( 4707, 11, 28)), + (1000000, ( 4707, 11, 29)), + (9999999, (29349, 01, 25)), + ]; + for (let i = 0z; i < len(cases); i += 1) { + const paramt = cases[i].0; + const expect = cases[i].1; + const actual = calc_ymd(paramt); + assert(expect.0 == actual.0, "year mismatch"); + assert(expect.1 == actual.1, "month mismatch"); + assert(expect.2 == actual.2, "day mismatch"); + }; +}; + +@test fn calc_yearday() void = { + const cases = [ + ((-0768, 02, 05), 037), + ((-0001, 12, 31), 365), + (( 0000, 01, 01), 001), + (( 0000, 01, 02), 002), + (( 0000, 12, 31), 366), + (( 0001, 01, 01), 001), + (( 0001, 01, 02), 002), + (( 1965, 03, 23), 082), + (( 1969, 12, 31), 365), + (( 1970, 01, 01), 001), + (( 1970, 01, 02), 002), + (( 1999, 12, 31), 365), + (( 2000, 01, 01), 001), + (( 2000, 01, 02), 002), + (( 2038, 01, 18), 018), + (( 2038, 01, 19), 019), + (( 2038, 01, 20), 020), + (( 2243, 10, 17), 290), + (( 4707, 11, 28), 332), + (( 4707, 11, 29), 333), + ((29349, 01, 25), 025), + ]; + for (let i = 0z; i < len(cases); i += 1) { + const params = cases[i].0; + const expect = cases[i].1; + const actual = calc_yearday(params.0, params.1, params.2); + assert(expect == actual, "yearday miscalculation"); + }; +}; + +@test fn calc_weekday() void = { + const cases = [ + (-999999, 4), // -0768-02-05 + (-719529, 5), // -0001-12-31 + (-719528, 6), // 0000-01-01 + (-719527, 7), // 0000-01-02 + (-719163, 7), // 0000-12-31 + (-719162, 1), // 0001-01-01 + (-719161, 2), // 0001-01-02 + ( -1745, 2), // 1965-03-23 + ( -1, 3), // 1969-12-31 + ( 0, 4), // 1970-01-01 + ( 1, 5), // 1970-01-02 + ( 10956, 5), // 1999-12-31 + ( 10957, 6), // 2000-01-01 + ( 10958, 7), // 2000-01-02 + ( 24854, 1), // 2038-01-18 + ( 24855, 2), // 2038-01-19 + ( 24856, 3), // 2038-01-20 + ( 100000, 2), // 2243-10-17 + ( 999999, 4), // 4707-11-28 + (1000000, 5), // 4707-11-29 + (9999999, 6), // 29349-01-25 + ]; + for (let i = 0z; i < len(cases); i += 1) { + const paramt = cases[i].0; + const expect = cases[i].1; + const actual = calc_weekday(paramt); + assert(expect == actual, "weekday miscalculation"); + }; +}; + diff --git a/datetime/date.ha b/datetime/date.ha @@ -4,78 +4,188 @@ use errors; // Represents an ISO calendar date. // Instances created from datetime:: functions are guaranteed to be valid. export type localdate = struct { - era: (void | int), - year: (void | int), - month: (void | int), - day: (void | int), - weekyear: (void | int), - week: (void | int), - weekday: (void | int), - yearday: (void | int), + era: (void | int), + year: (void | int), + month: (void | int), + day: (void | int), + yearday: (void | int), + isoweekyear: (void | int), + isoweek: (void | int), + week: (void | int), + weekday: (void | int), }; export fn init_date() localdate = localdate { - era = void, - year = void, - month = void, - day = void, - weekyear = void, - week = void, - weekday = void, - yearday = void, -}; - -// TODO: The following function can be split up for efficiency. -// calc_y, calc_m, calc_d, etc. - -// Populates the year, month, and day fields of a given [[localdate]], -// calculated from a [[chrono::epochal]] -export fn calc_ymd(epochal: chrono::epochal, ld: *localdate) void = { + era = void, + year = void, + month = void, + day = void, + yearday = void, + isoweekyear = void, + isoweek = void, + week = void, + weekday = void, +}; + +export type WEEKDAYS = enum int { + MON = 1, + TUE = 2, + WED = 3, + THU = 4, + FRI = 5, + SAT = 6, + SUN = 7, +}; + + +// Calculates an era, given a year +fn calc_era(y: int) int = { + return if (y >= 0) { + yield 1; // CE "Common Era" + } else { + yield 0; // BCE "Before Common Era" + }; +}; + + +// Calculates the (year, month, day) of a [[chrono::epochal]] +// +// TODO: Split up this function? Probably not. Most useful as is. +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 // // Alternate methods of date calculation should be explored. - const J = epochal - EPOCH_JULIAN; + const J = e - EPOCH_JULIAN; + + // TODO: substitute numbers where possible + const b = 274277; + const c = -38; const j = 1401; - const y = 4716; - const B = 274277; - const C = -38; - const r = 4; - const v = 3; + const m = 2; + const n = 12; const p = 1461; + const r = 4; + const s = 153; const u = 5; + const v = 3; const w = 2; - const s = 153; - const n = 12; - const m = 2; + const y = 4716; - const f = J + j + (((4 * J + B) / 146097) * 3) / 4 + C; - const e = r * f + v; - const g = (e % p) / r; + const f = J + j + (((4 * J + b) / 146097) * 3) / 4 + c; + const a = r * f + v; + const g = (a % p) / r; const h = u * g + w; const D = (h % s) / u + 1; const M = ((h / s + m) % n) + 1; - const Y = (e / p) - y + (n + m - M) / n; + const Y = (a / p) - y + (n + m - M) / n; - ld.year = Y: int; - ld.month = M: int; - ld.day = D: int; + return (Y: int, M: int, D: int); }; -export fn calc_ywd(e: chrono::epochal, ld: *localdate) void = { - // TODO - ld.year = 0; - ld.week = 0; - ld.weekday = 0; - return; +// Calculates the day of a year, given a year, month, and day +fn calc_yearday(y: int, m: int, d: int) int = { + const months_firsts: [_]int = [ + 0, 31, 59, + 90, 120, 151, + 181, 212, 243, + 273, 304, 334, + ]; + + if (m >= 2 && is_leap_year(y)) { + return months_firsts[m - 1] + d + 1; + } else { + return months_firsts[m - 1] + d; + }; }; -export fn calc_yd(e: chrono::epochal, ld: *localdate) void = { - // TODO - ld.year = 0; - ld.yearday = 0; - return; +// Calculates the ISO week-numbering year, +// given a year, month, day, and weekday +fn calc_isoweekyear(y: int, m: int, d: int, wd: int) int = { + if ( + // if the date is within a week whose Thurday + // belongs to the previous gregorian year + m == 1 && ( + (d == 1 && (wd == 5 || wd == 6 || wd == 7)) + || (d == 2 && (wd == 6 || wd == 7)) + || (d == 3 && wd == 7) + ) + ) { + return y - 1; + } else if ( + // if the date is within a week whose Thurday + // belongs to the next gregorian year + m == 12 && ( + (d == 29 && wd == 1) + || (d == 30 && (wd == 1 || wd == 2)) + || (d == 31 && (wd == 1 || wd == 2 || wd == 3)) + ) + ) { + return y + 1; + } else { + return y; + }; +}; + +// Calculates the ISO week of a [[chrono::epochal]] +fn calc_isoweek(y: int, w: int, wd: int, yd: int) int = { + const jan1wd = (yd - wd + 7) % 7 + 1; + + const iw = if (jan1wd == 1) { + yield w; + } else if (jan1wd == 2 || jan1wd == 3 || jan1wd == 4) { + yield w + 1; + } else { + yield if (w == 0) { + yield if (jan1wd == 5) { + yield 53; + } else if (jan1wd == 6) { + yield if (is_leap_year(y - 1)) { + yield 53; + } else { + yield 52; + }; + } else if (jan1wd == 7) { + yield 52; + } else { + // all jan1wd values exhausted + abort("Unreachable"); + }; + } else { + yield w; + }; + }; + + return iw; +}; + +// Calculates the week within a Gregorian year [0..53], +// given a yearday and weekday. +// All days in a new year before the year's first Monday belong to week 0. +fn calc_week(yd: int, wd: int) int = { + return (5 + yd - wd) / 7; +}; + +// Calculates the weekday of a [[chrono::epochal]], +// 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 weekday of a [[chrono::epochal]], +// from Monday=0 to Sunday=6 +fn calc_zeroweekday(wd: int) int = { + return wd - 1; +}; + +// Calculates whether a given year is a leap year. +fn is_leap_year(y: int) bool = { + return if (y % 4 != 0) false + else if (y % 100 != 0) true + else if (y % 400 != 0) false + else true; }; // Converts a [[chrono::epocal]] to a [[localdate]]. The fields in "date" are @@ -91,32 +201,36 @@ export fn conv_epochal_localdate( date: *localdate, want: *localdate, ) void = { - if ( - // TODO: split up calc_ymd for finer grained - // calculating and populating - want.year is int - || want.month is int - || want.day is int - ) { - calc_ymd(e, date); + if (want.year is int || want.month is int || want.day is int) { + const ymd = calc_ymd(e); + date.year = ymd.0; + date.month = ymd.1; + date.day = ymd.2; }; - if ( - want.year is int - || want.week is int - || want.weekday is int - ) { - calc_ywd(e, date); + + if (want.yearday is int) { + if (date.year is void || date.month is void || date.day is void) { + const ymd = calc_ymd(e); + date.year = ymd.0; + date.month = ymd.1; + date.day = ymd.2; + }; + calc_yearday(date.year: int, date.month: int, date.day: int); }; - if ( - want.year is int - || want.yearday is int - ) { - calc_yd(e, date); + + if (want.week is int) { + // TODO + //calc_isoweek(e); + void; + }; + + if (want.weekday is int) { + date.weekday = calc_weekday(e); }; }; // Converts a year-month-day date into an [[chrono::epocal]] -export fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = { +fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = { // Algorithm adapted from: // https://en.wikipedia.org/wiki/Julian_day // @@ -134,13 +248,13 @@ export fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::inv }; // Converts a year-week-weekday date into an [[chrono::epocal]] -export fn epocal_from_ywd() (chrono::epochal | errors::invalid) = { +fn epocal_from_ywd() (chrono::epochal | errors::invalid) = { // TODO return 0; }; // Converts a year-yearday date into an [[chrono::epocal]] -export fn epocal_from_yd() (chrono::epochal | errors::invalid) = { +fn epocal_from_yd() (chrono::epochal | errors::invalid) = { // TODO return 0; }; diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -71,7 +71,8 @@ export fn new_datetime( year = year, month = month, day = day, - weekyear = void, + isoweekyear = void, + isoweek = void, week = void, weekday = void, yearday = void, diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -182,14 +182,26 @@ bytes() { } datetime() { - gen_srcs datetime \ - calendar.ha \ - datetime.ha \ - timezone.ha \ - date.ha \ - time.ha \ - format.ha - gen_ssa datetime errors fmt strings strio time time::chrono + if [ $testing -eq 0 ] + then + gen_srcs datetime \ + calendar.ha \ + datetime.ha \ + timezone.ha \ + date.ha \ + time.ha \ + format.ha + else + gen_srcs datetime \ + calendar.ha \ + datetime.ha \ + timezone.ha \ + date.ha \ + date+test.ha \ + time.ha \ + format.ha + fi + gen_ssa datetime errors fmt strings strio time time::chrono } compress_flate() { diff --git a/stdlib.mk b/stdlib.mk @@ -2896,6 +2896,7 @@ testlib_datetime_any_srcs= \ $(STDLIB)/datetime/datetime.ha \ $(STDLIB)/datetime/timezone.ha \ $(STDLIB)/datetime/date.ha \ + $(STDLIB)/datetime/date+test.ha \ $(STDLIB)/datetime/time.ha \ $(STDLIB)/datetime/format.ha