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:
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 \