commit 0c583e8a21133a602e21379251f3b51d1c34d36c
parent 61b346df663cac76481f46041a86a099a83e76a9
Author: Byron Torres <b@torresjrjr.com>
Date: Tue, 1 Feb 2022 12:08:36 +0000
rename olson:: to tzdb::
Signed-off-by: Byron Torres <b@torresjrjr.com>
Diffstat:
8 files changed, 351 insertions(+), 351 deletions(-)
diff --git a/datetime/README b/datetime/README
@@ -24,7 +24,7 @@ modifying a [[datetime]]'s fields directly is highly discouraged. See
[[datetime]]s may be localized to different [[chrono::timezone]]s via
the [[in]] function. The "field" functions will evaluate the correct
values accordingly. You'll find a standard selection of world timezones
-in the [[time::olson]] module.
+in the [[time::tzdb]] module.
TODO: Settle on consistent naming and language for localisation.
"timezone" or "locality"?
diff --git a/datetime/datetime.ha b/datetime/datetime.ha
@@ -46,13 +46,13 @@ fn init() datetime = datetime {
// Creates a new datetime. When loc=void, defaults to chrono::local.
//
// // 0000 Jan 1st 00:00:00.000000000 +0000 UTC
-// datetime::new(chrono::UTC, 0);
+// datetime::new(time::chrono::UTC, 0);
//
// // 2038 Jan 19th 03:14:07.000000618 +0000 UTC
-// datetime::new(chrono::UTC, 0, 2038, 01, 19, 03, 14, 07, 618);
+// datetime::new(time::chrono::UTC, 0, 2038, 01, 19, 03, 14, 07, 618);
//
// // 2038 Jan 19th 02:00:00.000000000 +0100 Europe/Amsterdam
-// datetime::new(&olson::tz("Europe/Amsterdam"), 1 * time::HOUR,
+// datetime::new(&time::tzdb::tz("Europe/Amsterdam"), 1 * time::HOUR,
// 2038, 01, 19, 02);
//
// TODO: revise examples
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -1184,10 +1184,10 @@ time_chrono() {
gen_ssa time::chrono time
}
-time_olson() {
- gen_srcs time::olson \
- olson.ha
- gen_ssa time::olson endian errors fs io os path strings time time::chrono
+time_tzdb() {
+ gen_srcs time::tzdb \
+ tzdb.ha
+ gen_ssa time::tzdb endian errors fs io os path strings time time::chrono
}
types() {
@@ -1358,7 +1358,7 @@ strio
temp linux freebsd
time linux freebsd
time::chrono
-time::olson
+time::tzdb
types
unix linux freebsd
unix::hosts
diff --git a/stdlib.mk b/stdlib.mk
@@ -622,11 +622,11 @@ stdlib_deps_any+=$(stdlib_time_chrono_any)
stdlib_time_chrono_linux=$(stdlib_time_chrono_any)
stdlib_time_chrono_freebsd=$(stdlib_time_chrono_any)
-# gen_lib time::olson (any)
-stdlib_time_olson_any=$(HARECACHE)/time/olson/time_olson-any.o
-stdlib_deps_any+=$(stdlib_time_olson_any)
-stdlib_time_olson_linux=$(stdlib_time_olson_any)
-stdlib_time_olson_freebsd=$(stdlib_time_olson_any)
+# gen_lib time::tzdb (any)
+stdlib_time_tzdb_any=$(HARECACHE)/time/tzdb/time_tzdb-any.o
+stdlib_deps_any+=$(stdlib_time_tzdb_any)
+stdlib_time_tzdb_linux=$(stdlib_time_tzdb_any)
+stdlib_time_tzdb_freebsd=$(stdlib_time_tzdb_any)
# gen_lib types (any)
stdlib_types_any=$(HARECACHE)/types/types-any.o
@@ -1807,15 +1807,15 @@ $(HARECACHE)/time/chrono/time_chrono-any.ssa: $(stdlib_time_chrono_any_srcs) $(s
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntime::chrono \
-t$(HARECACHE)/time/chrono/time_chrono.td $(stdlib_time_chrono_any_srcs)
-# time::olson (+any)
-stdlib_time_olson_any_srcs= \
- $(STDLIB)/time/olson/olson.ha
+# time::tzdb (+any)
+stdlib_time_tzdb_any_srcs= \
+ $(STDLIB)/time/tzdb/tzdb.ha
-$(HARECACHE)/time/olson/time_olson-any.ssa: $(stdlib_time_olson_any_srcs) $(stdlib_rt) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_path_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_time_chrono_$(PLATFORM))
+$(HARECACHE)/time/tzdb/time_tzdb-any.ssa: $(stdlib_time_tzdb_any_srcs) $(stdlib_rt) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_path_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_time_chrono_$(PLATFORM))
@printf 'HAREC \t$@\n'
- @mkdir -p $(HARECACHE)/time/olson
- @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntime::olson \
- -t$(HARECACHE)/time/olson/time_olson.td $(stdlib_time_olson_any_srcs)
+ @mkdir -p $(HARECACHE)/time/tzdb
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntime::tzdb \
+ -t$(HARECACHE)/time/tzdb/time_tzdb.td $(stdlib_time_tzdb_any_srcs)
# types (+any)
stdlib_types_any_srcs= \
@@ -2586,11 +2586,11 @@ testlib_deps_any+=$(testlib_time_chrono_any)
testlib_time_chrono_linux=$(testlib_time_chrono_any)
testlib_time_chrono_freebsd=$(testlib_time_chrono_any)
-# gen_lib time::olson (any)
-testlib_time_olson_any=$(TESTCACHE)/time/olson/time_olson-any.o
-testlib_deps_any+=$(testlib_time_olson_any)
-testlib_time_olson_linux=$(testlib_time_olson_any)
-testlib_time_olson_freebsd=$(testlib_time_olson_any)
+# gen_lib time::tzdb (any)
+testlib_time_tzdb_any=$(TESTCACHE)/time/tzdb/time_tzdb-any.o
+testlib_deps_any+=$(testlib_time_tzdb_any)
+testlib_time_tzdb_linux=$(testlib_time_tzdb_any)
+testlib_time_tzdb_freebsd=$(testlib_time_tzdb_any)
# gen_lib types (any)
testlib_types_any=$(TESTCACHE)/types/types-any.o
@@ -3816,15 +3816,15 @@ $(TESTCACHE)/time/chrono/time_chrono-any.ssa: $(testlib_time_chrono_any_srcs) $(
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntime::chrono \
-t$(TESTCACHE)/time/chrono/time_chrono.td $(testlib_time_chrono_any_srcs)
-# time::olson (+any)
-testlib_time_olson_any_srcs= \
- $(STDLIB)/time/olson/olson.ha
+# time::tzdb (+any)
+testlib_time_tzdb_any_srcs= \
+ $(STDLIB)/time/tzdb/tzdb.ha
-$(TESTCACHE)/time/olson/time_olson-any.ssa: $(testlib_time_olson_any_srcs) $(testlib_rt) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_path_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_time_chrono_$(PLATFORM))
+$(TESTCACHE)/time/tzdb/time_tzdb-any.ssa: $(testlib_time_tzdb_any_srcs) $(testlib_rt) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_path_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_time_chrono_$(PLATFORM))
@printf 'HAREC \t$@\n'
- @mkdir -p $(TESTCACHE)/time/olson
- @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntime::olson \
- -t$(TESTCACHE)/time/olson/time_olson.td $(testlib_time_olson_any_srcs)
+ @mkdir -p $(TESTCACHE)/time/tzdb
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntime::tzdb \
+ -t$(TESTCACHE)/time/tzdb/time_tzdb.td $(testlib_time_tzdb_any_srcs)
# types (+any)
testlib_types_any_srcs= \
diff --git a/time/olson/README b/time/olson/README
@@ -1,5 +0,0 @@
-The time::olson module provides access to the IANA Time Zone Database.
-This module parses the timezone data normally installed on your system.
-The database is also known as zoneinfo, tzdata, or the Olson database.
-
-See https://www.iana.org/time-zones
diff --git a/time/olson/olson.ha b/time/olson/olson.ha
@@ -1,313 +0,0 @@
-use bufio;
-use endian;
-use errors;
-use fs;
-use io;
-use os;
-use path;
-use strings;
-use time;
-use time::chrono;
-
-// Some TZif data is invalid
-export type invalidtzif = !void;
-
-// Parses and retrieves a [[chrono::timezone]] from the system zoneinfo
-// database, or if applicable, from an internal selection of timezones. All
-// Olson timezones default to using the [[chrono::utc]] timescale and
-// [[chrono::EARTH_DAY]] daylength.
-//
-// TODO: Tidy up errors.
-//
-export fn tz(name: str) (chrono::timezone | errors::overflow | fs::error | io::error | invalidtzif) = {
- switch (name) {
- case "Local" =>
- return *chrono::LOCAL;
- case "UTC" =>
- return *chrono::UTC;
- case "TAI" =>
- return *chrono::TAI;
- case "MTC" =>
- return *chrono::MTC;
- case =>
- void;
- };
-
- // Try reading from a TZif file installed in on the system.
- //
- // TODO: try various prefixes for various OSs, try reading from
- // installed zip files, etc.
- const prefix = "/usr/share/zoneinfo/";
-
- // TODO: try names like "./nearby/tzif_file" or "/abs/path/tzif_file"?
- const filepath = path::init();
- path::add(&filepath, prefix, name)?;
- const fpath = path::string(&filepath);
- const file = os::open(fpath)?;
- defer io::close(file);
-
- static let buf: [os::BUFSIZ]u8 = [0...];
- const file = bufio::buffered(file, buf, []);
- const tz = parse_tzif(&file, chrono::timezone {
- name = name,
- timescale = &chrono::utc,
- daylength = chrono::EARTH_DAY,
- ...
- })?;
-
- return tz;
-};
-
-// Parses data in the TZif format, and returns the given timezone with the
-// fields "zones", "transitions", and "posix_extend" filled.
-//
-// See: https://datatracker.ietf.org/doc/html/rfc8536
-fn parse_tzif(
- h: io::handle,
- tz: chrono::timezone,
-) (chrono::timezone | invalidtzif | io::error) = {
- const buf1: [1]u8 = [0...];
- const buf4: [4]u8 = [0...];
- const buf8: [8]u8 = [0...];
- const buf15: [15]u8 = [0...];
-
- // test for magic "TZif"
- read(h, buf4)?;
- if (strings::fromutf8(buf4) != "TZif") {
- return invalidtzif;
- };
-
- // read version
- read(h, buf1)?;
- const version = switch (buf1[0]) {
- case 0 =>
- yield 1;
- case '2' =>
- yield 2;
- case '3' =>
- yield 3;
- case =>
- return invalidtzif;
- };
-
- // skip padding
- read(h, buf15)?;
-
- // read counts
- read(h, buf4)?; let isutcnt = endian::begetu32(buf4);
- read(h, buf4)?; let isstdcnt = endian::begetu32(buf4);
- read(h, buf4)?; let leapcnt = endian::begetu32(buf4);
- read(h, buf4)?; let timecnt = endian::begetu32(buf4);
- read(h, buf4)?; let typecnt = endian::begetu32(buf4);
- read(h, buf4)?; let charcnt = endian::begetu32(buf4);
-
- let is64 = false;
- if (version > 1) {
- is64 = true;
-
- // skip to the version 2 data
- const skip = (
- // size of version 1 data block
- timecnt * 4
- + timecnt
- + typecnt * 6
- + charcnt
- + leapcnt * 8
- + isstdcnt
- + isutcnt
- // size of version 2 header
- + 20
- );
- for (let i = 0z; i < skip; i += 1) {
- read(h, buf1)?;
- };
-
- // read version 2 counts
- read(h, buf4)?; isutcnt = endian::begetu32(buf4);
- read(h, buf4)?; isstdcnt = endian::begetu32(buf4);
- read(h, buf4)?; leapcnt = endian::begetu32(buf4);
- read(h, buf4)?; timecnt = endian::begetu32(buf4);
- read(h, buf4)?; typecnt = endian::begetu32(buf4);
- read(h, buf4)?; charcnt = endian::begetu32(buf4);
- };
-
- if (typecnt == 0 || charcnt == 0) {
- return invalidtzif;
- };
-
- if (isutcnt != 0 && isutcnt != typecnt) {
- return invalidtzif;
- };
-
- if (isstdcnt != 0 && isstdcnt != typecnt) {
- return invalidtzif;
- };
-
- // TODO: if and how to handle? check Olson's tz code for behaviour.
- if (isutcnt != 0 && isstdcnt != 0) {
- void;
- };
-
- const timesz = if (is64) 8 else 4;
-
- // read data
-
- const transition_times: []i64 = [];
- if (is64) {
- readitems8(h, &transition_times, timecnt);
- } else {
- readitems4(h, &transition_times, timecnt);
- };
-
- const zone_indicies: []u8 = [];
- readbytes(h, &zone_indicies, timecnt);
-
- const zonedata: []u8 = [];
- readbytes(h, &zonedata, typecnt * 6);
-
- const abbrdata: []u8 = [];
- readbytes(h, &abbrdata, charcnt);
-
- const leapdata: []u8 = [];
- readbytes(h, &leapdata, leapcnt * (timesz: u32 + 4));
-
- const stdwalldata: []u8 = [];
- readbytes(h, &stdwalldata, isstdcnt);
-
- const normlocaldata: []u8 = [];
- readbytes(h, &normlocaldata, isutcnt);
-
- // read footer
-
- let footerdata: []u8 = [];
- read(h, buf1)?;
- if (buf1[0] != 0x0A) { // '\n' newline
- return invalidtzif;
- };
- for (let start = true; true; start = false) {
- read(h, buf1)?;
- if (buf1[0] == 0x0A) { // '\n' newline
- break;
- };
- if (buf1[0] == 0x0) { // cannot contain NUL
- return invalidtzif;
- };
- append(footerdata, buf1...);
- };
- const posix_extend = strings::fromutf8(footerdata);
-
- // assemble structured data
-
- // assemble zones
- let zones: []chrono::zone = [];
- for (let i = 0z; i < typecnt; i += 1) {
- const idx = i * 6;
- const zone = chrono::zone { ... };
-
- // offset
- const zoffset = endian::begetu32(zonedata[idx..idx + 4]): i32;
- if (zoffset == -2147483648) { // -2^31
- return invalidtzif;
- };
- zone.zoffset = zoffset * time::SECOND;
-
- // daylight saving time indicator
- zone.dst = switch (zonedata[idx + 4]) {
- case 1u8 =>
- yield true;
- case 0u8 =>
- yield false;
- case =>
- return invalidtzif;
- };
-
- // abbreviation
- const abbridx = zonedata[idx + 5];
- if (abbridx < 0 || abbridx > (charcnt - 1)) {
- return invalidtzif;
- };
- let bytes: []u8 = [];
- for (let j = abbridx; j < len(abbrdata); j += 1) {
- if (abbrdata[j] == 0x0) {
- bytes = abbrdata[abbridx..j];
- break;
- };
- };
- if (len(bytes) == 0) { // no NUL encountered
- return invalidtzif;
- };
- const abbr = strings::fromutf8(bytes);
- zone.abbr = abbr;
-
- append(zones, zone);
- };
-
- // assemble transitions
- let transitions: []chrono::transition = [];
- for (let i = 0z; i < timecnt; i += 1) {
- const zoneindex = zone_indicies[i]: int;
- if (zoneindex < 0 || zoneindex > (typecnt: int - 1)) {
- return invalidtzif;
- };
-
- const tx = chrono::transition {
- when = time::instant {
- sec = transition_times[i],
- ...
- },
- zoneindex = zoneindex,
- };
-
- // stdwalldata and normlocaldata have been omitted,
- // until they show their utility.
-
- append(transitions, tx);
- };
-
- // commit and return data
- tz.zones = zones;
- tz.transitions = transitions;
- tz.posix_extend = posix_extend;
- return tz;
-};
-
-// Error wrapper for [[io::read]]
-fn read(h: io::handle, buf: []u8) (void | invalidtzif | io::error) = {
- match (io::read(h, buf)) {
- case let err: io::error =>
- return err;
- case io::EOF =>
- return invalidtzif;
- case let sz: size =>
- if (sz != len(buf)) {
- return invalidtzif;
- };
- };
-};
-
-fn readbytes(h: io::handle, items: *[]u8, n: size) void = {
- const buf: [1]u8 = [0];
- for (let i = 0z; i < n; i += 1) {
- read(h, buf)?;
- const it = buf[0];
- append(items, it);
- };
-};
-
-fn readitems8(h: io::handle, items: *[]i64, n: size) void = {
- const buf: [8]u8 = [0...];
- for (let i = 0z; i < n; i += 1) {
- read(h, buf)?;
- const it = endian::begetu64(buf): i64;
- append(items, it);
- };
-};
-
-fn readitems4(h: io::handle, items: *[]i64, n: size) void = {
- const buf: [4]u8 = [0...];
- for (let i = 0z; i < n; i += 1) {
- read(h, buf)?;
- const it = endian::begetu32(buf): i64;
- append(items, it);
- };
-};
diff --git a/time/tzdb/README b/time/tzdb/README
@@ -0,0 +1,5 @@
+The time::tzdb module provides access to the IANA Time Zone Database.
+This module parses the timezone data normally installed on your system.
+The database is also known as zoneinfo, tzdata, or the Olson database.
+
+See https://www.iana.org/time-zones
diff --git a/time/tzdb/tzdb.ha b/time/tzdb/tzdb.ha
@@ -0,0 +1,313 @@
+use bufio;
+use endian;
+use errors;
+use fs;
+use io;
+use os;
+use path;
+use strings;
+use time;
+use time::chrono;
+
+// Some TZif data is invalid
+export type invalidtzif = !void;
+
+// Parses and retrieves a [[chrono::timezone]] from the system zoneinfo
+// database, or if applicable, from an internal selection of timezones.
+// All tzdb timezones default to the [[chrono::utc]] timescale and
+// [[chrono::EARTH_DAY]] daylength.
+//
+// TODO: Tidy up errors.
+//
+export fn tz(name: str) (chrono::timezone | errors::overflow | fs::error | io::error | invalidtzif) = {
+ switch (name) {
+ case "Local" =>
+ return *chrono::LOCAL;
+ case "UTC" =>
+ return *chrono::UTC;
+ case "TAI" =>
+ return *chrono::TAI;
+ case "MTC" =>
+ return *chrono::MTC;
+ case =>
+ void;
+ };
+
+ // Try reading from a TZif file installed in on the system.
+ //
+ // TODO: try various prefixes for various OSs, try reading from
+ // installed zip files, etc.
+ const prefix = "/usr/share/zoneinfo/";
+
+ // TODO: try names like "./nearby/tzif_file" or "/abs/path/tzif_file"?
+ const filepath = path::init();
+ path::add(&filepath, prefix, name)?;
+ const fpath = path::string(&filepath);
+ const file = os::open(fpath)?;
+ defer io::close(file);
+
+ static let buf: [os::BUFSIZ]u8 = [0...];
+ const file = bufio::buffered(file, buf, []);
+ const tz = parse_tzif(&file, chrono::timezone {
+ name = name,
+ timescale = &chrono::utc,
+ daylength = chrono::EARTH_DAY,
+ ...
+ })?;
+
+ return tz;
+};
+
+// Parses data in the TZif format, and returns the given timezone with the
+// fields "zones", "transitions", and "posix_extend" filled.
+//
+// See: https://datatracker.ietf.org/doc/html/rfc8536
+fn parse_tzif(
+ h: io::handle,
+ tz: chrono::timezone,
+) (chrono::timezone | invalidtzif | io::error) = {
+ const buf1: [1]u8 = [0...];
+ const buf4: [4]u8 = [0...];
+ const buf8: [8]u8 = [0...];
+ const buf15: [15]u8 = [0...];
+
+ // test for magic "TZif"
+ read(h, buf4)?;
+ if (strings::fromutf8(buf4) != "TZif") {
+ return invalidtzif;
+ };
+
+ // read version
+ read(h, buf1)?;
+ const version = switch (buf1[0]) {
+ case 0 =>
+ yield 1;
+ case '2' =>
+ yield 2;
+ case '3' =>
+ yield 3;
+ case =>
+ return invalidtzif;
+ };
+
+ // skip padding
+ read(h, buf15)?;
+
+ // read counts
+ read(h, buf4)?; let isutcnt = endian::begetu32(buf4);
+ read(h, buf4)?; let isstdcnt = endian::begetu32(buf4);
+ read(h, buf4)?; let leapcnt = endian::begetu32(buf4);
+ read(h, buf4)?; let timecnt = endian::begetu32(buf4);
+ read(h, buf4)?; let typecnt = endian::begetu32(buf4);
+ read(h, buf4)?; let charcnt = endian::begetu32(buf4);
+
+ let is64 = false;
+ if (version > 1) {
+ is64 = true;
+
+ // skip to the version 2 data
+ const skip = (
+ // size of version 1 data block
+ timecnt * 4
+ + timecnt
+ + typecnt * 6
+ + charcnt
+ + leapcnt * 8
+ + isstdcnt
+ + isutcnt
+ // size of version 2 header
+ + 20
+ );
+ for (let i = 0z; i < skip; i += 1) {
+ read(h, buf1)?;
+ };
+
+ // read version 2 counts
+ read(h, buf4)?; isutcnt = endian::begetu32(buf4);
+ read(h, buf4)?; isstdcnt = endian::begetu32(buf4);
+ read(h, buf4)?; leapcnt = endian::begetu32(buf4);
+ read(h, buf4)?; timecnt = endian::begetu32(buf4);
+ read(h, buf4)?; typecnt = endian::begetu32(buf4);
+ read(h, buf4)?; charcnt = endian::begetu32(buf4);
+ };
+
+ if (typecnt == 0 || charcnt == 0) {
+ return invalidtzif;
+ };
+
+ if (isutcnt != 0 && isutcnt != typecnt) {
+ return invalidtzif;
+ };
+
+ if (isstdcnt != 0 && isstdcnt != typecnt) {
+ return invalidtzif;
+ };
+
+ // TODO: if and how to handle? check Olson's tz code for behaviour.
+ if (isutcnt != 0 && isstdcnt != 0) {
+ void;
+ };
+
+ const timesz = if (is64) 8 else 4;
+
+ // read data
+
+ const transition_times: []i64 = [];
+ if (is64) {
+ readitems8(h, &transition_times, timecnt);
+ } else {
+ readitems4(h, &transition_times, timecnt);
+ };
+
+ const zone_indicies: []u8 = [];
+ readbytes(h, &zone_indicies, timecnt);
+
+ const zonedata: []u8 = [];
+ readbytes(h, &zonedata, typecnt * 6);
+
+ const abbrdata: []u8 = [];
+ readbytes(h, &abbrdata, charcnt);
+
+ const leapdata: []u8 = [];
+ readbytes(h, &leapdata, leapcnt * (timesz: u32 + 4));
+
+ const stdwalldata: []u8 = [];
+ readbytes(h, &stdwalldata, isstdcnt);
+
+ const normlocaldata: []u8 = [];
+ readbytes(h, &normlocaldata, isutcnt);
+
+ // read footer
+
+ let footerdata: []u8 = [];
+ read(h, buf1)?;
+ if (buf1[0] != 0x0A) { // '\n' newline
+ return invalidtzif;
+ };
+ for (let start = true; true; start = false) {
+ read(h, buf1)?;
+ if (buf1[0] == 0x0A) { // '\n' newline
+ break;
+ };
+ if (buf1[0] == 0x0) { // cannot contain NUL
+ return invalidtzif;
+ };
+ append(footerdata, buf1...);
+ };
+ const posix_extend = strings::fromutf8(footerdata);
+
+ // assemble structured data
+
+ // assemble zones
+ let zones: []chrono::zone = [];
+ for (let i = 0z; i < typecnt; i += 1) {
+ const idx = i * 6;
+ const zone = chrono::zone { ... };
+
+ // offset
+ const zoffset = endian::begetu32(zonedata[idx..idx + 4]): i32;
+ if (zoffset == -2147483648) { // -2^31
+ return invalidtzif;
+ };
+ zone.zoffset = zoffset * time::SECOND;
+
+ // daylight saving time indicator
+ zone.dst = switch (zonedata[idx + 4]) {
+ case 1u8 =>
+ yield true;
+ case 0u8 =>
+ yield false;
+ case =>
+ return invalidtzif;
+ };
+
+ // abbreviation
+ const abbridx = zonedata[idx + 5];
+ if (abbridx < 0 || abbridx > (charcnt - 1)) {
+ return invalidtzif;
+ };
+ let bytes: []u8 = [];
+ for (let j = abbridx; j < len(abbrdata); j += 1) {
+ if (abbrdata[j] == 0x0) {
+ bytes = abbrdata[abbridx..j];
+ break;
+ };
+ };
+ if (len(bytes) == 0) { // no NUL encountered
+ return invalidtzif;
+ };
+ const abbr = strings::fromutf8(bytes);
+ zone.abbr = abbr;
+
+ append(zones, zone);
+ };
+
+ // assemble transitions
+ let transitions: []chrono::transition = [];
+ for (let i = 0z; i < timecnt; i += 1) {
+ const zoneindex = zone_indicies[i]: int;
+ if (zoneindex < 0 || zoneindex > (typecnt: int - 1)) {
+ return invalidtzif;
+ };
+
+ const tx = chrono::transition {
+ when = time::instant {
+ sec = transition_times[i],
+ ...
+ },
+ zoneindex = zoneindex,
+ };
+
+ // stdwalldata and normlocaldata have been omitted,
+ // until they show their utility.
+
+ append(transitions, tx);
+ };
+
+ // commit and return data
+ tz.zones = zones;
+ tz.transitions = transitions;
+ tz.posix_extend = posix_extend;
+ return tz;
+};
+
+// Error wrapper for [[io::read]]
+fn read(h: io::handle, buf: []u8) (void | invalidtzif | io::error) = {
+ match (io::read(h, buf)) {
+ case let err: io::error =>
+ return err;
+ case io::EOF =>
+ return invalidtzif;
+ case let sz: size =>
+ if (sz != len(buf)) {
+ return invalidtzif;
+ };
+ };
+};
+
+fn readbytes(h: io::handle, items: *[]u8, n: size) void = {
+ const buf: [1]u8 = [0];
+ for (let i = 0z; i < n; i += 1) {
+ read(h, buf)?;
+ const it = buf[0];
+ append(items, it);
+ };
+};
+
+fn readitems8(h: io::handle, items: *[]i64, n: size) void = {
+ const buf: [8]u8 = [0...];
+ for (let i = 0z; i < n; i += 1) {
+ read(h, buf)?;
+ const it = endian::begetu64(buf): i64;
+ append(items, it);
+ };
+};
+
+fn readitems4(h: io::handle, items: *[]i64, n: size) void = {
+ const buf: [4]u8 = [0...];
+ for (let i = 0z; i < n; i += 1) {
+ read(h, buf)?;
+ const it = endian::begetu32(buf): i64;
+ append(items, it);
+ };
+};