hare

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

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:
Mdatetime/README | 2+-
Mdatetime/datetime.ha | 6+++---
Mscripts/gen-stdlib | 10+++++-----
Mstdlib.mk | 48++++++++++++++++++++++++------------------------
Dtime/olson/README | 5-----
Dtime/olson/olson.ha | 313-------------------------------------------------------------------------------
Atime/tzdb/README | 5+++++
Atime/tzdb/tzdb.ha | 313+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + }; +};