hare

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

commit 189e10f061f7fc5e38caa6948e272b4493115f93
parent b25b0cfc68280e58558d2cca37b92595652bbb27
Author: Byron Torres <b@torresjrjr.com>
Date:   Fri, 12 Nov 2021 22:17:13 +0000

add calendar fns, expand localdate, sketch tz code

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

Diffstat:
Mchrono/chronology.ha | 46+++++++++++++++++-----------------------------
Mchrono/isocal/calendar.ha | 192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mchrono/isocal/date.ha | 38++++++++++++++++++++++++--------------
Mchrono/isocal/datetime.ha | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mchrono/isocal/time.ha | 4++--
Dchrono/isocal/types.ha | 34----------------------------------
Mchrono/timescales.ha | 48+++++++++++++++++++++++++++++++++++++++++++-----
Achrono/timezone.ha | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 4++--
Mstdlib.mk | 8++++----
10 files changed, 396 insertions(+), 134 deletions(-)

diff --git a/chrono/chronology.ha b/chrono/chronology.ha @@ -1,38 +1,26 @@ use time; -// Represents a chronological system for ordering days as dates and eras -export type chronology = struct { - // MJD = Modified Julian Date - to_mjd: nullable *fn(day: moment) moment, - to_moment: nullable *fn(day: moment) moment, -}; - -// Represents a calendar system for ordering datea and eras -export type calendar = struct { - // MJDN = Modified Julian Day Number - // RD = Rata Die - to_mjdn: nullable *fn(dn: daynum) daynum, - to_rd: nullable *fn(dn: daynum) daynum, -}; +// // A chronological system for ordering days as dates and eras +// export type chronology = struct { +// to_taidate: nullable *fn(n: moment) moment, +// from_taidate: nullable *fn(n: moment) moment, +// +// scale: timescale, +// }; -// Represents a moment in time on a calendar; essentially a datetime +// A date & time within a chronology export type moment = struct { - day: daynum, + date: epochal, time: time::duration, + loc: locality, }; -// Represents a Rata Die, an ordinal day since a calendar epoch +// An ordinal day on earth since the calendar epoch (zeroth day) 1970-01-01 // // Notes: -// aka: -// - epochalday -// - epochal -// - nday -export type daynum = i64; - -// Represents the Modified Julian Day Number calendar. -// Not to be confused with the Julian calendar. -export const MJDN: calendar = calendar { ... }; - -// Represents the Modified Julian Date chronology. -export const MJD: chronology = chronology { ... }; +// 1970-01-01 is "the Hare date-epoch". It was chosen out of simplicity to match +// the UNIX timescale epoch. This shouldn't affect performance in calendar +// implementations because they can convert to other epochs if they so desire. +// (as of writing) chrono::isocal:: converts to the Julian epoch for +// calculations. +export type epochal = i64; diff --git a/chrono/isocal/calendar.ha b/chrono/isocal/calendar.ha @@ -1,21 +1,173 @@ +use time; +use chrono; + + +// The epoch of the Julian Day Number +export def EPOCH_JULIAN: i64 = -2440588; + +// The epoch of the Common Era +export def EPOCH_COMMONERA: i64 = -719164; + + +export fn conv_datetime_moment(dt: datetime) chrono::moment = { + const d = conv_localdate_epochal(dt.date); + const t = conv_localtime_time(dt.time); + const m = chrono::moment { + date = d, + time = t, + loc = dt.loc, + }; + return m; +}; + +export fn conv_moment_datetime(m: chrono::moment) datetime = { + const ld = conv_epochal_localdate(m.date); + const lt = conv_time_localtime(m.time); + const dt = datetime { + date = ld, + time = lt, + loc = m.loc, + }; + return dt; +}; + + +// +// date-like +// + +// Calculates a moment's number of days since the calendar epoch 0001-01-01 +export fn epocal(m: chrono::moment) int = { + return (EPOCH_COMMONERA + m.date): int; +}; + +// Calculates a moment's era +export fn era(m: chrono::moment) int = { + if (m.date > EPOCH_COMMONERA) { + return 0; // CE, Common Era + } else { + return -1; // BCE, Before Common Era + }; +}; + +// Calculates a moment's year +export fn year(m: chrono::moment) int = { + const ld = conv_epochal_localdate(m.date); + return ld.year as int; +}; + +// Calculates a moment's month +export fn month(m: chrono::moment) int = { + const ld = conv_epochal_localdate(m.date); + return ld.month as int; +}; + +// Calculates a moment's day of the month +export fn day(m: chrono::moment) int = { + const ld = conv_epochal_localdate(m.date); + return ld.day as int; +}; + +// Calculates a moment's ISO week calendar year +export fn weekyear(m: chrono::moment) int = { + // TODO + return 0; +}; + +// Calculates a moment's ISO week +export fn week(m: chrono::moment) int = { + // TODO + return 0; +}; + +// Calculates a moment's ordinal day of the year +export fn yearday(m: chrono::moment) int = { + // TODO + return 0; +}; + +// +// time-like +// + +// Calculates a moment's hour of the day +export fn hour(m: chrono::moment) int = { + return (m.time / time::HOUR): int; +}; + +// Calculates a moment's minute of the hour +export fn min(m: chrono::moment) int = { + return ((m.time / time::MINUTE) % 60): int; +}; + +// Calculates a moment's second of the minute +export fn sec(m: chrono::moment) int = { + return ((m.time / time::SECOND) % 60): int; +}; + +// Calculates a moment's nanosecond of the second +export fn nsec(m: chrono::moment) int = { + return (m.time % time::SECOND): int; +}; + + +// A contextual span of time in the proleptic Gregorian calendar. +// Used for calendar arithmetic. +export type period = struct { + eras: int, + years: int, + + // Can be 28, 29, 30, or 31 days long + months: int, + + // Weeks start on Monday + weeks: int, + + days: int, + hours: int, + minutes: int, + seconds: int, + nanoseconds: int, +}; + +// Hops along the calendar from a moment, according to the given periods +// sequencially, consulting period's units from largest to smallest. +// +// // m := 1999-05-13 12:30:45 +// isocal::hop(m, isocal::period { +// years = 22, // 2021-01-01 00:00:00 +// months = -1, // 2020-11-01 00:00:00 +// days = -4, // 2020-10-27 00:00:00 +// }); +// +export fn hop(m: chrono::moment, pp: period...) chrono::moment = { + // TODO + for (let i = 0z; i < len(pp); i += 1) { + const p = pp[i]; + }; + return m; +}; + +// Adds a calindrical period of time to a moment, largest units first. +// Tries to conserve relative distance from cyclical points on the calendar. +// +// // m := 1999-05-13 12:30:45 +// isocal::hop(m, isocal::period { +// years = 22, // 2021-05-13 00:00:00 +// months = -1, // 2021-04-13 00:00:00 +// days = -4, // 2020-04-09 00:00:00 +// }); +// +// When units overflow, such as when adding a month to Jan 31st inaccurately +// results to Feb 31th, the flag is consulted on how to handle this. +// +// TODO: +// How to handle overflows and predictability with cal-arithm in general? +export fn add(m: chrono::moment, flag: int, pp: period...) chrono::moment = { + // TODO + for (let i = 0z; i < len(pp); i += 1) { + const p = pp[i]; + }; + return m; +}; -// // Represents a calendar system which supports datetimes -// export type calendar = struct { -// // chrono::calendar, -// to_localdate: *conv_daynum_localdate, -// to_daynum: *conv_localdate_daynum, -// }; -// -// export type conv_daynum_localdate = fn(dn: daynum) localdate; -// -// export type conv_localdate_daynum = fn(ld: localdate) daynum; -// -// export type chronology = struct { -// // chrono::chronology, -// to_datetime: *conv_moment_datetime, -// to_moment: *conv_datetime_moment, -// }; -// -// export type conv_moment_datetime = fn(m: moment) datetime; -// -// export type conv_datetime_moment = fn(dt: datetime) moment; diff --git a/chrono/isocal/date.ha b/chrono/isocal/date.ha @@ -1,14 +1,24 @@ use chrono; -// Represents a ISO calendar date +// Represents an ISO calendar date. +// Instances created from isocal:: functions are guaranteed to be valid. export type localdate = struct { - year: i64, - month: int, - day: int, + era: (void | int), + year: (void | int), + month: (void | int), + day: (void | int), + weekyear: (void | int), + week: (void | int), + weekday: (void | int), + yearday: (void | int), }; -export fn conv_daynum_localdate(dn: chrono::daynum) localdate = { - const J = dn; +export fn conv_epochal_localdate(epochal: chrono::epochal) localdate = { + // 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 = 1401; const y = 4716; const B = 274277; @@ -32,24 +42,24 @@ export fn conv_daynum_localdate(dn: chrono::daynum) localdate = { const Y = (e / p) - y + (n + m - M) / n; const ld = localdate { - year = Y, + year = Y: int, month = M: int, day = D: int, }; return ld; }; -export fn conv_localdate_daynum(ld: localdate) chrono::daynum = { - const Y = ld.year; - const M = ld.month; - const D = ld.day; +export fn conv_localdate_epochal(ld: localdate) chrono::epochal = { + const Y = ld.year as int; + const M = ld.month as int; + const D = ld.day as int; const jdn = ( - (1461 * (Y + 4000 + (M - 14) / 12)) / 4 + (1461 * (Y + 4800 + (M - 14) / 12)) / 4 + (367 * (M - 2 - 12 * ((M - 14) / 12))) / 12 - (3 * ((Y + 4900 + (M - 14) / 12) / 100)) / 4 + D - 32075 ); - const dn = jdn; - return dn; + const epochal = jdn + EPOCH_JULIAN; + return epochal; }; diff --git a/chrono/isocal/datetime.ha b/chrono/isocal/datetime.ha @@ -1,4 +1,6 @@ use time; +use chrono; +use errors; // Represents a ISO datetime // @@ -18,14 +20,50 @@ export type datetime = struct { loc: locality, }; +export type locality = chrono::locality; + +// Creates a new moment +// +// // 1995 July 18th 09:16:00.000 +// isocal::new_moment(1995, 07, 18, 9, 16, 0, 0, isocal::local) +// +// For alternative forms, assemble a datetime manually using the desired types. +export fn new_moment( + year: int, + month: int, + day: int, + hour: int, + min: int, + sec: int, + nsec: int, + loc: locality, +) chrono::moment = { + const dt = datetime { + date = localdate { + year = year, + month = month, + day = day, + }, + time = localtime { + hour = hour, + min = min, + sec = sec, + nsec = nsec, + }, + loc = loc, + }; + const m = conv_datetime_moment(dt); + return m; +}; + // Creates a new datetime // // // 1995 July 18th 09:16:00.000 // isocal::new(1995, 07, 18, 9, 16, 0, 0, isocal::local) // // For alternative forms, assemble a datetime manually using the desired types. -export fn new( - year: i64, +export fn new_datetime( + year: int, month: int, day: int, hour: int, @@ -33,32 +71,51 @@ export fn new( sec: int, nsec: int, loc: locality, -) datetime = datetime { - date = localdate { - year = year, - month = month, - day = day, - }, - time = localtime { - hour = hour, - min = min, - sec = sec, - nsec = nsec, - }, - loc = loc, +) (datetime | errors::invalid) = { + const dt = datetime { + date = localdate { + year = year, + month = month, + day = day, + }, + time = localtime { + hour = hour, + min = min, + sec = sec, + nsec = nsec, + }, + loc = loc, + }; + if (!validate(dt)) { + return errors::invalid; + }; + return dt; +}; + +// Returns the current moment +export fn now_moment() chrono::moment = { + const i = time::now(time::clock::REALTIME); + const u = time::unix(i); + const d = (u / 86400); + const t = ( + (i.sec * time::SECOND) + (i.nsec * time::NANOSECOND) + ) % (24 * time::HOUR); + const m = chrono::moment { + date = d, + time = t, + loc = chrono::local, + }; + return m; }; // Returns the current datetime -export fn now() datetime = { +export fn now_datetime() datetime = { const i = time::now(time::clock::REALTIME); const u = time::unix(i); + const d = (u / 86400); + const ld = conv_epochal_localdate(d); const dt = datetime { - date = localdate { - // TODO: figure out calendar arithmetics first - year = 1970, - month = 1, - day = (i.sec / 86400): int, - }, + date = ld, time = localtime { hour = (i.sec / 3600): int % 24, min = (i.sec / 60): int % 60, @@ -69,9 +126,12 @@ export fn now() datetime = { // 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 = local, + loc = chrono::local, }; return dt; }; -export type error = !void; +export fn validate(dt: datetime) bool = { + // TODO + return true; +}; diff --git a/chrono/isocal/time.ha b/chrono/isocal/time.ha @@ -11,8 +11,8 @@ export type localtime = struct { export fn conv_time_localtime(t: time::duration) localtime = { const lt = localtime { hour = (t / time::HOUR): int, - min = (t / time::MINUTE): int % 60, - sec = (t / time::SECOND): int % 60, + min = ((t / time::MINUTE) % 60): int, + sec = ((t / time::SECOND) % 60): int, nsec = (t % time::SECOND): int, }; return lt; diff --git a/chrono/isocal/types.ha b/chrono/isocal/types.ha @@ -1,34 +0,0 @@ -use time; -use chrono; - -// // Represents a timezone -// // Allows conversion between datetimes and instances according to a timescale -// export type timezone = struct { -// chrono::timezone, -// to_tz_utc: nullable *converter, -// from_tz_utc: nullable *converter, -// to_tz_tai: nullable *converter, -// from_tz_tai: nullable *converter, -// }; - -export type converter = fn(dt: datetime) datetime; - -// Represents the locality of a datetime -export type locality = (local | zoneoffset | timezone); - -// Represents its associated datetime as local -export type local = void; - -// Represents a simple, constant offset -// -// Notes: -// Should this be "utcoffset"? -// Should this just be seconds? -export type zoneoffset = time::duration; - -// Represents a timezone; a political region with a ruleset regarding offsets -export type timezone = struct { - timescale: chrono::timescale, - // TODO - // func...: *fn... -}; diff --git a/chrono/timescales.ha b/chrono/timescales.ha @@ -2,11 +2,19 @@ use time; // Represents a linear scale of time, with an epoch. export type timescale = struct { - to_tai: *converter, - from_tai: *converter, + to_tai: *ts_converter, + from_tai: *ts_converter, }; -export type converter = fn(i: time::instant) []time::instant; +export type ts_converter = fn(i: time::instant) []time::instant; + +// TODO: put these error types to use. + +export type nonexistant = !void; + +export type ambiguous = ![]time::instant; + +export type error = !(nonexistant | ambiguous); // International Atomic Time @@ -31,7 +39,8 @@ fn conv_tai_tai(i: time::instant) []time::instant = { // Coordinated Universal Time // -// Based on TAI. Used as the basis of civil timekeeping. +// Used as the basis of civil timekeeping. +// Based on TAI, with an offset, changed roughly biannually. // Discontinuous (has leap seconds). export const UTC: timescale = timescale { to_tai = &conv_utc_tai, @@ -57,7 +66,8 @@ fn conv_utc_tai(utc: time::instant) []time::instant = { // Unix Time // -// Based on UTC. Used for computer timekeeping. +// Used for computer timekeeping. +// Based on UTC, near 1-to-1 correspondence. // Discontinuous (has leap seconds). export const UNIX: timescale = timescale { to_tai = &conv_utc_tai, @@ -80,3 +90,31 @@ fn conv_unix_tai(unix: time::instant) []time::instant = { return [tai]; }; + + +// Global Positioning System Time +// +// Used for GPS coordination. +// Based on TAI, constant -19 second offset. +// Continuous (no leap seconds). +export const GPS: timescale = timescale { + to_tai = &conv_utc_tai, + from_tai = &conv_tai_utc, +}; + +fn conv_tai_gps(tai: time::instant) []time::instant = { + const gps = time::instant { + sec = tai.sec - 19, + nsec = tai.nsec, + }; + return [gps]; +}; + +fn conv_gps_tai(gps: time::instant) []time::instant = { + const tai = time::instant { + sec = gps.sec + 19, + nsec = gps.nsec, + }; + return [tai]; +}; + diff --git a/chrono/timezone.ha b/chrono/timezone.ha @@ -0,0 +1,48 @@ +use time; + +// Represents the locality of a datetime +export type locality = (local | zoffset | timezone); + +// Represents its associated datetime as local +export type local = void; + +// Represents a simple, constant timezone offset +export type zoffset = time::duration; + +// Represents a conditional offset, dependant on the time of year +export type zone = struct { + zoffset: zoffset, + applied: *fn(m: moment) bool, + abbrev: str, // "CET" + dst: bool, +}; + +// Represents a timezone; a political region with a ruleset regarding offsets +export type timezone = struct { + timescale: timescale, + zones: []zone, + name: str, // "Europe/Amsterdam" +}; + +// Retrieves a IANA timezone object by name +export fn tzdb(name: str) timezone = { + // TODO + return timezone { ... }; +}; + +// export const TZ_UTC: timezone = timezone { +// timescale = UTC, +// zones = [ +// zone { +// zoffset = 0 * time::SECOND, +// applied = &zone_always, +// abbrev = "UTC", +// dst = false, +// }, +// ], +// name = "Etc/UTC", +// }; +// +// fn zone_always(m: moment) bool = { +// return true; +// }; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -184,13 +184,13 @@ bytes() { chrono() { gen_srcs chrono \ chronology.ha \ - timescales.ha + timescales.ha \ + timezone.ha gen_ssa chrono time } chrono_isocal() { gen_srcs chrono::isocal \ - types.ha \ calendar.ha \ datetime.ha \ date.ha \ diff --git a/stdlib.mk b/stdlib.mk @@ -722,7 +722,8 @@ $(HARECACHE)/bytes/bytes-any.ssa: $(stdlib_bytes_any_srcs) $(stdlib_rt) $(stdlib # chrono (+any) stdlib_chrono_any_srcs= \ $(STDLIB)/chrono/chronology.ha \ - $(STDLIB)/chrono/timescales.ha + $(STDLIB)/chrono/timescales.ha \ + $(STDLIB)/chrono/timezone.ha $(HARECACHE)/chrono/chrono-any.ssa: $(stdlib_chrono_any_srcs) $(stdlib_rt) $(stdlib_time_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -732,7 +733,6 @@ $(HARECACHE)/chrono/chrono-any.ssa: $(stdlib_chrono_any_srcs) $(stdlib_rt) $(std # chrono::isocal (+any) stdlib_chrono_isocal_any_srcs= \ - $(STDLIB)/chrono/isocal/types.ha \ $(STDLIB)/chrono/isocal/calendar.ha \ $(STDLIB)/chrono/isocal/datetime.ha \ $(STDLIB)/chrono/isocal/date.ha \ @@ -2666,7 +2666,8 @@ $(TESTCACHE)/bytes/bytes-any.ssa: $(testlib_bytes_any_srcs) $(testlib_rt) $(test # chrono (+any) testlib_chrono_any_srcs= \ $(STDLIB)/chrono/chronology.ha \ - $(STDLIB)/chrono/timescales.ha + $(STDLIB)/chrono/timescales.ha \ + $(STDLIB)/chrono/timezone.ha $(TESTCACHE)/chrono/chrono-any.ssa: $(testlib_chrono_any_srcs) $(testlib_rt) $(testlib_time_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -2676,7 +2677,6 @@ $(TESTCACHE)/chrono/chrono-any.ssa: $(testlib_chrono_any_srcs) $(testlib_rt) $(t # chrono::isocal (+any) testlib_chrono_isocal_any_srcs= \ - $(STDLIB)/chrono/isocal/types.ha \ $(STDLIB)/chrono/isocal/calendar.ha \ $(STDLIB)/chrono/isocal/datetime.ha \ $(STDLIB)/chrono/isocal/date.ha \