authorgravatar for jens.goldberg@gmail.comJens Goldberg <jens.goldberg@gmail.com> 2022-01-01 12:47:08+00:00
committergravatar for jens.goldberg@gmail.comJens Goldberg <jens.goldberg@gmail.com> 2022-01-01 12:47:08+00:00
loga54788ba7af64e59d3fbaf8951e3164f1347a2f4
treece2b8278d77299afe44d10d18e6dd9f6a79cb310
parentcb06f6f9846a4705a4d8ae1bcaa508e992bafee8

Support legacy TZ format, expose header struct to a potential writer


2 files changed, 78 insertions(+), 59 deletions(-)

lib/std/tz.zig+78-59
......@@ -38,70 +38,71 @@ pub const Tz = struct {
3838 transitions: []const Transition,
3939 timetypes: []const Timetype,
4040 leapseconds: []const Leapsecond,
41 footer: []const u8,
41 footer: ?[]const u8,
4242
43 pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz {
44 const Header = extern struct {
45 magic: [4]u8,
46 version: u8,
47 reserved: [15]u8,
48 };
49
50 const Counts = extern struct {
43 const Header = extern struct {
44 magic: [4]u8,
45 version: u8,
46 reserved: [15]u8,
47 counts: extern struct {
5148 isutcnt: u32,
5249 isstdcnt: u32,
5350 leapcnt: u32,
5451 timecnt: u32,
5552 typecnt: u32,
5653 charcnt: u32,
57 };
54 },
55 };
5856
59 // Parse and skip the legacy header and data
60 {
61 const header = try reader.readStruct(Header);
62 if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;
63 if (header.version == 0) return error.UnsupportedLegacyFormat;
64 if (header.version != '2' and header.version != '3') return error.BadVersion;
57 pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz {
58 var legacy_header = try reader.readStruct(Header);
59 if (!std.mem.eql(u8, &legacy_header.magic, "TZif")) return error.BadHeader;
60 if (legacy_header.version != 0 and legacy_header.version != '2' and legacy_header.version != '3') return error.BadVersion;
6561
66 var counts = try reader.readStruct(Counts);
67 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
68 std.mem.bswapAllFields(Counts, &counts);
69 }
62 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
63 std.mem.bswapAllFields(@TypeOf(legacy_header.counts), &legacy_header.counts);
64 }
7065
71 const skipv = counts.timecnt * 5 + counts.typecnt * 6 + counts.charcnt + counts.leapcnt * 8 + counts.isstdcnt + counts.isutcnt;
66 if (legacy_header.version == 0) {
67 return parseBlock(allocator, reader, legacy_header, true);
68 } else {
69 // If the format is modern, just skip over the legacy data
70 const skipv = legacy_header.counts.timecnt * 5 + legacy_header.counts.typecnt * 6 + legacy_header.counts.charcnt + legacy_header.counts.leapcnt * 8 + legacy_header.counts.isstdcnt + legacy_header.counts.isutcnt;
7271 try reader.skipBytes(skipv, .{});
73 }
7472
75 const header = try reader.readStruct(Header);
76 if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;
77 if (header.version != '2' and header.version != '3') return error.BadVersion;
73 var header = try reader.readStruct(Header);
74 if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;
75 if (header.version != '2' and header.version != '3') return error.BadVersion;
76 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
77 std.mem.bswapAllFields(@TypeOf(header.counts), &header.counts);
78 }
7879
79 var counts = try reader.readStruct(Counts);
80 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
81 std.mem.bswapAllFields(Counts, &counts);
80 return parseBlock(allocator, reader, header, false);
8281 }
82 }
8383
84 if (counts.isstdcnt != 0 and counts.isstdcnt != counts.typecnt) return error.Malformed; // rfc8536: isstdcnt [...] MUST either be zero or equal to "typecnt"
85 if (counts.isutcnt != 0 and counts.isutcnt != counts.typecnt) return error.Malformed; // rfc8536: isutcnt [...] MUST either be zero or equal to "typecnt"
86 if (counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero
87 if (counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero
88 if (counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical
84 fn parseBlock(allocator: std.mem.Allocator, reader: anytype, header: Header, legacy: bool) !Tz {
85 if (header.counts.isstdcnt != 0 and header.counts.isstdcnt != header.counts.typecnt) return error.Malformed; // rfc8536: isstdcnt [...] MUST either be zero or equal to "typecnt"
86 if (header.counts.isutcnt != 0 and header.counts.isutcnt != header.counts.typecnt) return error.Malformed; // rfc8536: isutcnt [...] MUST either be zero or equal to "typecnt"
87 if (header.counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero
88 if (header.counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero
89 if (header.counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical
8990
90 var leapseconds = try allocator.alloc(Leapsecond, counts.leapcnt);
91 var leapseconds = try allocator.alloc(Leapsecond, header.counts.leapcnt);
9192 errdefer allocator.free(leapseconds);
92 var transitions = try allocator.alloc(Transition, counts.timecnt);
93 var transitions = try allocator.alloc(Transition, header.counts.timecnt);
9394 errdefer allocator.free(transitions);
94 var timetypes = try allocator.alloc(Timetype, counts.typecnt);
95 var timetypes = try allocator.alloc(Timetype, header.counts.typecnt);
9596 errdefer allocator.free(timetypes);
9697
9798 // Parse transition types
9899 var i: usize = 0;
99 while (i < counts.timecnt) : (i += 1) {
100 transitions[i].ts = try reader.readIntBig(i64);
100 while (i < header.counts.timecnt) : (i += 1) {
101 transitions[i].ts = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64);
101102 }
102103
103104 i = 0;
104 while (i < counts.timecnt) : (i += 1) {
105 while (i < header.counts.timecnt) : (i += 1) {
105106 const tt = try reader.readByte();
106107 if (tt >= timetypes.len) return error.Malformed; // rfc8536: Each type index MUST be in the range [0, "typecnt" - 1]
107108 transitions[i].timetype = &timetypes[tt];
......@@ -109,13 +110,13 @@ pub const Tz = struct {
109110
110111 // Parse time types
111112 i = 0;
112 while (i < counts.typecnt) : (i += 1) {
113 while (i < header.counts.typecnt) : (i += 1) {
113114 const offset = try reader.readIntBig(i32);
114115 if (offset < -2147483648) return error.Malformed; // rfc8536: utoff [...] MUST NOT be -2**31
115116 const dst = try reader.readByte();
116117 if (dst != 0 and dst != 1) return error.Malformed; // rfc8536: (is)dst [...] The value MUST be 0 or 1.
117118 const idx = try reader.readByte();
118 if (idx > counts.charcnt - 1) return error.Malformed; // rfc8536: (desig)idx [...] Each index MUST be in the range [0, "charcnt" - 1]
119 if (idx > header.counts.charcnt - 1) return error.Malformed; // rfc8536: (desig)idx [...] Each index MUST be in the range [0, "charcnt" - 1]
119120 timetypes[i] = .{
120121 .offset = offset,
121122 .flags = dst,
......@@ -127,8 +128,8 @@ pub const Tz = struct {
127128 }
128129
129130 var designators_data: [256 + 6]u8 = undefined;
130 try reader.readNoEof(designators_data[0..counts.charcnt]);
131 const designators = designators_data[0..counts.charcnt];
131 try reader.readNoEof(designators_data[0..header.counts.charcnt]);
132 const designators = designators_data[0..header.counts.charcnt];
132133 if (designators[designators.len - 1] != 0) return error.Malformed; // rfc8536: charcnt [...] includes the trailing NUL (0x00) octet
133134
134135 // Iterate through the timetypes again, setting the designator names
......@@ -142,8 +143,8 @@ pub const Tz = struct {
142143
143144 // Parse leap seconds
144145 i = 0;
145 while (i < counts.leapcnt) : (i += 1) {
146 const occur = try reader.readIntBig(i64);
146 while (i < header.counts.leapcnt) : (i += 1) {
147 const occur: i64 = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64);
147148 if (occur < 0) return error.Malformed; // rfc8536: occur [...] MUST be nonnegative
148149 if (i > 0 and leapseconds[i - 1].occurrence + 2419199 > occur) return error.Malformed; // rfc8536: occur [...] each later value MUST be at least 2419199 greater than the previous value
149150 if (occur > std.math.maxInt(i48)) return error.Malformed; // Unreasonably far into the future
......@@ -161,7 +162,7 @@ pub const Tz = struct {
161162
162163 // Parse standard/wall indicators
163164 i = 0;
164 while (i < counts.isstdcnt) : (i += 1) {
165 while (i < header.counts.isstdcnt) : (i += 1) {
165166 const stdtime = try reader.readByte();
166167 if (stdtime == 1) {
167168 timetypes[i].flags |= 0x02;
......@@ -170,7 +171,7 @@ pub const Tz = struct {
170171
171172 // Parse UT/local indicators
172173 i = 0;
173 while (i < counts.isutcnt) : (i += 1) {
174 while (i < header.counts.isutcnt) : (i += 1) {
174175 const ut = try reader.readByte();
175176 if (ut == 1) {
176177 timetypes[i].flags |= 0x04;
......@@ -178,29 +179,34 @@ pub const Tz = struct {
178179 }
179180 }
180181
181 if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline
182
183182 // Footer
184 var footerdata_buf: [128]u8 = undefined;
185 const footer = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) {
186 error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string
187 else => return err,
188 };
189
190 const footer_dup = try allocator.dupe(u8, footer);
191 errdefer allocator.free(footer_dup);
183 var footer: ?[]u8 = null;
184 if (!legacy) {
185 if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline
186 var footerdata_buf: [128]u8 = undefined;
187 const footer_mem = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) {
188 error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string
189 else => return err,
190 };
191 if (footer_mem.len != 0) {
192 footer = try allocator.dupe(u8, footer_mem);
193 }
194 }
195 errdefer if (footer) |ft| allocator.free(ft);
192196
193197 return Tz{
194198 .allocator = allocator,
195199 .transitions = transitions,
196200 .timetypes = timetypes,
197201 .leapseconds = leapseconds,
198 .footer = footer_dup,
202 .footer = footer,
199203 };
200204 }
201205
202206 pub fn deinit(self: *Tz) void {
203 self.allocator.free(self.footer);
207 if (self.footer) |footer| {
208 self.allocator.free(footer);
209 }
204210 self.allocator.free(self.leapseconds);
205211 self.allocator.free(self.transitions);
206212 self.allocator.free(self.timetypes);
......@@ -231,3 +237,16 @@ test "fat" {
231237 try std.testing.expect(std.mem.eql(u8, tz.transitions[3].timetype.name(), "+05"));
232238 try std.testing.expectEqual(tz.transitions[4].ts, 1268251224); // 2010-03-10 20:00:00 UTC
233239}
240
241test "legacy" {
242 // Taken from Slackware 8.0, from 2001
243 const data = @embedFile("tz/europe_vatican.tzif");
244 var in_stream = std.io.fixedBufferStream(data);
245
246 var tz = try std.Tz.parse(std.testing.allocator, in_stream.reader());
247 defer tz.deinit();
248
249 try std.testing.expectEqual(tz.transitions.len, 170);
250 try std.testing.expect(std.mem.eql(u8, tz.transitions[69].timetype.name(), "CET"));
251 try std.testing.expectEqual(tz.transitions[123].ts, 1414285200); // 2014-10-26 01:00:00 UTC
252}
lib/std/tz/europe_vatican.tzif created
Binary files /dev/null and b/lib/std/tz/europe_vatican.tzif differ