hare

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

commit 375e360eeb6356a80bbf7f0ab06c36d1e8191d57
parent 41f12dfccfe58105b9ecfa8383ed08c21448fa62
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 13 Apr 2022 15:41:12 +0200

Merge time::tzdb into time::chrono

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mdatetime/README | 2+-
Mdatetime/datetime.ha | 2+-
Mscripts/gen-stdlib | 11+++--------
Mstdlib.mk | 42++++++------------------------------------
Mtime/chrono/timezone.ha | 45++++++++++++++++++++++++++++++++++++++++-----
Atime/chrono/tzdb.ha | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtime/tzdb/README | 5-----
Dtime/tzdb/tzdb.ha | 331-------------------------------------------------------------------------------
8 files changed, 367 insertions(+), 387 deletions(-)

diff --git a/datetime/README b/datetime/README @@ -22,7 +22,7 @@ use of the [[builder]] interface is recommended. [[datetime]]s may be localized to different [[time::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::tzdb]] module. +[[time::chrono]] module. To convert datetimes to and from strings, use [[parse]] and [[format]]. diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -55,7 +55,7 @@ fn init() datetime = datetime { // 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(&time::tzdb::tz("Europe/Amsterdam"), 1 * time::HOUR, +// datetime::new(&time::chrono::tz("Europe/Amsterdam"), 1 * time::HOUR, // 2038, 01, 19, 02); // // 'offs' is the zone offset from the normal timezone (in most cases, UTC). For diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -1182,14 +1182,10 @@ time_chrono() { chronology.ha \ leapsec.ha \ timescale.ha \ - timezone.ha - gen_ssa time::chrono bufio fmt io os strconv strings time -} - -time_tzdb() { - gen_srcs time::tzdb \ + timezone.ha \ tzdb.ha - gen_ssa time::tzdb endian errors fs io os path strings time time::chrono + gen_ssa time::chrono bufio endian errors fs fmt io os strconv \ + strings time path } types() { @@ -1360,7 +1356,6 @@ strio temp linux freebsd time linux freebsd time::chrono -time::tzdb types unix linux freebsd unix::hosts diff --git a/stdlib.mk b/stdlib.mk @@ -622,12 +622,6 @@ 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::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 stdlib_deps_any+=$(stdlib_types_any) @@ -1801,24 +1795,15 @@ stdlib_time_chrono_any_srcs= \ $(STDLIB)/time/chrono/chronology.ha \ $(STDLIB)/time/chrono/leapsec.ha \ $(STDLIB)/time/chrono/timescale.ha \ - $(STDLIB)/time/chrono/timezone.ha + $(STDLIB)/time/chrono/timezone.ha \ + $(STDLIB)/time/chrono/tzdb.ha -$(HARECACHE)/time/chrono/time_chrono-any.ssa: $(stdlib_time_chrono_any_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) +$(HARECACHE)/time/chrono/time_chrono-any.ssa: $(stdlib_time_chrono_any_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_fmt_$(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 \ -t$(HARECACHE)/time/chrono/time_chrono.td $(stdlib_time_chrono_any_srcs) -# time::tzdb (+any) -stdlib_time_tzdb_any_srcs= \ - $(STDLIB)/time/tzdb/tzdb.ha - -$(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/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= \ $(STDLIB)/types/limits.ha \ @@ -2588,12 +2573,6 @@ 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::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 testlib_deps_any+=$(testlib_types_any) @@ -3812,24 +3791,15 @@ testlib_time_chrono_any_srcs= \ $(STDLIB)/time/chrono/chronology.ha \ $(STDLIB)/time/chrono/leapsec.ha \ $(STDLIB)/time/chrono/timescale.ha \ - $(STDLIB)/time/chrono/timezone.ha + $(STDLIB)/time/chrono/timezone.ha \ + $(STDLIB)/time/chrono/tzdb.ha -$(TESTCACHE)/time/chrono/time_chrono-any.ssa: $(testlib_time_chrono_any_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_time_$(PLATFORM)) +$(TESTCACHE)/time/chrono/time_chrono-any.ssa: $(testlib_time_chrono_any_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_fmt_$(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 \ -t$(TESTCACHE)/time/chrono/time_chrono.td $(testlib_time_chrono_any_srcs) -# time::tzdb (+any) -testlib_time_tzdb_any_srcs= \ - $(STDLIB)/time/tzdb/tzdb.ha - -$(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/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= \ $(STDLIB)/types/limits.ha \ diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha @@ -1,3 +1,7 @@ +use bufio; +use io; +use os; +use path; use time; // The virtual region a moment is interpreted in @@ -160,18 +164,49 @@ export fn fixedzone(ts: *timescale, daylen: time::duration, z: zone) timezone = // The system's local timezone, set during initialisation export const LOCAL: locality = &TZ_local; -// TODO: set time::chrono::LOCAL to a correct timezone @init fn set_local_timezone() void = { - return; + match (os::getenv("TZ")) { + case let zone: str => + TZ_local = match (tz(zone)) { + case let tz: timezone => + yield tz; + case => + return; + }; + case void => + // TODO: Update this path on +linux et al + const file = match (os::open("/etc/localtime")) { + case let file: io::file => + yield file; + case => + return; + }; + defer io::close(file); + + static let buf: [os::BUFSIZ]u8 = [0...]; + const file = bufio::buffered(file, buf, []); + TZ_local = match (parse_tzif(&file, timezone { + name = "Local time", + timescale = &utc, + daylength = EARTH_DAY, + ... + })) { + case let tz: timezone => + yield tz; + case => + return; + }; + }; }; -const TZ_local: timezone = timezone { - name = "Local Time", + +let TZ_local: timezone = timezone { + name = "Local time", timescale = &utc, daylength = EARTH_DAY, zones = [ zone { zoffset = 0 * time::SECOND, - name = "Local Time", + name = "Local time", abbr = "", dst = false, }, diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha @@ -0,0 +1,316 @@ +use bufio; +use endian; +use errors; +use fs; +use io; +use os; +use path; +use strings; +use time; + +// Some TZif data is invalid +export type invalidtzif = !void; + +// Possible errors returned from [[tz]]. +export type error = !(fs::error | io::error | invalidtzif); + +// Converts an [[error]] to a human-friendly representation. +export fn strerror(err: error) const str = { + match (err) { + case invalidtzif => + return "Invalid TZif data in time zone"; + case let err: fs::error => + return fs::strerror(err); + case let err: io::error => + return io::strerror(err); + }; +}; + +// 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. +export fn tz(name: str) (timezone | fs::error | io::error | invalidtzif) = { + // TODO: Move this path to +linux et al + const prefix = "/usr/share/zoneinfo/"; + + 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, timezone { + name = name, + timescale = &utc, + daylength = 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: timezone, +) (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" + mustread(h, buf4)?; + if (strings::fromutf8(buf4) != "TZif") { + return invalidtzif; + }; + + // read version + mustread(h, buf1)?; + const version = switch (buf1[0]) { + case 0 => + yield 1; + case '2' => + yield 2; + case '3' => + yield 3; + case => + return invalidtzif; + }; + + // skip padding + mustread(h, buf15)?; + + // read counts + mustread(h, buf4)?; let isutcnt = endian::begetu32(buf4); + mustread(h, buf4)?; let isstdcnt = endian::begetu32(buf4); + mustread(h, buf4)?; let leapcnt = endian::begetu32(buf4); + mustread(h, buf4)?; let timecnt = endian::begetu32(buf4); + mustread(h, buf4)?; let typecnt = endian::begetu32(buf4); + mustread(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) { + mustread(h, buf1)?; + }; + + // read version 2 counts + mustread(h, buf4)?; isutcnt = endian::begetu32(buf4); + mustread(h, buf4)?; isstdcnt = endian::begetu32(buf4); + mustread(h, buf4)?; leapcnt = endian::begetu32(buf4); + mustread(h, buf4)?; timecnt = endian::begetu32(buf4); + mustread(h, buf4)?; typecnt = endian::begetu32(buf4); + mustread(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 = []; + mustread(h, buf1)?; + if (buf1[0] != 0x0A) { // '\n' newline + return invalidtzif; + }; + for (let start = true; true; start = false) { + mustread(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: []zone = []; + for (let i = 0z; i < typecnt; i += 1) { + const idx = i * 6; + const zone = 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: []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 = 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; +}; + +fn mustread(h: io::handle, buf: []u8) (void | invalidtzif | io::error) = { + match (io::readall(h, buf)) { + case let err: io::error => + return err; + case io::EOF => + return invalidtzif; + case size => + return; + }; +}; + +fn readbytes( + h: io::handle, + items: *[]u8, + n: size, +) (void | invalidtzif | io::error) = { + const buf: [1]u8 = [0]; + for (let i = 0z; i < n; i += 1) { + mustread(h, buf)?; + const it = buf[0]; + append(items, it); + }; +}; + +fn readitems8( + h: io::handle, + items: *[]i64, + n: size, +) (void | invalidtzif | io::error) = { + const buf: [8]u8 = [0...]; + for (let i = 0z; i < n; i += 1) { + mustread(h, buf)?; + const it = endian::begetu64(buf): i64; + append(items, it); + }; +}; + +fn readitems4( + h: io::handle, + items: *[]i64, + n: size, +) (void | invalidtzif | io::error) = { + const buf: [4]u8 = [0...]; + for (let i = 0z; i < n; i += 1) { + mustread(h, buf)?; + const it = endian::begetu32(buf): i64; + append(items, it); + }; +}; diff --git a/time/tzdb/README b/time/tzdb/README @@ -1,5 +0,0 @@ -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 @@ -1,331 +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; - -// Possible errors returned from [[tz]]. -export type error = !(fs::error | io::error | invalidtzif); - -// Converts an [[error]] to a human-friendly representation. -export fn strerror(err: error) const str = { - match (err) { - case invalidtzif => - return "Invalid TZif data in time zone"; - case let err: fs::error => - return fs::strerror(err); - case let err: io::error => - return io::strerror(err); - }; -}; - -// 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. -export fn tz(name: str) (chrono::timezone | error) = { - // TODO: Tidy up errors. - switch (name) { - case "Local" => - return *chrono::LOCAL; - case "UTC" => - return *chrono::UTC; - case "TAI" => - return *chrono::TAI; - case "MTC" => - return *chrono::MTC; - case => - void; - }; - - // TODO: Move this path to +linux et al - const prefix = "/usr/share/zoneinfo/"; - - 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" - mustread(h, buf4)?; - if (strings::fromutf8(buf4) != "TZif") { - return invalidtzif; - }; - - // read version - mustread(h, buf1)?; - const version = switch (buf1[0]) { - case 0 => - yield 1; - case '2' => - yield 2; - case '3' => - yield 3; - case => - return invalidtzif; - }; - - // skip padding - mustread(h, buf15)?; - - // read counts - mustread(h, buf4)?; let isutcnt = endian::begetu32(buf4); - mustread(h, buf4)?; let isstdcnt = endian::begetu32(buf4); - mustread(h, buf4)?; let leapcnt = endian::begetu32(buf4); - mustread(h, buf4)?; let timecnt = endian::begetu32(buf4); - mustread(h, buf4)?; let typecnt = endian::begetu32(buf4); - mustread(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) { - mustread(h, buf1)?; - }; - - // read version 2 counts - mustread(h, buf4)?; isutcnt = endian::begetu32(buf4); - mustread(h, buf4)?; isstdcnt = endian::begetu32(buf4); - mustread(h, buf4)?; leapcnt = endian::begetu32(buf4); - mustread(h, buf4)?; timecnt = endian::begetu32(buf4); - mustread(h, buf4)?; typecnt = endian::begetu32(buf4); - mustread(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 = []; - mustread(h, buf1)?; - if (buf1[0] != 0x0A) { // '\n' newline - return invalidtzif; - }; - for (let start = true; true; start = false) { - mustread(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; -}; - -fn mustread(h: io::handle, buf: []u8) (void | invalidtzif | io::error) = { - match (io::readall(h, buf)) { - case let err: io::error => - return err; - case io::EOF => - return invalidtzif; - case size => - return; - }; -}; - -fn readbytes( - h: io::handle, - items: *[]u8, - n: size, -) (void | invalidtzif | io::error) = { - const buf: [1]u8 = [0]; - for (let i = 0z; i < n; i += 1) { - mustread(h, buf)?; - const it = buf[0]; - append(items, it); - }; -}; - -fn readitems8( - h: io::handle, - items: *[]i64, - n: size, -) (void | invalidtzif | io::error) = { - const buf: [8]u8 = [0...]; - for (let i = 0z; i < n; i += 1) { - mustread(h, buf)?; - const it = endian::begetu64(buf): i64; - append(items, it); - }; -}; - -fn readitems4( - h: io::handle, - items: *[]i64, - n: size, -) (void | invalidtzif | io::error) = { - const buf: [4]u8 = [0...]; - for (let i = 0z; i < n; i += 1) { - mustread(h, buf)?; - const it = endian::begetu32(buf): i64; - append(items, it); - }; -};