commit f86f82297ea85f91ba16d6d1e33d1a52a0e0f206
parent 3dd574837fff80fe080d3d55016261f8f72cd689
Author: Byron Torres <b@torresjrjr.com>
Date: Tue, 26 Jul 2022 18:24:01 +0100
time::chrono: handle errors, utc leapsecs init
Error types are now consolidated and organised.
[[time::chrono::utc]] aborts when to_tai() & from_tai() are called with
uninitialized UTC/TAI leap second data.
Fixes: https://todo.sr.ht/~sircmpwn/hare/720
References: https://todo.sr.ht/~sircmpwn/hare/642
Signed-off-by: Byron Torres <b@torresjrjr.com>
Diffstat:
6 files changed, 87 insertions(+), 35 deletions(-)
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -1305,21 +1305,23 @@ time_chrono() {
gen_srcs -plinux time::chrono \
+linux.ha \
chronology.ha \
+ error.ha \
leapsec.ha \
timescale.ha \
timezone.ha \
tzdb.ha
gen_ssa -plinux time::chrono \
- bufio bytes encoding::utf8 endian errors fs io os strconv strings time path
+ bufio bytes encoding::utf8 endian errors fmt fs io os strconv strings time path
gen_srcs -pfreebsd time::chrono \
+freebsd.ha \
chronology.ha \
+ error.ha \
leapsec.ha \
timescale.ha \
timezone.ha \
tzdb.ha
gen_ssa -pfreebsd time::chrono \
- bufio bytes encoding::utf8 endian errors fs io os strconv strings time path
+ bufio bytes encoding::utf8 endian errors fmt fs io os strconv strings time path
}
types() {
diff --git a/stdlib.mk b/stdlib.mk
@@ -1973,12 +1973,13 @@ $(HARECACHE)/time/time-freebsd.ssa: $(stdlib_time_freebsd_srcs) $(stdlib_rt) $(s
stdlib_time_chrono_linux_srcs = \
$(STDLIB)/time/chrono/+linux.ha \
$(STDLIB)/time/chrono/chronology.ha \
+ $(STDLIB)/time/chrono/error.ha \
$(STDLIB)/time/chrono/leapsec.ha \
$(STDLIB)/time/chrono/timescale.ha \
$(STDLIB)/time/chrono/timezone.ha \
$(STDLIB)/time/chrono/tzdb.ha
-$(HARECACHE)/time/chrono/time_chrono-linux.ssa: $(stdlib_time_chrono_linux_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_utf8_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_path_$(PLATFORM))
+$(HARECACHE)/time/chrono/time_chrono-linux.ssa: $(stdlib_time_chrono_linux_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_utf8_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_path_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/time/chrono
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntime::chrono \
@@ -1988,12 +1989,13 @@ $(HARECACHE)/time/chrono/time_chrono-linux.ssa: $(stdlib_time_chrono_linux_srcs)
stdlib_time_chrono_freebsd_srcs = \
$(STDLIB)/time/chrono/+freebsd.ha \
$(STDLIB)/time/chrono/chronology.ha \
+ $(STDLIB)/time/chrono/error.ha \
$(STDLIB)/time/chrono/leapsec.ha \
$(STDLIB)/time/chrono/timescale.ha \
$(STDLIB)/time/chrono/timezone.ha \
$(STDLIB)/time/chrono/tzdb.ha
-$(HARECACHE)/time/chrono/time_chrono-freebsd.ssa: $(stdlib_time_chrono_freebsd_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_utf8_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_path_$(PLATFORM))
+$(HARECACHE)/time/chrono/time_chrono-freebsd.ssa: $(stdlib_time_chrono_freebsd_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_utf8_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_path_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/time/chrono
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntime::chrono \
@@ -4200,12 +4202,13 @@ $(TESTCACHE)/time/time-freebsd.ssa: $(testlib_time_freebsd_srcs) $(testlib_rt) $
testlib_time_chrono_linux_srcs = \
$(STDLIB)/time/chrono/+linux.ha \
$(STDLIB)/time/chrono/chronology.ha \
+ $(STDLIB)/time/chrono/error.ha \
$(STDLIB)/time/chrono/leapsec.ha \
$(STDLIB)/time/chrono/timescale.ha \
$(STDLIB)/time/chrono/timezone.ha \
$(STDLIB)/time/chrono/tzdb.ha
-$(TESTCACHE)/time/chrono/time_chrono-linux.ssa: $(testlib_time_chrono_linux_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_utf8_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_path_$(PLATFORM))
+$(TESTCACHE)/time/chrono/time_chrono-linux.ssa: $(testlib_time_chrono_linux_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_utf8_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_path_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/time/chrono
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntime::chrono \
@@ -4215,12 +4218,13 @@ $(TESTCACHE)/time/chrono/time_chrono-linux.ssa: $(testlib_time_chrono_linux_srcs
testlib_time_chrono_freebsd_srcs = \
$(STDLIB)/time/chrono/+freebsd.ha \
$(STDLIB)/time/chrono/chronology.ha \
+ $(STDLIB)/time/chrono/error.ha \
$(STDLIB)/time/chrono/leapsec.ha \
$(STDLIB)/time/chrono/timescale.ha \
$(STDLIB)/time/chrono/timezone.ha \
$(STDLIB)/time/chrono/tzdb.ha
-$(TESTCACHE)/time/chrono/time_chrono-freebsd.ssa: $(testlib_time_chrono_freebsd_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_utf8_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_path_$(PLATFORM))
+$(TESTCACHE)/time/chrono/time_chrono-freebsd.ssa: $(testlib_time_chrono_freebsd_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_utf8_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_path_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/time/chrono
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntime::chrono \
diff --git a/time/chrono/error.ha b/time/chrono/error.ha
@@ -0,0 +1,34 @@
+// License: MPL-2.0
+// (c) 2022 Byron Torres <b@torresjrjr.com>
+use encoding::utf8;
+use fmt;
+use fs;
+use io;
+
+// All possible errors returned from [[time::chrono]].
+export type error = !(invalid | tzdberror | invalidtzif);
+
+// Converts an [[error]] into a human-friendly string.
+export fn strerror(err: error) const str = {
+ match (err) {
+ case invalid =>
+ return "Invalid moment";
+ case invalidtzif =>
+ return "Invalid TZif data";
+ case let err: tzdberror =>
+ match (err) {
+ case let err: fs::error =>
+ return fmt::asprintf(
+ "Timezone database error: {}",
+ fs::strerror(err),
+ );
+ case let err: io::error =>
+ return fmt::asprintf(
+ "Timezone database error: {}",
+ io::strerror(err),
+ );
+ case invalidtzif =>
+ return "Timezone database error: Invalid TZif data";
+ };
+ };
+};
diff --git a/time/chrono/leapsec.ha b/time/chrono/leapsec.ha
@@ -27,34 +27,47 @@ use strings;
// design also inhibits our ambitions for dealing with multiple, dynamic
// timescales. Therefore, we have decided to take an alternative approach.
+// Error initializing the [[utc]] [[timescale]].
+type utciniterror = !(fs::error | io::error | encoding::utf8::invalid);
+
// The number of seconds between the years 1900 and 1970. This number is
// deliberately hypothetical since timekeeping before atomic clocks was not
// accurate enough to account for small changes in time.
export def SECS_1900_1970: i64 = 2208988800;
-// The filepath of the system's leap-seconds.list file.
+// The filepath of the system's "leap-seconds.list" file, which contains UTC/TAI
+// leap second data.
export def UTC_LEAPSECS_FILE: str = "/usr/share/zoneinfo/leap-seconds.list";
-// UTC timestamps and their offsets from TAI, sourced from the system's
-// leap-seconds.list file.
+// UTC/TAI leap second data; UTC timestamps and their offsets from TAI.
+// Sourced from [[UTC_LEAPSECS_FILE]].
let utc_leapsecs: [](i64, i64) = [];
-@init fn init_utc_leapsecs() void = {
+let utc_isinitialized: bool = false;
+
+// Initializes the [[utc]] [[timescale]].
+@init fn init_utc() void = {
os::init_cwd();
- const file = match (os::open(UTC_LEAPSECS_FILE)) {
- case let file: io::file =>
- yield file;
- case fs::error =>
+ match (init_utc_leapsecs()) {
+ case void =>
+ utc_isinitialized = true;
+ case =>
return;
};
+};
+
+fn init_utc_leapsecs() (void | utciniterror) = {
+ const file = os::open(UTC_LEAPSECS_FILE)?;
defer io::close(file)!;
- read_utc_leapsecs_file(file, &utc_leapsecs)!;
+ parse_utc_leapsecs(file, &utc_leapsecs)?;
};
-fn read_utc_leapsecs_file(
+// Parse UTC/TAI leap second data from [[UTC_LEAPSECS_FILE]].
+// See file for format details.
+fn parse_utc_leapsecs(
h: io::handle,
leapsecs: *[](i64, i64),
-) (void | io::error | encoding::utf8::invalid) = {
+) (void | encoding::utf8::invalid | io::error) = {
for (true) {
const line = match (bufio::scanline(h)) {
case let err: io::error =>
diff --git a/time/chrono/timescale.ha b/time/chrono/timescale.ha
@@ -42,6 +42,11 @@ fn conv_tai_tai(i: time::instant) (time::instant | time::error) = {
// Used as the basis of civil timekeeping.
// Based on TAI; time-dependent offset.
// Discontinuous (has leap seconds).
+//
+// During a program's initialization, this timescale initializes by loading its
+// UTC/TAI leap second data from [[UTC_LEAPSECS_FILE]]; otherwise, fails
+// silently. If failed, any attempt to consult UTC leapsec data (like calling
+// utc.to_tai(), utc.from_tai()) causes an abort. This includes [[chrono::in]].
export const utc: timescale = timescale {
name = "Coordinated Universal Time",
abbr = "UTC",
@@ -50,6 +55,10 @@ export const utc: timescale = timescale {
};
fn conv_tai_utc(a: time::instant) (time::instant | time::error) = {
+ if (!utc_isinitialized) {
+ abort("utc timescale uninitialized");
+ };
+
const idx = lookup_leaps(&utc_leapsecs, time::unix(a));
const ofst = utc_leapsecs[idx].1;
@@ -65,6 +74,10 @@ fn conv_tai_utc(a: time::instant) (time::instant | time::error) = {
};
fn conv_utc_tai(a: time::instant) (time::instant | time::error) = {
+ if (!utc_isinitialized) {
+ abort("utc timescale uninitialized");
+ };
+
const idx = lookup_leaps(&utc_leapsecs, time::unix(a));
const ofst = utc_leapsecs[idx].1;
diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha
@@ -12,30 +12,16 @@ use path;
use strings;
use time;
+// Error concerning the Timezone database.
+export type tzdberror = !(invalidtzif | fs::error | io::error);
+
// Invalid TZif data.
export type invalidtzif = !void;
-// Possible errors returned from [[tz]].
-export type error = !(fs::error | io::error | invalidtzif);
-// TODO: Move to an appropriate file, and add other chrono error types.
-
-// Converts an [[error]] to a human-friendly representation.
-export fn strerror(err: error) const str = {
- match (err) {
- case invalidtzif =>
- return "Invalid TZif data";
- case let err: fs::error =>
- return fs::strerror(err);
- case let err: io::error =>
- return io::strerror(err);
- };
-};
-
// Finds and loads a [[timezone]] from the system's Timezone database, normally
// located at /usr/share/zoneinfo. All timezones provided default to the [[utc]]
// [[timescale]] and [[EARTH_DAY]] day-length.
-export fn tz(name: str) (timezone | fs::error | io::error | invalidtzif) = {
-// TODO: Consolidate errors (use chrono::error?).
+export fn tz(name: str) (timezone | tzdberror) = {
const filepath = path::init();
path::add(&filepath, ZONEINFO_PREFIX, name)!;
const fpath = path::string(&filepath);