zig-skills/references/std-tz.md

241 lines
6.8 KiB
Markdown

# std.Tz - TZif Timezone Database Parsing (Zig 0.16.0)
Parse IANA Time Zone Database files (TZif format, RFC 8536) into transitions, time types, leap seconds, and an optional POSIX footer. `std.Tz` stores this data; applications perform their own timestamp lookup and, if needed, interpret future rules from the footer.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
When reading timezone files in Zig 0.16, use `std.Io.Dir`/`std.Io.File` and explicit `std.Io`; use `std.Io.Reader.fixed(bytes)` style patterns instead of old `std.io.fixedBufferStream`.
## Quick Reference
| Type | Description |
|------|-------------|
| `Tz` | Parsed timezone with transitions, time types, and leap seconds |
| `Transition` | Point in time when timezone rules change |
| `Timetype` | Timezone offset, DST flag, and abbreviation |
| `Leapsecond` | Leap second occurrence and cumulative correction |
## Basic Usage
```zig
const std = @import("std");
fn load(io: std.Io, allocator: std.mem.Allocator) !void {
// Open system timezone file
const file = try std.Io.Dir.openFileAbsolute(io, "/usr/share/zoneinfo/America/New_York", .{});
defer file.close(io);
var read_buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &read_buf);
// Parse TZif data
var tz = try std.Tz.parse(allocator, &file_reader.interface);
defer tz.deinit();
// Access timezone information
std.debug.print("Transitions: {}\n", .{tz.transitions.len});
std.debug.print("Footer (POSIX TZ): {s}\n", .{tz.footer orelse "(none)"});
}
```
## Parsing from Embedded Data
```zig
const std = @import("std");
// Embed TZif file at compile time
const tokyo_tz = @embedFile("tz/asia_tokyo.tzif");
pub fn parseEmbedded(allocator: std.mem.Allocator) !void {
var reader: std.Io.Reader = .fixed(tokyo_tz);
var tz = try std.Tz.parse(allocator, &reader);
defer tz.deinit();
// Use timezone data...
}
```
## Tz Struct
```zig
pub const Tz = struct {
allocator: std.mem.Allocator,
transitions: []const Transition, // Sorted by timestamp
timetypes: []const Timetype,
leapseconds: []const Leapsecond,
footer: ?[]const u8, // POSIX TZ string for future dates
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !Tz
pub fn deinit(self: *Tz) void
};
```
## Transition
A transition marks when timezone rules change (e.g., DST start/end):
```zig
pub const Transition = struct {
ts: i64, // Unix timestamp (seconds since epoch)
timetype: *Timetype, // Pointer to active time type after this transition
};
```
## Timetype
Describes timezone offset and DST status:
```zig
pub const Timetype = struct {
offset: i32, // UTC offset in seconds (e.g., -18000 for EST = UTC-5)
flags: u8, // Packed flags
name_data: [6:0]u8, // Null-terminated abbreviation (e.g., "EST", "PDT")
pub fn name(self: *const Timetype) [:0]const u8 // Get abbreviation
pub fn isDst(self: Timetype) bool // Is daylight saving time?
pub fn standardTimeIndicator(self: Timetype) bool
pub fn utIndicator(self: Timetype) bool
};
```
## Leapsecond
Leap second corrections for TAI-UTC:
```zig
pub const Leapsecond = struct {
occurrence: i48, // Unix timestamp when leap second occurs
correction: i16, // Cumulative TAI-UTC difference
};
```
## Look Up Current Timezone Offset
```zig
fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
// Find the last transition before or at the given timestamp
var result: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
result = t.timetype;
} else {
break;
}
}
// Return offset or default to first timetype
if (result) |tt| {
return tt.offset;
} else if (tz.timetypes.len > 0) {
return tz.timetypes[0].offset;
}
return 0;
}
// Usage for a timestamp supplied by the caller
const unix_timestamp: i64 = 1_700_000_000;
const offset = getUtcOffset(&tz, unix_timestamp);
const local_time = unix_timestamp + offset;
```
## Check if DST is Active
```zig
fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
var active: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
active = t.timetype;
} else {
break;
}
}
if (active) |tt| return tt.isDst();
return tz.timetypes.len > 0 and tz.timetypes[0].isDst();
}
```
## Get Timezone Abbreviation
```zig
fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 {
var result: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
result = t.timetype;
} else {
break;
}
}
if (result) |tt| {
return tt.name();
} else if (tz.timetypes.len > 0) {
return tz.timetypes[0].name();
}
return "UTC";
}
// Returns "EST", "EDT", "PST", "PDT", "JST", etc.
```
## List All Transitions
```zig
fn printTransitions(tz: *const std.Tz) void {
for (tz.transitions) |t| {
std.debug.print("{d}: {s} (offset {d}s, DST: {})\n", .{
t.ts,
t.timetype.name(),
t.timetype.offset,
t.timetype.isDst(),
});
}
}
```
## Selected Parse Errors
| Error | Cause |
|-------|-------|
| `error.BadHeader` | Invalid TZif magic bytes (not "TZif") |
| `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) |
| `error.Malformed` | RFC 8536 validation failure |
| `error.OverlargeFooter` | Footer exceeded the active reader's capacity while scanning to its newline |
Allocation failures and `std.Io.Reader` failures such as `EndOfStream` or `ReadFailed` can also propagate.
## System Timezone Paths
| Platform | Path |
|----------|------|
| Linux/BSD | `/usr/share/zoneinfo/<Region>/<City>` |
| macOS | `/var/db/timezone/zoneinfo/<Region>/<City>` |
Common timezone identifiers:
- `America/New_York`, `America/Los_Angeles`, `America/Chicago`
- `Europe/London`, `Europe/Paris`, `Europe/Berlin`
- `Asia/Tokyo`, `Asia/Shanghai`, `Asia/Kolkata`
- `UTC`, `Etc/GMT`
## POSIX TZ Footer
Modern TZif files (v2+) may include a POSIX TZ string in the footer. `std.Tz` stores a non-empty footer but does not interpret it to calculate future offsets:
```zig
if (tz.footer) |posix_tz| {
// e.g., "EST5EDT,M3.2.0,M11.1.0" for US Eastern
std.debug.print("POSIX TZ: {s}\n", .{posix_tz});
}
```
## Notes
- Allocates memory for transitions, timetypes, leapseconds, and footer
- Call `deinit()` to free allocated memory
- Supports TZif version 0 (legacy 32-bit), 2, and 3 (64-bit timestamps)
- Timezone abbreviations are limited to 6 characters (POSIX compliance)
- Transition timestamps are Unix epoch seconds (signed i64)
- Offset is in seconds, negative for west of UTC (e.g., -18000 = UTC-5)