commit 5fc0a4ff6b7700edd35692f605056afa28aecbdf
parent bab9aefa2f6682aa4a850cde6f3f465a8cf21ef0
Author: Byron Torres <b@torresjrjr.com>
Date: Sat, 12 Mar 2022 16:25:52 +0000
new chrono::isocal:: modules
Signed-off-by: Byron Torres <b@torresjrjr.com>
Diffstat:
14 files changed, 424 insertions(+), 32 deletions(-)
diff --git a/chrono/README b/chrono/README
@@ -0,0 +1,4 @@
+The chrono module provides the basis for chronology, namely timescales
+and leap seconds, and abstracted interfaces for calendars and datetimes.
+
+For working with the ISO calendar, see the [[chrono::isocal]] submodule.
diff --git a/chrono/calendar.ha b/chrono/calendar.ha
@@ -0,0 +1,16 @@
+use time;
+
+// Represents a system for ordering days as dates and eras.
+// Allows conversion between [[date]] types.
+export type calendar = struct {
+ // TODO
+ TODO: bool,
+};
+
+export type localdate = struct {
+ nday: i64,
+};
+
+export type localtime = struct {
+ ntime: time::duration,
+};
diff --git a/chrono/chronology.ha b/chrono/chronology.ha
@@ -0,0 +1,17 @@
+use time;
+
+// Represents a [[timescale]]-[[calendar]] system for ordering events.
+// Allows conversion between [[datetime]] and [[time::instant]] types.
+//
+// Notes:
+// Needs rethinking. Initially, the idea was to have the possibility of multiple
+// plug-and-plug calendars, but since hare doesn't have objects, we need to
+// deisgn this another way. Probably, datetime:: will be solely ISO 8601
+// Proleptic Gregorian, with TAI, UTC, and UNIX, and then we'll have something
+// like datetime::chrono::* for alternative chronologies. 3rd party libraries
+// would be similar.
+export type chronology = struct {
+ timescale: timescale,
+ calendar: calendar,
+};
+
diff --git a/chrono/isocal/README b/chrono/isocal/README
@@ -0,0 +1,5 @@
+The chrono::isocal submodule implements the international standard
+proleptic Gregorian calendar, as per ISO 8601.
+
+This module provides datetimes, timezones, offsets, and the arithmetics,
+formatting, parsing, serialization, and conversion thereof.
diff --git a/chrono/isocal/datetime.ha b/chrono/isocal/datetime.ha
@@ -0,0 +1,73 @@
+use time;
+
+// Creates a new datetime, using a [[calendardate]].
+//
+// // 1995 July 18th 09:16:00.000
+// datetime::new(1995, 07, 18, 9, 16, 0, 0, datetime::local)
+// chrono::iso::new(1995, 07, 18, 9, 16, 0, 0, chrono::iso::local)
+//
+// For alternative forms, assemble a datetime manually using the desired types.
+export fn new(
+ year: i64,
+ month: u8,
+ day: u8,
+ hour: u8,
+ min: u8,
+ sec: u8,
+ nsec: u64,
+ loc: locality,
+) datetime = datetime {
+ date = localdate {
+ year = year,
+ month = month,
+ day = day,
+ },
+ time = localtime {
+ hour = hour,
+ min = min,
+ sec = sec,
+ nsec = nsec,
+ },
+ loc = loc,
+};
+
+// Returns the current datetime
+export fn now() datetime = {
+ const i = time::now(time::clock::REALTIME);
+ const u = time::unix(i);
+ const dt = datetime {
+ date = localdate {
+ // TODO: figure out calendar arithmetics first
+ year = 1970,
+ month = 1,
+ day = (i.sec / 86400): u8,
+ },
+ time = localtime {
+ hour = (i.sec / 3600): u8 % 24,
+ min = (i.sec / 60): u8 % 60,
+ sec = i.sec: u8 % 60,
+ nsec = i.nsec: u64,
+ },
+
+ // TODO: What to do here? How to get the timezone from
+ // /etc/localtime? How to determine the system's timescale?
+ // Assuming UTC may be sufficient.
+ loc = local,
+ };
+ return dt;
+};
+
+// Normalizes all fields a datetime
+export fn normalize(dt: datetime) datetime = {
+ validate(dt)!;
+ // TODO
+ return dt;
+};
+
+// Validates a datetime
+export fn validate(dt: datetime) (datetime | error) = {
+ // TODO
+ return dt;
+};
+
+export type error = !void;
diff --git a/chrono/isocal/types.ha b/chrono/isocal/types.ha
@@ -0,0 +1,79 @@
+use time;
+use chrono;
+
+// Represents a ISO datetime
+//
+// Notes:
+// Java has good separation of types: A LocalDatetime, ZonedDatetime,
+// OffsetDatetime. Python instead reasons about datetimes as being
+// timezone-aware/naive. Here's I try to leaverage Hare's type system to combine
+// the two.
+//
+// Putting the date and time related fields into separate typed structs maybe
+// isn't a good idea (see `type localdate` below), but I still put it here
+// because localtime is often used on it's own, and it makes some sense to have
+// a datetime be composed of a date and time.
+export type datetime = struct {
+ date: localdate,
+ time: localtime,
+ loc: locality,
+};
+
+// Represents a ISO date
+export type localdate = struct {
+ // days since calendar epoch
+ epochday: i64,
+
+ // ISO calendar date
+ year: i64,
+ month: u8,
+ day: u8,
+
+ // ISO week date
+ weekyear: i64,
+ week: u8,
+ weekday: u8,
+
+ // ISO ordinal date
+ yearday: uint,
+};
+
+// Represents a ISO time of day
+export type localtime = struct {
+ hour: u8,
+ min: u8,
+ sec: u8,
+ nsec: u64,
+};
+
+// Represents the locality of a datetime
+//
+// Notes:
+// `timescale` may not meaningfully belong here. It's here because sometimes ISO
+// datetimes like "1999-05-13T12:30:45.125 UTC" exist as a human-readible form
+// of a UTC instant, using the ISO calendar, and where localtime.sec ranges only
+// from 0-59.
+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
+//
+// Notes:
+// timezone types design needs a lot of thought. Enum is probably not a good
+// idea. Initially this was a `type enum str {}`, which may have been a worse
+// idea. Maybe we need a `fn tzdb(tz: str)` like other langs' libs.
+export type timezone = enum {
+ UTC,
+ EUROPE_LONDON,
+ // ...
+};
+
diff --git a/chrono/timescales.ha b/chrono/timescales.ha
@@ -0,0 +1,80 @@
+use time;
+
+// Represents a linear scale of time, with an epoch.
+export type timescale = struct {
+ to_tai: *fn(i: time::instant) []time::instant,
+ from_tai: *fn(i: time::instant) []time::instant,
+};
+
+
+// International Atomic Time
+//
+// The realisation of proper time on Earth's geoid.
+// Continuous (no leap seconds).
+export const TAI: timescale = timescale {
+ to_tai = &conv_tai_tai,
+ from_tai = &conv_tai_tai,
+};
+
+fn conv_tai_tai(i: time::instant) []time::instant = {
+ return [i];
+};
+
+
+// TODO: Write proper conversion functions for all timescales.
+// Functions are to return two or no instants, depending on any leap second
+// events, and use a proper leap second table.
+// https://www.ietf.org/timezones/data/leap-seconds.list
+
+
+// Coordinated Universal Time
+//
+// Based on TAI. Used as the basis of civil timekeeping.
+// Discontinuous (has leap seconds).
+export const UTC: timescale = timescale {
+ to_tai = &conv_utc_tai,
+ from_tai = &conv_tai_utc,
+};
+
+fn conv_tai_utc(tai: time::instant) []time::instant = {
+ const utc = time::instant {
+ sec = tai.sec - 37,
+ nsec = tai.nsec,
+ };
+ return [utc];
+};
+
+fn conv_utc_tai(utc: time::instant) []time::instant = {
+ const tai = time::instant {
+ sec = utc.sec + 37,
+ nsec = utc.nsec,
+ };
+ return [tai];
+};
+
+
+// Unix Time
+//
+// Based on UTC. Used for computer timekeeping.
+// Discontinuous (has leap seconds).
+export const UNIX: timescale = timescale {
+ to_tai = &conv_utc_tai,
+ from_tai = &conv_tai_utc,
+};
+
+fn conv_tai_unix(tai: time::instant) []time::instant = {
+ const unix = time::instant {
+ sec = tai.sec - 37,
+ nsec = tai.nsec,
+ };
+ return [unix];
+};
+
+fn conv_unix_tai(unix: time::instant) []time::instant = {
+ const tai = time::instant {
+ sec = unix.sec + 37,
+ nsec = unix.nsec,
+ };
+ return [tai];
+};
+
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -181,6 +181,21 @@ bytes() {
gen_ssa bytes types
}
+chrono() {
+ gen_srcs chrono \
+ chronology.ha \
+ timescales.ha \
+ calendar.ha
+ gen_ssa chrono time
+}
+
+chrono_isocal() {
+ gen_srcs chrono::isocal \
+ datetime.ha \
+ types.ha
+ gen_ssa chrono::isocal chrono time
+}
+
compress_flate() {
gen_srcs compress::flate \
inflate.ha
@@ -1153,6 +1168,7 @@ time() {
+linux/functions.ha \
+linux/+'$(ARCH)'.ha \
arithm.ha \
+ conv.ha \
types.ha
gen_ssa -plinux time linux::vdso
gen_srcs -pfreebsd time \
@@ -1253,6 +1269,8 @@ uuid() {
modules="ascii
bufio
bytes
+chrono
+chrono::isocal
compress::flate
compress::zlib
crypto
diff --git a/stdlib.mk b/stdlib.mk
@@ -148,6 +148,18 @@ stdlib_deps_any+=$(stdlib_bytes_any)
stdlib_bytes_linux=$(stdlib_bytes_any)
stdlib_bytes_freebsd=$(stdlib_bytes_any)
+# gen_lib chrono (any)
+stdlib_chrono_any=$(HARECACHE)/chrono/chrono-any.o
+stdlib_deps_any+=$(stdlib_chrono_any)
+stdlib_chrono_linux=$(stdlib_chrono_any)
+stdlib_chrono_freebsd=$(stdlib_chrono_any)
+
+# gen_lib chrono::isocal (any)
+stdlib_chrono_isocal_any=$(HARECACHE)/chrono/isocal/chrono_isocal-any.o
+stdlib_deps_any+=$(stdlib_chrono_isocal_any)
+stdlib_chrono_isocal_linux=$(stdlib_chrono_isocal_any)
+stdlib_chrono_isocal_freebsd=$(stdlib_chrono_isocal_any)
+
# gen_lib compress::flate (any)
stdlib_compress_flate_any=$(HARECACHE)/compress/flate/compress_flate-any.o
stdlib_deps_any+=$(stdlib_compress_flate_any)
@@ -707,6 +719,29 @@ $(HARECACHE)/bytes/bytes-any.ssa: $(stdlib_bytes_any_srcs) $(stdlib_rt) $(stdlib
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nbytes \
-t$(HARECACHE)/bytes/bytes.td $(stdlib_bytes_any_srcs)
+# chrono (+any)
+stdlib_chrono_any_srcs= \
+ $(STDLIB)/chrono/chronology.ha \
+ $(STDLIB)/chrono/timescales.ha \
+ $(STDLIB)/chrono/calendar.ha
+
+$(HARECACHE)/chrono/chrono-any.ssa: $(stdlib_chrono_any_srcs) $(stdlib_rt) $(stdlib_time_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/chrono
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nchrono \
+ -t$(HARECACHE)/chrono/chrono.td $(stdlib_chrono_any_srcs)
+
+# chrono::isocal (+any)
+stdlib_chrono_isocal_any_srcs= \
+ $(STDLIB)/chrono/isocal/datetime.ha \
+ $(STDLIB)/chrono/isocal/types.ha
+
+$(HARECACHE)/chrono/isocal/chrono_isocal-any.ssa: $(stdlib_chrono_isocal_any_srcs) $(stdlib_rt) $(stdlib_chrono_$(PLATFORM)) $(stdlib_time_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/chrono/isocal
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nchrono::isocal \
+ -t$(HARECACHE)/chrono/isocal/chrono_isocal.td $(stdlib_chrono_isocal_any_srcs)
+
# compress::flate (+any)
stdlib_compress_flate_any_srcs= \
$(STDLIB)/compress/flate/inflate.ha
@@ -1739,6 +1774,7 @@ stdlib_time_linux_srcs= \
$(STDLIB)/time/+linux/functions.ha \
$(STDLIB)/time/+linux/+$(ARCH).ha \
$(STDLIB)/time/arithm.ha \
+ $(STDLIB)/time/conv.ha \
$(STDLIB)/time/types.ha
$(HARECACHE)/time/time-linux.ssa: $(stdlib_time_linux_srcs) $(stdlib_rt) $(stdlib_linux_vdso_$(PLATFORM))
@@ -2054,6 +2090,18 @@ testlib_deps_any+=$(testlib_bytes_any)
testlib_bytes_linux=$(testlib_bytes_any)
testlib_bytes_freebsd=$(testlib_bytes_any)
+# gen_lib chrono (any)
+testlib_chrono_any=$(TESTCACHE)/chrono/chrono-any.o
+testlib_deps_any+=$(testlib_chrono_any)
+testlib_chrono_linux=$(testlib_chrono_any)
+testlib_chrono_freebsd=$(testlib_chrono_any)
+
+# gen_lib chrono::isocal (any)
+testlib_chrono_isocal_any=$(TESTCACHE)/chrono/isocal/chrono_isocal-any.o
+testlib_deps_any+=$(testlib_chrono_isocal_any)
+testlib_chrono_isocal_linux=$(testlib_chrono_isocal_any)
+testlib_chrono_isocal_freebsd=$(testlib_chrono_isocal_any)
+
# gen_lib compress::flate (any)
testlib_compress_flate_any=$(TESTCACHE)/compress/flate/compress_flate-any.o
testlib_deps_any+=$(testlib_compress_flate_any)
@@ -2613,6 +2661,29 @@ $(TESTCACHE)/bytes/bytes-any.ssa: $(testlib_bytes_any_srcs) $(testlib_rt) $(test
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nbytes \
-t$(TESTCACHE)/bytes/bytes.td $(testlib_bytes_any_srcs)
+# chrono (+any)
+testlib_chrono_any_srcs= \
+ $(STDLIB)/chrono/chronology.ha \
+ $(STDLIB)/chrono/timescales.ha \
+ $(STDLIB)/chrono/calendar.ha
+
+$(TESTCACHE)/chrono/chrono-any.ssa: $(testlib_chrono_any_srcs) $(testlib_rt) $(testlib_time_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/chrono
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nchrono \
+ -t$(TESTCACHE)/chrono/chrono.td $(testlib_chrono_any_srcs)
+
+# chrono::isocal (+any)
+testlib_chrono_isocal_any_srcs= \
+ $(STDLIB)/chrono/isocal/datetime.ha \
+ $(STDLIB)/chrono/isocal/types.ha
+
+$(TESTCACHE)/chrono/isocal/chrono_isocal-any.ssa: $(testlib_chrono_isocal_any_srcs) $(testlib_rt) $(testlib_chrono_$(PLATFORM)) $(testlib_time_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/chrono/isocal
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nchrono::isocal \
+ -t$(TESTCACHE)/chrono/isocal/chrono_isocal.td $(testlib_chrono_isocal_any_srcs)
+
# compress::flate (+any)
testlib_compress_flate_any_srcs= \
$(STDLIB)/compress/flate/inflate.ha
@@ -3690,6 +3761,7 @@ testlib_time_linux_srcs= \
$(STDLIB)/time/+linux/functions.ha \
$(STDLIB)/time/+linux/+$(ARCH).ha \
$(STDLIB)/time/arithm.ha \
+ $(STDLIB)/time/conv.ha \
$(STDLIB)/time/types.ha
$(TESTCACHE)/time/time-linux.ssa: $(testlib_time_linux_srcs) $(testlib_rt) $(testlib_linux_vdso_$(PLATFORM))
diff --git a/time/+linux/functions.ha b/time/+linux/functions.ha
@@ -7,15 +7,15 @@ use linux::vdso;
// Converts a [[duration]] to an [[rt::timespec]]. This function is
// non-portable.
-export fn duration_to_timespec(n: duration, ts: *rt::timespec) void = {
- ts.tv_sec = n / SECOND;
- ts.tv_nsec = n % SECOND;
+export fn duration_to_timespec(d: duration, ts: *rt::timespec) void = {
+ ts.tv_sec = d / SECOND;
+ ts.tv_nsec = d % SECOND;
};
// Converts an [[instant]] to an [[rt::timespec]]. This function is
// non-portable.
export fn instant_to_timespec(t: instant, ts: *rt::timespec) void = {
- ts.tv_sec = t.sec;
+ ts.tv_sec = t.sec;
ts.tv_nsec = t.nsec;
};
@@ -27,9 +27,9 @@ export fn timespec_to_instant(ts: rt::timespec) instant = instant {
};
// Yields the process to the kernel and returns after the requested duration.
-export fn sleep(n: duration) void = {
+export fn sleep(d: duration) void = {
let in = rt::timespec { ... };
- duration_to_timespec(n, &in);
+ duration_to_timespec(d, &in);
let req = ∈
for (true) {
diff --git a/time/README b/time/README
@@ -0,0 +1,4 @@
+The time module provides basic timekeeping primitives and access to the
+system's clocks.
+
+For more human abstractions of time, see the [[chrono]] module.
diff --git a/time/arithm.ha b/time/arithm.ha
@@ -1,4 +1,5 @@
// License: MPL-2.0
+// (c) 2022 Byron Torres <b@torresjrjr.com>
// (c) 2021 Drew DeVault <sir@cmpwn.com>
// Adds a [[duration]] to an [[instant]], returning an instant further in the
@@ -9,10 +10,40 @@ export fn add(a: instant, d: duration) instant = instant {
nsec = a.nsec + d,
};
-// Returns the duration from [[instant]] a to [[instant]] b.
-// If a is earlier than b, the duration is positive.
-// If a is equivilent to b, the duration is zero.
-// If a is later than b, the duration is negative.
+// Returns the [[duration]] from [[instant]] "a" to [[instant]] "b".
export fn diff(a: instant, b: instant) duration = {
return ((b.sec - a.sec) * SECOND) + (b.nsec - a.nsec);
};
+
+// Returns:
+// -1 if a precedes b;
+// 0 if a and b are simultaneous;
+// +1 if b precedes a;
+//
+// Notes:
+// At first seems superceded by elapsed, but useful when you need values at
+// compile time:
+// switch (compare(a, b)) {
+// case -1 => ...;
+// case 0 => ...;
+// case 1 => ...;
+// case => abort("Unreachable")
+// };
+//
+// Would it be better as an enum, to avoid that abort()?
+export fn compare(a: instant, b: instant) i8 = {
+ return if (a.sec < b.sec) -1
+ else if (a.sec > b.sec) 1
+ else if (a.nsec < b.nsec) -1
+ else if (a.nsec > b.nsec) 1
+ else 0;
+};
+
+@test fn compare() void = {
+ let a = now(clock::MONOTONIC);
+ sleep(1 * MILLISECOND);
+ let b = now(clock::MONOTONIC);
+ assert(compare(a, b) < 0);
+ assert(compare(b, a) > 0);
+ assert(compare(a, a) == 0);
+};
diff --git a/time/conv.ha b/time/conv.ha
@@ -0,0 +1,8 @@
+// Converts the given [[instant]] to a Unix timestamp.
+export fn unix(a: instant) i64 = a.sec;
+
+// Returns a [[instant]] from a Unix timestamp.
+export fn from_unix(u: i64) instant = instant {
+ sec = u,
+ nsec = 0,
+};
diff --git a/time/types.ha b/time/types.ha
@@ -27,28 +27,13 @@ export def HOUR: duration = 60 * MINUTE;
// Represents a specific instant in time as seconds (+nanoseconds) since an
// arbitrary epoch. Instants may only be meaningfully compared with other
// instants sourced from the same clock.
-export type instant = struct { sec: i64, nsec: i64 };
-
-// The return value is negative if a < b, zero if a == b, and positive if a > b.
-export fn compare(a: instant, b: instant) int = {
- return if (a.sec < b.sec) -1 else if (a.sec > b.sec) 1 else
- if (a.nsec < b.nsec) -1 else if (a.nsec > b.nsec) 1 else 0;
-};
-
-// Converts the given [[instant]] to a Unix timestamp.
-export fn unix(a: instant) i64 = a.sec;
-
-// Returns a [[instant]] from a Unix timestamp.
-export fn from_unix(u: i64) instant = instant {
- sec = u,
- nsec = 0,
+export type instant = struct {
+ sec: i64,
+ nsec: i64,
};
-@test fn compare() void = {
- let a = now(clock::MONOTONIC);
- sleep(1 * MILLISECOND);
- let b = now(clock::MONOTONIC);
- assert(compare(a, b) < 0);
- assert(compare(b, a) > 0);
- assert(compare(a, a) == 0);
+// Represents a unique interval of time between two instants.
+export type interval = struct {
+ start: instant,
+ end: instant,
};