File: tzdb.ha

package info (click to toggle)
hare 0.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,948 kB
  • sloc: asm: 1,264; makefile: 123; sh: 114; lisp: 101
file content (346 lines) | stat: -rw-r--r-- 8,099 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use bufio;
use bytes;
use encoding::utf8;
use endian;
use errors;
use fs;
use io;
use os;
use path;
use strings;
use time;
use time::chrono;

// Error concerning the Timezone database.
export type tzdberror = !(invalidtzif | fs::error | io::error);

// Invalid TZif data.
export type invalidtzif = !void;

// Loads a [[timezone]] from the system's TZDB (Timezone Database) as a
// [[locality]]. Each call returns a new instance. The caller must free the
// return value. See [[timezone_free]].
//
// The given "TZID" shall be a TZDB identifier and acts as the relative filepath
// to a TZif (Time Zone Information Format) file under [[TZDB_PATH]].
// For example, "Europe/Amsterdam" for "/usr/share/zoneinfo/Europe/Amsterdam".
//
// The loaded timezones are assigned the TZID as its name, the
// [[time::chrono::utc]] timescale, and the [[EARTH_DAY]] day-length
export fn tzdb(tzid: str) (locality | tzdberror | nomem) = {
	const filepath = match (path::init(TZDB_PATH, tzid)) {
	case let buf: path::buffer =>
		yield buf;
	case let err: path::error =>
		assert(err is path::too_long);
		return errors::noentry: fs::error;
	};
	const file = os::open(path::string(&filepath))?;

	static let buf: [os::BUFSZ]u8 = [0...];
	const bufstrm = bufio::init(file, buf, []);

	let ok = false;
	let name = strings::dup(tzid)?;
	defer if (!ok) free(name);
	let loc = alloc(timezone {
		tsc = &chrono::utc,
		daylength = EARTH_DAY,
		petz = PETZ_EMPTY,
		name = name,
		...
	})?;
	defer if (!ok) free(loc);
	match (load_tzif(&bufstrm, loc)) {
	case void =>
		io::close(&bufstrm)?;
		io::close(file)?;
		ok = true;
		return loc;
	case invalidtzif =>
		io::close(&bufstrm): void;
		io::close(file): void;
		return invalidtzif;
	case let err: io::error =>
		io::close(&bufstrm): void;
		io::close(file): void;
		return err;
	};
};

// Loads TZif (Time Zone Information Format) data onto a given [[timezone]],
// initializing the fields ".phases", ".transitions", and ".petz".
//
// Refer to "RFC 8536" for the TZif format.
export fn load_tzif(h: io::handle, tz: *timezone) (void | invalidtzif | io::error | nomem) = {
	const buf1: [1]u8 = [0...];
	const buf4: [4]u8 = [0...];
	const buf8: [8]u8 = [0...];
	const buf15: [15]u8 = [0...];
	let ok = false;

	// test for magic "TZif"
	mustread(h, buf4)?;
	if (!bytes::equal(buf4, ['T', 'Z', 'i', 'f'])) {
		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)
			&& (isstdcnt == 0 && isstdcnt == typecnt)) {
		return invalidtzif;
	};

	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)?;
	};
	defer free(transition_times);
	const zone_indicies: []u8 = [];
	defer free(zone_indicies);
	readbytes(h, &zone_indicies, timecnt)?;
	const zonedata: []u8 = [];
	defer free(zonedata);
	readbytes(h, &zonedata, typecnt * 6)?;
	const abbrdata: []u8 = [];
	defer free(abbrdata);
	readbytes(h, &abbrdata, charcnt)?;
	const leapdata: []u8 = [];
	defer free(leapdata);
	readbytes(h, &leapdata, leapcnt * (timesz: u32 + 4))?;
	const stdwalldata: []u8 = [];
	defer free(stdwalldata);
	readbytes(h, &stdwalldata, isstdcnt)?;
	const normlocaldata: []u8 = [];
	defer free(normlocaldata);
	readbytes(h, &normlocaldata, isutcnt)?;
	// read footer

	let footerdata: []u8 = [];
	defer free(footerdata);
	mustread(h, buf1)?;
	if (buf1[0] != 0x0A) { // '\n' newline
		return invalidtzif;
	};
	for (true) {
		mustread(h, buf1)?;
		if (buf1[0] == 0x0A) { // '\n' newline
			break;
		};
		if (buf1[0] == 0x0) { // cannot contain NUL
			return invalidtzif;
		};
		append(footerdata, buf1...)?;
	};
	const petz = match (strings::fromutf8(footerdata)) {
	case let s: str =>
		yield s;
	case utf8::invalid =>
		return invalidtzif;
	};

	// assemble structured data

	// assemble zones
	let zones: []zonephase = [];
	defer if (!ok) free(zones);
	for (let i = 0z; i < typecnt; i += 1) {
		const idx = i * 6;
		const zone = zonephase { ... };

		// offset
		const zoff = endian::begetu32(zonedata[idx..idx + 4]): i32;
		if (zoff == -2147483648) { // -2^31
			return invalidtzif;
		};
		zone.zoff = zoff * 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 = match (strings::fromutf8(bytes)) {
		case let s: str =>
			yield s;
		case utf8::invalid =>
			return invalidtzif;
		};
		zone.abbr = strings::dup(abbr)?;

		if (append(zones, zone) is nomem) {
			free(zone.abbr);
			return nomem;
		};
	};

	// assemble transitions
	let transitions: []zonetransition = [];
	defer if (!ok) free(transitions);

	for (let i = 0z; i < timecnt; i += 1) {
		const zoneindex = zone_indicies[i];
		if (zoneindex < 0 || zoneindex > (typecnt - 1)) {
			return invalidtzif;
		};

		const tx = zonetransition {
			when = time::instant {
				sec = transition_times[i],
				...
			},
			zoneindex = zoneindex,
		};

		// stdwalldata and normlocaldata have been omitted,
		// until they show their utility.

		append(transitions, tx)?;
	};

	// save and return data
	tz.phases = zones;
	tz.transitions = transitions;

	if (parse_petz(&tz.petz, petz) is invalidpetzstr) {
		return invalidtzif;
	};

	ok = true;
};

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 | nomem) = {
	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 | nomem) = {
	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 | nomem) = {
	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)?;
	};
};