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 {...@@ -38,70 +38,71 @@ pub const Tz = struct {
38 transitions: []const Transition,38 transitions: []const Transition,
39 timetypes: []const Timetype,39 timetypes: []const Timetype,
40 leapseconds: []const Leapsecond,40 leapseconds: []const Leapsecond,
41 footer: []const u8,41 footer: ?[]const u8,
4242
43 pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz {43 const Header = extern struct {
44 const Header = extern struct {44 magic: [4]u8,
45 magic: [4]u8,45 version: u8,
46 version: u8,46 reserved: [15]u8,
47 reserved: [15]u8,47 counts: extern struct {
48 };
49
50 const Counts = extern struct {
51 isutcnt: u32,48 isutcnt: u32,
52 isstdcnt: u32,49 isstdcnt: u32,
53 leapcnt: u32,50 leapcnt: u32,
54 timecnt: u32,51 timecnt: u32,
55 typecnt: u32,52 typecnt: u32,
56 charcnt: u32,53 charcnt: u32,
57 };54 },
55 };
5856
59 // Parse and skip the legacy header and data57 pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz {
60 {58 var legacy_header = try reader.readStruct(Header);
61 const header = try reader.readStruct(Header);59 if (!std.mem.eql(u8, &legacy_header.magic, "TZif")) return error.BadHeader;
62 if (!std.mem.eql(u8, &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;
63 if (header.version == 0) return error.UnsupportedLegacyFormat;
64 if (header.version != '2' and header.version != '3') return error.BadVersion;
6561
66 var counts = try reader.readStruct(Counts);62 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
67 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {63 std.mem.bswapAllFields(@TypeOf(legacy_header.counts), &legacy_header.counts);
68 std.mem.bswapAllFields(Counts, &counts);64 }
69 }
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;
72 try reader.skipBytes(skipv, .{});71 try reader.skipBytes(skipv, .{});
73 }
7472
75 const header = try reader.readStruct(Header);73 var header = try reader.readStruct(Header);
76 if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;74 if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;
77 if (header.version != '2' and header.version != '3') return error.BadVersion;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 return parseBlock(allocator, reader, header, false);
80 if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) {
81 std.mem.bswapAllFields(Counts, &counts);
82 }81 }
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"84 fn parseBlock(allocator: std.mem.Allocator, reader: anytype, header: Header, legacy: bool) !Tz {
85 if (counts.isutcnt != 0 and counts.isutcnt != counts.typecnt) return error.Malformed; // rfc8536: isutcnt [...] MUST either be zero or equal to "typecnt"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 (counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero86 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 (counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero87 if (header.counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero
88 if (counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical88 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);
91 errdefer allocator.free(leapseconds);92 errdefer allocator.free(leapseconds);
92 var transitions = try allocator.alloc(Transition, counts.timecnt);93 var transitions = try allocator.alloc(Transition, header.counts.timecnt);
93 errdefer allocator.free(transitions);94 errdefer allocator.free(transitions);
94 var timetypes = try allocator.alloc(Timetype, counts.typecnt);95 var timetypes = try allocator.alloc(Timetype, header.counts.typecnt);
95 errdefer allocator.free(timetypes);96 errdefer allocator.free(timetypes);
9697
97 // Parse transition types98 // Parse transition types
98 var i: usize = 0;99 var i: usize = 0;
99 while (i < counts.timecnt) : (i += 1) {100 while (i < header.counts.timecnt) : (i += 1) {
100 transitions[i].ts = try reader.readIntBig(i64);101 transitions[i].ts = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64);
101 }102 }
102103
103 i = 0;104 i = 0;
104 while (i < counts.timecnt) : (i += 1) {105 while (i < header.counts.timecnt) : (i += 1) {
105 const tt = try reader.readByte();106 const tt = try reader.readByte();
106 if (tt >= timetypes.len) return error.Malformed; // rfc8536: Each type index MUST be in the range [0, "typecnt" - 1]107 if (tt >= timetypes.len) return error.Malformed; // rfc8536: Each type index MUST be in the range [0, "typecnt" - 1]
107 transitions[i].timetype = &timetypes[tt];108 transitions[i].timetype = &timetypes[tt];
...@@ -109,13 +110,13 @@ pub const Tz = struct {...@@ -109,13 +110,13 @@ pub const Tz = struct {
109110
110 // Parse time types111 // Parse time types
111 i = 0;112 i = 0;
112 while (i < counts.typecnt) : (i += 1) {113 while (i < header.counts.typecnt) : (i += 1) {
113 const offset = try reader.readIntBig(i32);114 const offset = try reader.readIntBig(i32);
114 if (offset < -2147483648) return error.Malformed; // rfc8536: utoff [...] MUST NOT be -2**31115 if (offset < -2147483648) return error.Malformed; // rfc8536: utoff [...] MUST NOT be -2**31
115 const dst = try reader.readByte();116 const dst = try reader.readByte();
116 if (dst != 0 and dst != 1) return error.Malformed; // rfc8536: (is)dst [...] The value MUST be 0 or 1.117 if (dst != 0 and dst != 1) return error.Malformed; // rfc8536: (is)dst [...] The value MUST be 0 or 1.
117 const idx = try reader.readByte();118 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]
119 timetypes[i] = .{120 timetypes[i] = .{
120 .offset = offset,121 .offset = offset,
121 .flags = dst,122 .flags = dst,
...@@ -127,8 +128,8 @@ pub const Tz = struct {...@@ -127,8 +128,8 @@ pub const Tz = struct {
127 }128 }
128129
129 var designators_data: [256 + 6]u8 = undefined;130 var designators_data: [256 + 6]u8 = undefined;
130 try reader.readNoEof(designators_data[0..counts.charcnt]);131 try reader.readNoEof(designators_data[0..header.counts.charcnt]);
131 const designators = designators_data[0..counts.charcnt];132 const designators = designators_data[0..header.counts.charcnt];
132 if (designators[designators.len - 1] != 0) return error.Malformed; // rfc8536: charcnt [...] includes the trailing NUL (0x00) octet133 if (designators[designators.len - 1] != 0) return error.Malformed; // rfc8536: charcnt [...] includes the trailing NUL (0x00) octet
133134
134 // Iterate through the timetypes again, setting the designator names135 // Iterate through the timetypes again, setting the designator names
...@@ -142,8 +143,8 @@ pub const Tz = struct {...@@ -142,8 +143,8 @@ pub const Tz = struct {
142143
143 // Parse leap seconds144 // Parse leap seconds
144 i = 0;145 i = 0;
145 while (i < counts.leapcnt) : (i += 1) {146 while (i < header.counts.leapcnt) : (i += 1) {
146 const occur = try reader.readIntBig(i64);147 const occur: i64 = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64);
147 if (occur < 0) return error.Malformed; // rfc8536: occur [...] MUST be nonnegative148 if (occur < 0) return error.Malformed; // rfc8536: occur [...] MUST be nonnegative
148 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 value149 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
149 if (occur > std.math.maxInt(i48)) return error.Malformed; // Unreasonably far into the future150 if (occur > std.math.maxInt(i48)) return error.Malformed; // Unreasonably far into the future
...@@ -161,7 +162,7 @@ pub const Tz = struct {...@@ -161,7 +162,7 @@ pub const Tz = struct {
161162
162 // Parse standard/wall indicators163 // Parse standard/wall indicators
163 i = 0;164 i = 0;
164 while (i < counts.isstdcnt) : (i += 1) {165 while (i < header.counts.isstdcnt) : (i += 1) {
165 const stdtime = try reader.readByte();166 const stdtime = try reader.readByte();
166 if (stdtime == 1) {167 if (stdtime == 1) {
167 timetypes[i].flags |= 0x02;168 timetypes[i].flags |= 0x02;
...@@ -170,7 +171,7 @@ pub const Tz = struct {...@@ -170,7 +171,7 @@ pub const Tz = struct {
170171
171 // Parse UT/local indicators172 // Parse UT/local indicators
172 i = 0;173 i = 0;
173 while (i < counts.isutcnt) : (i += 1) {174 while (i < header.counts.isutcnt) : (i += 1) {
174 const ut = try reader.readByte();175 const ut = try reader.readByte();
175 if (ut == 1) {176 if (ut == 1) {
176 timetypes[i].flags |= 0x04;177 timetypes[i].flags |= 0x04;
...@@ -178,29 +179,34 @@ pub const Tz = struct {...@@ -178,29 +179,34 @@ pub const Tz = struct {
178 }179 }
179 }180 }
180181
181 if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline
182
183 // Footer182 // Footer
184 var footerdata_buf: [128]u8 = undefined;183 var footer: ?[]u8 = null;
185 const footer = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) {184 if (!legacy) {
186 error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string185 if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline
187 else => return err,186 var footerdata_buf: [128]u8 = undefined;
188 };187 const footer_mem = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) {
189188 error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string
190 const footer_dup = try allocator.dupe(u8, footer);189 else => return err,
191 errdefer allocator.free(footer_dup);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
193 return Tz{197 return Tz{
194 .allocator = allocator,198 .allocator = allocator,
195 .transitions = transitions,199 .transitions = transitions,
196 .timetypes = timetypes,200 .timetypes = timetypes,
197 .leapseconds = leapseconds,201 .leapseconds = leapseconds,
198 .footer = footer_dup,202 .footer = footer,
199 };203 };
200 }204 }
201205
202 pub fn deinit(self: *Tz) void {206 pub fn deinit(self: *Tz) void {
203 self.allocator.free(self.footer);207 if (self.footer) |footer| {
208 self.allocator.free(footer);
209 }
204 self.allocator.free(self.leapseconds);210 self.allocator.free(self.leapseconds);
205 self.allocator.free(self.transitions);211 self.allocator.free(self.transitions);
206 self.allocator.free(self.timetypes);212 self.allocator.free(self.timetypes);
...@@ -231,3 +237,16 @@ test "fat" {...@@ -231,3 +237,16 @@ test "fat" {
231 try std.testing.expect(std.mem.eql(u8, tz.transitions[3].timetype.name(), "+05"));237 try std.testing.expect(std.mem.eql(u8, tz.transitions[3].timetype.name(), "+05"));
232 try std.testing.expectEqual(tz.transitions[4].ts, 1268251224); // 2010-03-10 20:00:00 UTC238 try std.testing.expectEqual(tz.transitions[4].ts, 1268251224); // 2010-03-10 20:00:00 UTC
233}239}
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