zig-skills/references/std-tz.md

6.8 KiB

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

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

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

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

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:

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:

pub const Leapsecond = struct {
    occurrence: i48,  // Unix timestamp when leap second occurs
    correction: i16,  // Cumulative TAI-UTC difference
};

Look Up Current Timezone Offset

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

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

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

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

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:

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)