hare

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

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:
Mscripts/gen-stdlib | 6++++--
Mstdlib.mk | 12++++++++----
Atime/chrono/error.ha | 34++++++++++++++++++++++++++++++++++
Mtime/chrono/leapsec.ha | 35++++++++++++++++++++++++-----------
Mtime/chrono/timescale.ha | 13+++++++++++++
Mtime/chrono/tzdb.ha | 22++++------------------
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);