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:
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);
- };
-};