Showing preview only (251K chars total). Download the full file or copy to clipboard to get everything.
Repository: rockorager/zeit
Branch: main
Commit: 0ad98ccc1563
Files: 20
Total size: 242.1 KB
Directory structure:
gitextract_55wlgbp2/
├── .github/
│ └── workflows/
│ ├── docs.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── bench/
│ ├── bench_benjoffe.zig
│ ├── bench_days_benjoffe.zig
│ ├── bench_days_current.zig
│ ├── bench_hinnant.zig
│ ├── bench_leap_benjoffe.zig
│ ├── bench_leap_current.zig
│ ├── test_days.zig
│ └── test_leap.zig
├── build.zig
├── build.zig.zon
├── gen/
│ ├── main.zig
│ └── windowsZones.xml
└── src/
├── location.zig
├── timezone.zig
└── zeit.zig
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/docs.yml
================================================
name: docs
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
- run: zig build docs
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "zig-out/docs"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .github/workflows/test.yml
================================================
name: test
on:
pull_request:
branches: ["main", "0.16"]
workflow_dispatch:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v3
- uses: mlugg/setup-zig@v2
with:
version: master
- run: zig build test --summary new
check-fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: mlugg/setup-zig@v2
with:
version: master
- run: zig fmt --check .
================================================
FILE: .gitignore
================================================
.zig-cache/
zig-out/
================================================
FILE: LICENSE
================================================
Copyright (c) 2024 Tim Culverhouse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# zeit
A time library written in zig.
## Install
```
zig fetch --save git+https://github.com/rockorager/zeit?ref=main
```
Or install a [tag](https://github.com/rockorager/zeit/tags) instead of main.
## Usage
[API Documentation](https://rockorager.github.io/zeit/)
```zig
const std = @import("std");
const zeit = @import("zeit");
pub fn main() !void {
const allocator = std.heap.page_allocator;
var threaded = std.Io.Threaded.init(allocator, .{});
defer threaded.deinit();
const io = threaded.io();
// Get a "now" instant in UTC.
const now = zeit.instant(.{.now = io}, &zeit.utc);
// Load our local timezone. This needs an allocator. Optionally pass in a
// zeit.EnvConfig to support TZ and TZDIR environment variables
const local = try zeit.local(allocator, io, .{});
defer local.deinit();
// Convert our instant to a new timezone
const now_local = now.in(&local);
// Generate date/time info for this instant
const dt = now_local.time();
// Print it out
std.debug.print("{}", .{dt});
// zeit.Time{
// .year = 2024,
// .month = zeit.Month.mar,
// .day = 16,
// .hour = 8,
// .minute = 38,
// .second = 29,
// .millisecond = 496,
// .microsecond = 706,
// .nanosecond = 64
// .offset = -18000,
// }
var buf: [256]u8 = undefined;
var writer = std.Io.Writer.fixed(&buf);
// Format using strftime specifier. Format strings are not required to be comptime
try dt.strftime(&writer, "%Y-%m-%d %H:%M:%S %Z");
std.debug.print("{s}\n", .{writer.buffered()});
writer.end = 0;
// Or...golang magic date specifiers. Format strings are not required to be comptime
try dt.gofmt(&writer, "2006-01-02 15:04:05 MST");
std.debug.print("{s}\n", .{writer.buffered()});
// Load an arbitrary location using IANA location syntax. The location name
// comes from an enum which will automatically map IANA location names to
// Windows names, as needed. Pass an optional EnvConfig to support TZDIR
const vienna = try zeit.loadTimeZone(allocator, io, .@"Europe/Vienna", .{});
defer vienna.deinit();
// Parse an Instant from an ISO8601 or RFC3339 string
_ = try zeit.instantFromText(
.iso8601,
"2024-03-16T08:38:29.496-1200",
&zeit.utc,
});
_ = try zeit.instantFromText(
.rfc3339,
"2024-03-16T08:38:29.496706064-1200",
&zeit.utc,
});
}
```
================================================
FILE: bench/bench_benjoffe.zig
================================================
const std = @import("std");
const builtin = @import("builtin");
const Date = struct {
year: i32,
month: u4,
day: u5,
};
const is_arm = builtin.cpu.arch == .aarch64 or builtin.cpu.arch == .arm;
const ERAS: u64 = 4726498270;
const D_SHIFT: u64 = 146097 * ERAS - 719469;
const Y_SHIFT: u64 = 400 * ERAS - 1;
const SCALE: u64 = if (is_arm) 1 else 32;
const SHIFT_0: u64 = 30556 * SCALE;
const SHIFT_1: u64 = 5980 * SCALE;
const C1: u64 = 505054698555331;
const C2: u64 = 50504432782230121;
const C3: u64 = @as(u64, 8619973866219416) * 32 / SCALE;
/// Ben Joffe's very fast 64-bit date algorithm
/// https://www.benjoffe.com/fast-date-64
fn civilFromDays(days: i32) Date {
const rev: u64 = D_SHIFT -% @as(u64, @bitCast(@as(i64, days)));
const cen: u64 = @truncate(@as(u128, C1) * rev >> 64);
const jul: u64 = rev +% cen -% cen / 4;
const num: u128 = @as(u128, C2) * jul;
const yrs: u64 = Y_SHIFT -% @as(u64, @truncate(num >> 64));
const low: u64 = @truncate(num);
const ypt: u64 = @truncate(@as(u128, 24451 * SCALE) * low >> 64);
if (is_arm) {
const shift: u64 = SHIFT_0;
const N: u64 = (yrs % 4) * (16 * SCALE) +% shift -% ypt;
const M: u64 = N / (2048 * SCALE);
const D: u64 = @truncate(@as(u128, C3) * (N % (2048 * SCALE)) >> 64);
const bump: u64 = if (M > 12) 1 else 0;
const month: u4 = @intCast(if (bump == 1) M - 12 else M);
const day: u5 = @intCast(D + 1);
const year: i32 = @intCast(@as(i64, @bitCast(yrs)) + @as(i64, @intCast(bump)));
return .{ .year = year, .month = month, .day = day };
} else {
const bump: u64 = if (ypt < (3952 * SCALE)) 1 else 0;
const shift: u64 = if (bump == 1) SHIFT_1 else SHIFT_0;
const N: u64 = (yrs % 4) * (16 * SCALE) +% shift -% ypt;
const M: u64 = N / (2048 * SCALE);
const D: u64 = @truncate(@as(u128, C3) * (N % (2048 * SCALE)) >> 64);
const month: u4 = @intCast(M);
const day: u5 = @intCast(D + 1);
const year: i32 = @intCast(@as(i64, @bitCast(yrs)) + @as(i64, @intCast(bump)));
return .{ .year = year, .month = month, .day = day };
}
}
pub fn main() !void {
var result: i64 = 0;
const iterations = 10_000_000;
for (0..iterations) |i| {
const days: i32 = @intCast(@as(i64, @intCast(i)) - iterations / 2);
const date = civilFromDays(days);
result +%= @as(i64, date.year) + @as(i64, date.month) + @as(i64, date.day);
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/bench_days_benjoffe.zig
================================================
const std = @import("std");
const Date = struct {
year: i32,
month: u4,
day: u5,
};
/// Ben Joffe's fast overflow-safe inverse function
/// https://www.benjoffe.com/fast-date-64
fn daysFromCivil(date: Date) i32 {
const month: u32 = date.month;
const bump: u32 = if (month <= 2) 1 else 0;
const yrs: u32 = @bitCast(date.year +% 5880000 - @as(i32, @intCast(bump)));
const cen: u32 = yrs / 100;
const shift: i32 = if (bump == 1) 8829 else -2919;
const year_days: u32 = yrs * 365 + yrs / 4 - cen + cen / 4;
const month_days: u32 = @bitCast(@divFloor(979 * @as(i32, @intCast(month)) + shift, 32));
return @bitCast(year_days +% month_days +% date.day -% 2148345369);
}
pub fn main() !void {
var result: i64 = 0;
const iterations = 10_000_000;
for (0..iterations) |i| {
const year: i32 = @intCast(@as(i64, @intCast(i)) - iterations / 2);
const date = Date{ .year = year, .month = 6, .day = 15 };
result +%= daysFromCivil(date);
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/bench_days_current.zig
================================================
const std = @import("std");
const Date = struct {
year: i32,
month: u4,
day: u5,
};
const days_per_era = 365 * 400 + 97;
/// Current Hinnant algorithm
fn daysFromCivil(date: Date) i32 {
const m: i32 = date.month;
const y: i32 = if (m <= 2) date.year - 1 else date.year;
const era = @divFloor(y, 400);
const yoe: u32 = @intCast(y - era * 400);
const doy = blk: {
const a: u32 = if (m > 2) @intCast(m - 3) else @intCast(m + 9);
const b = a * 153 + 2;
break :blk @divFloor(b, 5) + date.day - 1;
};
const doe: i32 = @intCast(yoe * 365 + @divFloor(yoe, 4) - @divFloor(yoe, 100) + doy);
return era * days_per_era + doe - 719468;
}
pub fn main() !void {
var result: i64 = 0;
const iterations = 10_000_000;
for (0..iterations) |i| {
const year: i32 = @intCast(@as(i64, @intCast(i)) - iterations / 2);
const date = Date{ .year = year, .month = 6, .day = 15 };
result +%= daysFromCivil(date);
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/bench_hinnant.zig
================================================
const std = @import("std");
const Date = struct {
year: i32,
month: u4,
day: u5,
};
const days_per_era = 365 * 400 + 97;
/// Howard Hinnant's algorithm
/// https://howardhinnant.github.io/date_algorithms.html#civil_from_days
fn civilFromDays(days: i32) Date {
const z = days + 719468;
const era = @divFloor(z, days_per_era);
const doe: u32 = @intCast(z - era * days_per_era);
const yoe: u32 = @intCast(
@divFloor(
doe -
@divFloor(doe, 1460) +
@divFloor(doe, 36524) -
@divFloor(doe, 146096),
365,
),
);
const y: i32 = @as(i32, @intCast(yoe)) + era * 400;
const doy = doe - (365 * yoe + @divFloor(yoe, 4) - @divFloor(yoe, 100));
const mp = @divFloor(5 * doy + 2, 153);
const d = doy - @divFloor(153 * mp + 2, 5) + 1;
const m = if (mp < 10) mp + 3 else mp - 9;
return .{
.year = if (m <= 2) y + 1 else y,
.month = @intCast(m),
.day = @truncate(d),
};
}
pub fn main() !void {
var result: i64 = 0;
const iterations = 10_000_000;
for (0..iterations) |i| {
const days: i32 = @intCast(@as(i64, @intCast(i)) - iterations / 2);
const date = civilFromDays(days);
result +%= @as(i64, date.year) + @as(i64, date.month) + @as(i64, date.day);
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/bench_leap_benjoffe.zig
================================================
const std = @import("std");
/// Ben Joffe's fast full-range leap year algorithm
/// https://www.benjoffe.com/fast-leap-year
fn isLeapYear(year: i32) bool {
const cen_bias: u32 = 2147483600;
const cen_mul: u32 = 42949673;
const cen_cutoff: u32 = 171798692;
const a: u32 = @bitCast(year +% @as(i32, @bitCast(cen_bias)));
const low: u32 = a *% cen_mul;
const is_likely_cen = low < cen_cutoff;
const mask: u5 = if (is_likely_cen) 15 else 3;
return (year & mask) == 0;
}
pub fn main() !void {
var result: u64 = 0;
const iterations: i32 = 100_000_000;
var year: i32 = -iterations / 2;
while (year < iterations / 2) : (year += 1) {
if (isLeapYear(year)) result += 1;
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/bench_leap_current.zig
================================================
const std = @import("std");
/// Current Neri/Schneider algorithm
fn isLeapYear(year: i32) bool {
const d: i32 = if (@mod(year, 100) != 0) 4 else 16;
return (year & (d - 1)) == 0;
}
pub fn main() !void {
var result: u64 = 0;
const iterations: i32 = 100_000_000;
var year: i32 = -iterations / 2;
while (year < iterations / 2) : (year += 1) {
if (isLeapYear(year)) result += 1;
}
std.debug.print("result: {}\n", .{result});
}
================================================
FILE: bench/test_days.zig
================================================
const std = @import("std");
const Date = struct {
year: i32,
month: u4,
day: u5,
};
const days_per_era = 365 * 400 + 97;
fn daysFromCivilCurrent(date: Date) i32 {
const m: i32 = date.month;
const y: i32 = if (m <= 2) date.year - 1 else date.year;
const era = @divFloor(y, 400);
const yoe: u32 = @intCast(y - era * 400);
const doy = blk: {
const a: u32 = if (m > 2) @intCast(m - 3) else @intCast(m + 9);
const b = a * 153 + 2;
break :blk @divFloor(b, 5) + date.day - 1;
};
const doe: i32 = @intCast(yoe * 365 + @divFloor(yoe, 4) - @divFloor(yoe, 100) + doy);
return era * days_per_era + doe - 719468;
}
fn daysFromCivilBenjoffe(date: Date) i32 {
const month: u32 = date.month;
const bump: u32 = if (month <= 2) 1 else 0;
const yrs: u32 = @bitCast(date.year +% 5880000 - @as(i32, @intCast(bump)));
const cen: u32 = yrs / 100;
const shift: i32 = if (bump == 1) 8829 else -2919;
const year_days: u32 = yrs * 365 + yrs / 4 - cen + cen / 4;
const month_days: u32 = @bitCast(@divFloor(979 * @as(i32, @intCast(month)) + shift, 32));
return @bitCast(year_days +% month_days +% date.day -% 2148345369);
}
pub fn main() void {
var mismatches: u32 = 0;
// Test a wide range of years and all months/days
const test_years = [_]i32{ -5000, -1000, -100, -1, 0, 1, 100, 1000, 1970, 2000, 2024, 5000, 100000, -100000 };
for (test_years) |year| {
for (1..13) |m| {
const month: u4 = @intCast(m);
for (1..29) |d| {
const day: u5 = @intCast(d);
const date = Date{ .year = year, .month = month, .day = day };
const current = daysFromCivilCurrent(date);
const benjoffe = daysFromCivilBenjoffe(date);
if (current != benjoffe) {
mismatches += 1;
if (mismatches <= 10) {
std.debug.print("MISMATCH: {}-{:0>2}-{:0>2}: current={} benjoffe={}\n", .{ year, month, day, current, benjoffe });
}
}
}
}
}
if (mismatches == 0) {
std.debug.print("All tests passed!\n", .{});
} else {
std.debug.print("Total mismatches: {}\n", .{mismatches});
}
}
================================================
FILE: bench/test_leap.zig
================================================
const std = @import("std");
fn isLeapYearCurrent(year: i32) bool {
const d: i32 = if (@mod(year, 100) != 0) 4 else 16;
return (year & (d - 1)) == 0;
}
fn isLeapYearBenjoffe(year: i32) bool {
const cen_bias: u32 = 2147483600;
const cen_mul: u32 = 42949673;
const cen_cutoff: u32 = 171798692;
const a: u32 = @bitCast(year +% @as(i32, @bitCast(cen_bias)));
const low: u32 = a *% cen_mul;
const is_likely_cen = low < cen_cutoff;
const mask: u5 = if (is_likely_cen) 15 else 3;
return (year & mask) == 0;
}
pub fn main() void {
var mismatches: u32 = 0;
// Test full range of interesting years
var year: i32 = -1_000_000;
while (year <= 1_000_000) : (year += 1) {
const current = isLeapYearCurrent(year);
const benjoffe = isLeapYearBenjoffe(year);
if (current != benjoffe) {
mismatches += 1;
if (mismatches <= 10) {
std.debug.print("MISMATCH: year={}: current={} benjoffe={}\n", .{ year, current, benjoffe });
}
}
}
if (mismatches == 0) {
std.debug.print("All tests passed!\n", .{});
} else {
std.debug.print("Total mismatches: {}\n", .{mismatches});
}
}
================================================
FILE: build.zig
================================================
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const root_module = b.addModule("zeit", .{
.root_source_file = b.path("src/zeit.zig"),
.target = target,
.optimize = optimize,
});
const lib_unit_tests = b.addTest(.{
.root_module = root_module,
});
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
const gen_step = b.step("generate", "Update timezone names");
const gen = b.addExecutable(.{
.name = "generate",
.root_module = b.createModule(.{
.root_source_file = b.path("gen/main.zig"),
.target = target,
.optimize = optimize,
}),
});
const fmt = b.addFmt(
.{ .paths = &.{"src/location.zig"} },
);
const gen_run = b.addRunArtifact(gen);
fmt.step.dependOn(&gen_run.step);
gen_step.dependOn(&fmt.step);
// Docs
{
const docs_step = b.step("docs", "Build the zeit docs");
const docs_obj = b.addObject(.{
.name = "zeit",
.root_module = b.createModule(.{
.root_source_file = b.path("src/zeit.zig"),
.target = target,
.optimize = optimize,
}),
});
const docs = docs_obj.getEmittedDocs();
docs_step.dependOn(&b.addInstallDirectory(.{
.source_dir = docs,
.install_dir = .prefix,
.install_subdir = "docs",
}).step);
}
}
================================================
FILE: build.zig.zon
================================================
.{
.name = .zeit,
.fingerprint = 0x888df3e4939b8ee4,
.version = "0.6.0",
.dependencies = .{},
.paths = .{
"LICENSE",
"build.zig",
"build.zig.zon",
"src",
},
.minimum_zig_version = "0.16.0-dev.2533+355c62600",
}
================================================
FILE: gen/main.zig
================================================
//! Generates zig code for well-known timezones. Makes an enum of the "posix" style name
//! ("America/Chicago") and a function to return the timezone name as text. The name as text is
//! portable by platform: on Windows it will return the Windows name of this timezone
//!
//! Source data available at https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const allocator = init.arena.allocator();
var threaded: std.Io.Threaded = .init(allocator, .{});
defer threaded.deinit();
const io = threaded.io();
//const allocator = std.heap.page_allocator;
const data = @embedFile("windowsZones.xml");
var zones: std.ArrayList(MapZone) = .empty;
var read_idx: usize = 0;
while (read_idx < data.len) {
const eol = std.mem.indexOfScalarPos(u8, data, read_idx, '\n') orelse data.len;
defer read_idx = eol + 1;
const input_line = data[read_idx..eol];
const line = std.mem.trimEnd(u8, std.mem.trim(u8, input_line, " \t<>"), "/");
if (!std.mem.startsWith(u8, line, "mapZone")) continue;
var idx: usize = 0;
const windows = blk: {
idx = std.mem.indexOfScalarPos(u8, line, idx, '"') orelse unreachable;
const start = idx + 1;
idx = std.mem.indexOfScalarPos(u8, line, start, '"') orelse unreachable;
const end = idx;
break :blk line[start..end];
};
const territory = blk: {
idx = std.mem.indexOfScalarPos(u8, line, idx + 1, '"') orelse unreachable;
const start = idx + 1;
idx = std.mem.indexOfScalarPos(u8, line, start, '"') orelse unreachable;
const end = idx;
break :blk line[start..end];
};
const posix = blk: {
idx = std.mem.indexOfScalarPos(u8, line, idx + 1, '"') orelse unreachable;
const start = idx + 1;
idx = std.mem.indexOfScalarPos(u8, line, start, '"') orelse unreachable;
const end = idx;
break :blk line[start..end];
};
var iter = std.mem.splitScalar(u8, posix, ' ');
while (iter.next()) |psx| {
const map_zone: MapZone = .{
.windows = windows,
.territory = territory,
.posix = psx,
};
if (psx.len == 0) continue;
for (zones.items) |item| {
if (std.mem.eql(u8, item.windows, map_zone.windows) and
std.mem.eql(u8, item.posix, map_zone.posix)) break;
} else try zones.append(allocator, map_zone);
}
}
std.mem.sort(MapZone, zones.items, {}, lessThan);
var out = try std.Io.Dir.createFile(.cwd(), io, "src/location.zig", .{}); //.cwd().createFile("src/location.zig", .{});
defer out.close(io);
var output_buffer: [2048]u8 = undefined;
var writer = out.writer(io, &output_buffer);
try writeFile(zones.items, &writer.interface);
}
fn lessThan(_: void, lhs: MapZone, rhs: MapZone) bool {
return std.mem.order(u8, lhs.posix, rhs.posix).compare(.lt);
}
const MapZone = struct {
windows: []const u8,
territory: []const u8,
posix: []const u8,
};
fn writeFile(items: []const MapZone, writer: *std.Io.Writer) !void {
try writer.writeAll(
\\//!This file is generated. Do not edit directly! Run `zig build generate` to update after obtaining
\\//!the latest dataset.
\\
\\const builtin = @import("builtin");
\\pub const Location = enum {
\\
);
for (items) |item| {
try writer.print("@\"{s}\",\n", .{item.posix});
}
try writer.writeAll("\n");
try writer.writeAll(
\\ pub fn asText(self: Location) []const u8 {
\\ switch (builtin.os.tag) {
\\ .windows => {},
\\ else => return @tagName(self),
\\ }
);
try writer.writeAll(" return switch (self) {\n");
for (items) |item| {
try writer.print(".@\"{s}\" => \"{s}\",\n", .{ item.posix, item.windows });
}
try writer.writeAll("};}};");
try writer.flush(); // don't forget to flush!
}
================================================
FILE: gen/windowsZones.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
For terms of use, see http://www.unicode.org/copyright.html
-->
<supplementalData>
<version number="$Revision$"/>
<windowsZones>
<mapTimezones otherVersion="7e11800" typeVersion="2021a">
<!-- (UTC-12:00) International Date Line West -->
<mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
<mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
<!-- (UTC-11:00) Coordinated Universal Time-11 -->
<mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
<mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
<mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
<mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
<mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
<!-- (UTC-10:00) Aleutian Islands -->
<mapZone other="Aleutian Standard Time" territory="001" type="America/Adak"/>
<mapZone other="Aleutian Standard Time" territory="US" type="America/Adak"/>
<!-- (UTC-10:00) Hawaii -->
<mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
<mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
<mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
<!-- (UTC-09:30) Marquesas Islands -->
<mapZone other="Marquesas Standard Time" territory="001" type="Pacific/Marquesas"/>
<mapZone other="Marquesas Standard Time" territory="PF" type="Pacific/Marquesas"/>
<!-- (UTC-09:00) Alaska -->
<mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
<mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Metlakatla America/Nome America/Sitka America/Yakutat"/>
<!-- (UTC-09:00) Coordinated Universal Time-09 -->
<mapZone other="UTC-09" territory="001" type="Etc/GMT+9"/>
<mapZone other="UTC-09" territory="PF" type="Pacific/Gambier"/>
<mapZone other="UTC-09" territory="ZZ" type="Etc/GMT+9"/>
<!-- (UTC-08:00) Baja California -->
<mapZone other="Pacific Standard Time (Mexico)" territory="001" type="America/Tijuana"/>
<mapZone other="Pacific Standard Time (Mexico)" territory="MX" type="America/Tijuana"/>
<!-- (UTC-08:00) Coordinated Universal Time-08 -->
<mapZone other="UTC-08" territory="001" type="Etc/GMT+8"/>
<mapZone other="UTC-08" territory="PN" type="Pacific/Pitcairn"/>
<mapZone other="UTC-08" territory="ZZ" type="Etc/GMT+8"/>
<!-- (UTC-08:00) Pacific Time (US & Canada) -->
<mapZone other="Pacific Standard Time" territory="001" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="CA" type="America/Vancouver"/>
<mapZone other="Pacific Standard Time" territory="US" type="America/Los_Angeles"/>
<!-- (UTC-07:00) Arizona -->
<mapZone other="US Mountain Standard Time" territory="001" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="CA" type="America/Creston America/Dawson_Creek America/Fort_Nelson"/>
<mapZone other="US Mountain Standard Time" territory="MX" type="America/Hermosillo"/>
<mapZone other="US Mountain Standard Time" territory="US" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="ZZ" type="Etc/GMT+7"/>
<!-- (UTC-07:00) Chihuahua, La Paz, Mazatlan -->
<mapZone other="Mountain Standard Time (Mexico)" territory="001" type="America/Mazatlan"/>
<mapZone other="Mountain Standard Time (Mexico)" territory="MX" type="America/Mazatlan"/>
<!-- (UTC-07:00) Mountain Time (US & Canada) -->
<mapZone other="Mountain Standard Time" territory="001" type="America/Denver"/>
<mapZone other="Mountain Standard Time" territory="CA" type="America/Edmonton America/Cambridge_Bay America/Inuvik"/>
<mapZone other="Mountain Standard Time" territory="MX" type="America/Ciudad_Juarez"/>
<mapZone other="Mountain Standard Time" territory="US" type="America/Denver America/Boise"/>
<!-- (UTC-07:00) Yukon -->
<mapZone other="Yukon Standard Time" territory="001" type="America/Whitehorse"/>
<mapZone other="Yukon Standard Time" territory="CA" type="America/Whitehorse America/Dawson"/>
<!-- (UTC-06:00) Central America -->
<mapZone other="Central America Standard Time" territory="001" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="BZ" type="America/Belize"/>
<mapZone other="Central America Standard Time" territory="CR" type="America/Costa_Rica"/>
<mapZone other="Central America Standard Time" territory="EC" type="Pacific/Galapagos"/>
<mapZone other="Central America Standard Time" territory="GT" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="HN" type="America/Tegucigalpa"/>
<mapZone other="Central America Standard Time" territory="NI" type="America/Managua"/>
<mapZone other="Central America Standard Time" territory="SV" type="America/El_Salvador"/>
<mapZone other="Central America Standard Time" territory="ZZ" type="Etc/GMT+6"/>
<!-- (UTC-06:00) Central Time (US & Canada) -->
<mapZone other="Central Standard Time" territory="001" type="America/Chicago"/>
<mapZone other="Central Standard Time" territory="CA" type="America/Winnipeg America/Rankin_Inlet America/Resolute"/>
<mapZone other="Central Standard Time" territory="MX" type="America/Matamoros America/Ojinaga"/>
<mapZone other="Central Standard Time" territory="US" type="America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem"/>
<!-- (UTC-06:00) Easter Island -->
<mapZone other="Easter Island Standard Time" territory="001" type="Pacific/Easter"/>
<mapZone other="Easter Island Standard Time" territory="CL" type="Pacific/Easter"/>
<!-- (UTC-06:00) Guadalajara, Mexico City, Monterrey -->
<mapZone other="Central Standard Time (Mexico)" territory="001" type="America/Mexico_City"/>
<mapZone other="Central Standard Time (Mexico)" territory="MX" type="America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey America/Chihuahua "/>
<!-- (UTC-06:00) Saskatchewan -->
<mapZone other="Canada Central Standard Time" territory="001" type="America/Regina"/>
<mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
<!-- (UTC-05:00) Bogota, Lima, Quito, Rio Branco -->
<mapZone other="SA Pacific Standard Time" territory="001" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="BR" type="America/Rio_Branco America/Eirunepe"/>
<mapZone other="SA Pacific Standard Time" territory="CA" type="America/Coral_Harbour"/>
<mapZone other="SA Pacific Standard Time" territory="CO" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="EC" type="America/Guayaquil"/>
<mapZone other="SA Pacific Standard Time" territory="JM" type="America/Jamaica"/>
<mapZone other="SA Pacific Standard Time" territory="KY" type="America/Cayman"/>
<mapZone other="SA Pacific Standard Time" territory="PA" type="America/Panama"/>
<mapZone other="SA Pacific Standard Time" territory="PE" type="America/Lima"/>
<mapZone other="SA Pacific Standard Time" territory="ZZ" type="Etc/GMT+5"/>
<!-- (UTC-05:00) Chetumal -->
<mapZone other="Eastern Standard Time (Mexico)" territory="001" type="America/Cancun"/>
<mapZone other="Eastern Standard Time (Mexico)" territory="MX" type="America/Cancun"/>
<!-- (UTC-05:00) Eastern Time (US & Canada) -->
<mapZone other="Eastern Standard Time" territory="001" type="America/New_York"/>
<mapZone other="Eastern Standard Time" territory="BS" type="America/Nassau"/>
<mapZone other="Eastern Standard Time" territory="CA" type="America/Toronto America/Iqaluit"/>
<mapZone other="Eastern Standard Time" territory="US" type="America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville"/>
<!-- (UTC-05:00) Haiti -->
<mapZone other="Haiti Standard Time" territory="001" type="America/Port-au-Prince"/>
<mapZone other="Haiti Standard Time" territory="HT" type="America/Port-au-Prince"/>
<!-- (UTC-05:00) Havana -->
<mapZone other="Cuba Standard Time" territory="001" type="America/Havana"/>
<mapZone other="Cuba Standard Time" territory="CU" type="America/Havana"/>
<!-- (UTC-05:00) Indiana (East) -->
<mapZone other="US Eastern Standard Time" territory="001" type="America/Indianapolis"/>
<mapZone other="US Eastern Standard Time" territory="US" type="America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"/>
<!-- (UTC-05:00) Turks and Caicos -->
<mapZone other="Turks And Caicos Standard Time" territory="001" type="America/Grand_Turk"/>
<mapZone other="Turks And Caicos Standard Time" territory="TC" type="America/Grand_Turk"/>
<!-- (UTC-04:00) Asuncion -->
<mapZone other="Paraguay Standard Time" territory="001" type="America/Asuncion"/>
<mapZone other="Paraguay Standard Time" territory="PY" type="America/Asuncion"/>
<!-- (UTC-04:00) Atlantic Time (Canada) -->
<mapZone other="Atlantic Standard Time" territory="001" type="America/Halifax"/>
<mapZone other="Atlantic Standard Time" territory="BM" type="Atlantic/Bermuda"/>
<mapZone other="Atlantic Standard Time" territory="CA" type="America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton"/>
<mapZone other="Atlantic Standard Time" territory="GL" type="America/Thule"/>
<!-- (UTC-04:00) Caracas -->
<mapZone other="Venezuela Standard Time" territory="001" type="America/Caracas"/>
<mapZone other="Venezuela Standard Time" territory="VE" type="America/Caracas"/>
<!-- (UTC-04:00) Cuiaba -->
<mapZone other="Central Brazilian Standard Time" territory="001" type="America/Cuiaba"/>
<mapZone other="Central Brazilian Standard Time" territory="BR" type="America/Cuiaba America/Campo_Grande"/>
<!-- (UTC-04:00) Georgetown, La Paz, Manaus, San Juan -->
<mapZone other="SA Western Standard Time" territory="001" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="AG" type="America/Antigua"/>
<mapZone other="SA Western Standard Time" territory="AI" type="America/Anguilla"/>
<mapZone other="SA Western Standard Time" territory="AW" type="America/Aruba"/>
<mapZone other="SA Western Standard Time" territory="BB" type="America/Barbados"/>
<mapZone other="SA Western Standard Time" territory="BL" type="America/St_Barthelemy"/>
<mapZone other="SA Western Standard Time" territory="BO" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="BQ" type="America/Kralendijk"/>
<mapZone other="SA Western Standard Time" territory="BR" type="America/Manaus America/Boa_Vista America/Porto_Velho"/>
<mapZone other="SA Western Standard Time" territory="CA" type="America/Blanc-Sablon"/>
<mapZone other="SA Western Standard Time" territory="CW" type="America/Curacao"/>
<mapZone other="SA Western Standard Time" territory="DM" type="America/Dominica"/>
<mapZone other="SA Western Standard Time" territory="DO" type="America/Santo_Domingo"/>
<mapZone other="SA Western Standard Time" territory="GD" type="America/Grenada"/>
<mapZone other="SA Western Standard Time" territory="GP" type="America/Guadeloupe"/>
<mapZone other="SA Western Standard Time" territory="GY" type="America/Guyana"/>
<mapZone other="SA Western Standard Time" territory="KN" type="America/St_Kitts"/>
<mapZone other="SA Western Standard Time" territory="LC" type="America/St_Lucia"/>
<mapZone other="SA Western Standard Time" territory="MF" type="America/Marigot"/>
<mapZone other="SA Western Standard Time" territory="MQ" type="America/Martinique"/>
<mapZone other="SA Western Standard Time" territory="MS" type="America/Montserrat"/>
<mapZone other="SA Western Standard Time" territory="PR" type="America/Puerto_Rico"/>
<mapZone other="SA Western Standard Time" territory="SX" type="America/Lower_Princes"/>
<mapZone other="SA Western Standard Time" territory="TT" type="America/Port_of_Spain"/>
<mapZone other="SA Western Standard Time" territory="VC" type="America/St_Vincent"/>
<mapZone other="SA Western Standard Time" territory="VG" type="America/Tortola"/>
<mapZone other="SA Western Standard Time" territory="VI" type="America/St_Thomas"/>
<mapZone other="SA Western Standard Time" territory="ZZ" type="Etc/GMT+4"/>
<!-- (UTC-04:00) Santiago -->
<mapZone other="Pacific SA Standard Time" territory="001" type="America/Santiago"/>
<mapZone other="Pacific SA Standard Time" territory="CL" type="America/Santiago"/>
<!-- (UTC-03:30) Newfoundland -->
<mapZone other="Newfoundland Standard Time" territory="001" type="America/St_Johns"/>
<mapZone other="Newfoundland Standard Time" territory="CA" type="America/St_Johns"/>
<!-- (UTC-03:00) Araguaina -->
<mapZone other="Tocantins Standard Time" territory="001" type="America/Araguaina"/>
<mapZone other="Tocantins Standard Time" territory="BR" type="America/Araguaina"/>
<!-- (UTC-03:00) Brasilia -->
<mapZone other="E. South America Standard Time" territory="001" type="America/Sao_Paulo"/>
<mapZone other="E. South America Standard Time" territory="BR" type="America/Sao_Paulo"/>
<!-- (UTC-03:00) Cayenne, Fortaleza -->
<mapZone other="SA Eastern Standard Time" territory="001" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="AQ" type="Antarctica/Rothera Antarctica/Palmer"/>
<mapZone other="SA Eastern Standard Time" territory="BR" type="America/Fortaleza America/Belem America/Maceio America/Recife America/Santarem"/>
<mapZone other="SA Eastern Standard Time" territory="FK" type="Atlantic/Stanley"/>
<mapZone other="SA Eastern Standard Time" territory="GF" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="SR" type="America/Paramaribo"/>
<mapZone other="SA Eastern Standard Time" territory="ZZ" type="Etc/GMT+3"/>
<!-- (UTC-03:00) City of Buenos Aires -->
<mapZone other="Argentina Standard Time" territory="001" type="America/Buenos_Aires"/>
<mapZone other="Argentina Standard Time" territory="AR" type="America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba America/Jujuy America/Mendoza"/>
<!-- (UTC-03:00) Greenland -->
<mapZone other="Greenland Standard Time" territory="001" type="America/Godthab"/>
<mapZone other="Greenland Standard Time" territory="GL" type="America/Godthab"/>
<!-- (UTC-03:00) Montevideo -->
<mapZone other="Montevideo Standard Time" territory="001" type="America/Montevideo"/>
<mapZone other="Montevideo Standard Time" territory="UY" type="America/Montevideo"/>
<!-- (UTC-03:00) Punta Arenas -->
<mapZone other="Magallanes Standard Time" territory="001" type="America/Punta_Arenas"/>
<mapZone other="Magallanes Standard Time" territory="CL" type="America/Punta_Arenas"/>
<!-- (UTC-03:00) Saint Pierre and Miquelon -->
<mapZone other="Saint Pierre Standard Time" territory="001" type="America/Miquelon"/>
<mapZone other="Saint Pierre Standard Time" territory="PM" type="America/Miquelon"/>
<!-- (UTC-03:00) Salvador -->
<mapZone other="Bahia Standard Time" territory="001" type="America/Bahia"/>
<mapZone other="Bahia Standard Time" territory="BR" type="America/Bahia"/>
<!-- (UTC-02:00) Coordinated Universal Time-02 -->
<mapZone other="UTC-02" territory="001" type="Etc/GMT+2"/>
<mapZone other="UTC-02" territory="BR" type="America/Noronha"/>
<mapZone other="UTC-02" territory="GS" type="Atlantic/South_Georgia"/>
<mapZone other="UTC-02" territory="ZZ" type="Etc/GMT+2"/>
<!-- (UTC-01:00) Azores -->
<mapZone other="Azores Standard Time" territory="001" type="Atlantic/Azores"/>
<mapZone other="Azores Standard Time" territory="GL" type="America/Scoresbysund"/>
<mapZone other="Azores Standard Time" territory="PT" type="Atlantic/Azores"/>
<!-- (UTC-01:00) Cabo Verde Is. -->
<mapZone other="Cape Verde Standard Time" territory="001" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="CV" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="ZZ" type="Etc/GMT+1"/>
<!-- (UTC) Coordinated Universal Time -->
<mapZone other="UTC" territory="001" type="Etc/UTC"/>
<mapZone other="UTC" territory="ZZ" type="Etc/UTC Etc/GMT"/>
<!-- (UTC+00:00) Dublin, Edinburgh, Lisbon, London -->
<mapZone other="GMT Standard Time" territory="001" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="ES" type="Atlantic/Canary"/>
<mapZone other="GMT Standard Time" territory="FO" type="Atlantic/Faeroe"/>
<mapZone other="GMT Standard Time" territory="GB" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="GG" type="Europe/Guernsey"/>
<mapZone other="GMT Standard Time" territory="IE" type="Europe/Dublin"/>
<mapZone other="GMT Standard Time" territory="IM" type="Europe/Isle_of_Man"/>
<mapZone other="GMT Standard Time" territory="JE" type="Europe/Jersey"/>
<mapZone other="GMT Standard Time" territory="PT" type="Europe/Lisbon Atlantic/Madeira"/>
<!-- (UTC+00:00) Monrovia, Reykjavik -->
<mapZone other="Greenwich Standard Time" territory="001" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="BF" type="Africa/Ouagadougou"/>
<mapZone other="Greenwich Standard Time" territory="CI" type="Africa/Abidjan"/>
<mapZone other="Greenwich Standard Time" territory="GH" type="Africa/Accra"/>
<mapZone other="Greenwich Standard Time" territory="GL" type="America/Danmarkshavn"/>
<mapZone other="Greenwich Standard Time" territory="GM" type="Africa/Banjul"/>
<mapZone other="Greenwich Standard Time" territory="GN" type="Africa/Conakry"/>
<mapZone other="Greenwich Standard Time" territory="GW" type="Africa/Bissau"/>
<mapZone other="Greenwich Standard Time" territory="IS" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="LR" type="Africa/Monrovia"/>
<mapZone other="Greenwich Standard Time" territory="ML" type="Africa/Bamako"/>
<mapZone other="Greenwich Standard Time" territory="MR" type="Africa/Nouakchott"/>
<mapZone other="Greenwich Standard Time" territory="SH" type="Atlantic/St_Helena"/>
<mapZone other="Greenwich Standard Time" territory="SL" type="Africa/Freetown"/>
<mapZone other="Greenwich Standard Time" territory="SN" type="Africa/Dakar"/>
<mapZone other="Greenwich Standard Time" territory="TG" type="Africa/Lome"/>
<!-- (UTC+00:00) Sao Tome -->
<mapZone other="Sao Tome Standard Time" territory="001" type="Africa/Sao_Tome"/>
<mapZone other="Sao Tome Standard Time" territory="ST" type="Africa/Sao_Tome"/>
<!-- (UTC+01:00) Casablanca -->
<mapZone other="Morocco Standard Time" territory="001" type="Africa/Casablanca"/>
<mapZone other="Morocco Standard Time" territory="EH" type="Africa/El_Aaiun"/>
<mapZone other="Morocco Standard Time" territory="MA" type="Africa/Casablanca"/>
<!-- (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna -->
<mapZone other="W. Europe Standard Time" territory="001" type="Europe/Berlin"/>
<mapZone other="W. Europe Standard Time" territory="AD" type="Europe/Andorra"/>
<mapZone other="W. Europe Standard Time" territory="AT" type="Europe/Vienna"/>
<mapZone other="W. Europe Standard Time" territory="CH" type="Europe/Zurich"/>
<mapZone other="W. Europe Standard Time" territory="DE" type="Europe/Berlin Europe/Busingen"/>
<mapZone other="W. Europe Standard Time" territory="GI" type="Europe/Gibraltar"/>
<mapZone other="W. Europe Standard Time" territory="IT" type="Europe/Rome"/>
<mapZone other="W. Europe Standard Time" territory="LI" type="Europe/Vaduz"/>
<mapZone other="W. Europe Standard Time" territory="LU" type="Europe/Luxembourg"/>
<mapZone other="W. Europe Standard Time" territory="MC" type="Europe/Monaco"/>
<mapZone other="W. Europe Standard Time" territory="MT" type="Europe/Malta"/>
<mapZone other="W. Europe Standard Time" territory="NL" type="Europe/Amsterdam"/>
<mapZone other="W. Europe Standard Time" territory="NO" type="Europe/Oslo"/>
<mapZone other="W. Europe Standard Time" territory="SE" type="Europe/Stockholm"/>
<mapZone other="W. Europe Standard Time" territory="SJ" type="Arctic/Longyearbyen"/>
<mapZone other="W. Europe Standard Time" territory="SM" type="Europe/San_Marino"/>
<mapZone other="W. Europe Standard Time" territory="VA" type="Europe/Vatican"/>
<!-- (UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague -->
<mapZone other="Central Europe Standard Time" territory="001" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="AL" type="Europe/Tirane"/>
<mapZone other="Central Europe Standard Time" territory="CZ" type="Europe/Prague"/>
<mapZone other="Central Europe Standard Time" territory="HU" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="ME" type="Europe/Podgorica"/>
<mapZone other="Central Europe Standard Time" territory="RS" type="Europe/Belgrade"/>
<mapZone other="Central Europe Standard Time" territory="SI" type="Europe/Ljubljana"/>
<mapZone other="Central Europe Standard Time" territory="SK" type="Europe/Bratislava"/>
<!-- (UTC+01:00) Brussels, Copenhagen, Madrid, Paris -->
<mapZone other="Romance Standard Time" territory="001" type="Europe/Paris"/>
<mapZone other="Romance Standard Time" territory="BE" type="Europe/Brussels"/>
<mapZone other="Romance Standard Time" territory="DK" type="Europe/Copenhagen"/>
<mapZone other="Romance Standard Time" territory="ES" type="Europe/Madrid Africa/Ceuta"/>
<mapZone other="Romance Standard Time" territory="FR" type="Europe/Paris"/>
<!-- (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb -->
<mapZone other="Central European Standard Time" territory="001" type="Europe/Warsaw"/>
<mapZone other="Central European Standard Time" territory="BA" type="Europe/Sarajevo"/>
<mapZone other="Central European Standard Time" territory="HR" type="Europe/Zagreb"/>
<mapZone other="Central European Standard Time" territory="MK" type="Europe/Skopje"/>
<mapZone other="Central European Standard Time" territory="PL" type="Europe/Warsaw"/>
<!-- (UTC+01:00) West Central Africa -->
<mapZone other="W. Central Africa Standard Time" territory="001" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="AO" type="Africa/Luanda"/>
<mapZone other="W. Central Africa Standard Time" territory="BJ" type="Africa/Porto-Novo"/>
<mapZone other="W. Central Africa Standard Time" territory="CD" type="Africa/Kinshasa"/>
<mapZone other="W. Central Africa Standard Time" territory="CF" type="Africa/Bangui"/>
<mapZone other="W. Central Africa Standard Time" territory="CG" type="Africa/Brazzaville"/>
<mapZone other="W. Central Africa Standard Time" territory="CM" type="Africa/Douala"/>
<mapZone other="W. Central Africa Standard Time" territory="DZ" type="Africa/Algiers"/>
<mapZone other="W. Central Africa Standard Time" territory="GA" type="Africa/Libreville"/>
<mapZone other="W. Central Africa Standard Time" territory="GQ" type="Africa/Malabo"/>
<mapZone other="W. Central Africa Standard Time" territory="NE" type="Africa/Niamey"/>
<mapZone other="W. Central Africa Standard Time" territory="NG" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="TD" type="Africa/Ndjamena"/>
<mapZone other="W. Central Africa Standard Time" territory="TN" type="Africa/Tunis"/>
<mapZone other="W. Central Africa Standard Time" territory="ZZ" type="Etc/GMT-1"/>
<!-- (UTC+02:00) Amman -->
<mapZone other="Jordan Standard Time" territory="001" type="Asia/Amman"/>
<mapZone other="Jordan Standard Time" territory="JO" type="Asia/Amman"/>
<!-- (UTC+02:00) Athens, Bucharest -->
<mapZone other="GTB Standard Time" territory="001" type="Europe/Bucharest"/>
<mapZone other="GTB Standard Time" territory="CY" type="Asia/Nicosia Asia/Famagusta"/>
<mapZone other="GTB Standard Time" territory="GR" type="Europe/Athens"/>
<mapZone other="GTB Standard Time" territory="RO" type="Europe/Bucharest"/>
<!-- (UTC+02:00) Beirut -->
<mapZone other="Middle East Standard Time" territory="001" type="Asia/Beirut"/>
<mapZone other="Middle East Standard Time" territory="LB" type="Asia/Beirut"/>
<!-- (UTC+02:00) Cairo -->
<mapZone other="Egypt Standard Time" territory="001" type="Africa/Cairo"/>
<mapZone other="Egypt Standard Time" territory="EG" type="Africa/Cairo"/>
<!-- (UTC+02:00) Chisinau -->
<mapZone other="E. Europe Standard Time" territory="001" type="Europe/Chisinau"/>
<mapZone other="E. Europe Standard Time" territory="MD" type="Europe/Chisinau"/>
<!-- (UTC+02:00) Damascus -->
<mapZone other="Syria Standard Time" territory="001" type="Asia/Damascus"/>
<mapZone other="Syria Standard Time" territory="SY" type="Asia/Damascus"/>
<!-- (UTC+02:00) Gaza, Hebron -->
<mapZone other="West Bank Standard Time" territory="001" type="Asia/Hebron"/>
<mapZone other="West Bank Standard Time" territory="PS" type="Asia/Hebron Asia/Gaza"/>
<!-- (UTC+02:00) Harare, Pretoria -->
<mapZone other="South Africa Standard Time" territory="001" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="BI" type="Africa/Bujumbura"/>
<mapZone other="South Africa Standard Time" territory="BW" type="Africa/Gaborone"/>
<mapZone other="South Africa Standard Time" territory="CD" type="Africa/Lubumbashi"/>
<mapZone other="South Africa Standard Time" territory="LS" type="Africa/Maseru"/>
<mapZone other="South Africa Standard Time" territory="MW" type="Africa/Blantyre"/>
<mapZone other="South Africa Standard Time" territory="MZ" type="Africa/Maputo"/>
<mapZone other="South Africa Standard Time" territory="RW" type="Africa/Kigali"/>
<mapZone other="South Africa Standard Time" territory="SZ" type="Africa/Mbabane"/>
<mapZone other="South Africa Standard Time" territory="ZA" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="ZM" type="Africa/Lusaka"/>
<mapZone other="South Africa Standard Time" territory="ZW" type="Africa/Harare"/>
<mapZone other="South Africa Standard Time" territory="ZZ" type="Etc/GMT-2"/>
<!-- (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius -->
<mapZone other="FLE Standard Time" territory="001" type="Europe/Kiev"/>
<mapZone other="FLE Standard Time" territory="AX" type="Europe/Mariehamn"/>
<mapZone other="FLE Standard Time" territory="BG" type="Europe/Sofia"/>
<mapZone other="FLE Standard Time" territory="EE" type="Europe/Tallinn"/>
<mapZone other="FLE Standard Time" territory="FI" type="Europe/Helsinki"/>
<mapZone other="FLE Standard Time" territory="LT" type="Europe/Vilnius"/>
<mapZone other="FLE Standard Time" territory="LV" type="Europe/Riga"/>
<mapZone other="FLE Standard Time" territory="UA" type="Europe/Kiev"/>
<!-- (UTC+02:00) Jerusalem -->
<mapZone other="Israel Standard Time" territory="001" type="Asia/Jerusalem"/>
<mapZone other="Israel Standard Time" territory="IL" type="Asia/Jerusalem"/>
<!-- (UTC+02:00) Juba -->
<mapZone other="South Sudan Standard Time" territory="001" type="Africa/Juba"/>
<mapZone other="South Sudan Standard Time" territory="SS" type="Africa/Juba"/>
<!-- (UTC+02:00) Kaliningrad -->
<mapZone other="Kaliningrad Standard Time" territory="001" type="Europe/Kaliningrad"/>
<mapZone other="Kaliningrad Standard Time" territory="RU" type="Europe/Kaliningrad"/>
<!-- (UTC+02:00) Khartoum -->
<mapZone other="Sudan Standard Time" territory="001" type="Africa/Khartoum"/>
<mapZone other="Sudan Standard Time" territory="SD" type="Africa/Khartoum"/>
<!-- (UTC+02:00) Tripoli -->
<mapZone other="Libya Standard Time" territory="001" type="Africa/Tripoli"/>
<mapZone other="Libya Standard Time" territory="LY" type="Africa/Tripoli"/>
<!-- (UTC+02:00) Windhoek -->
<mapZone other="Namibia Standard Time" territory="001" type="Africa/Windhoek"/>
<mapZone other="Namibia Standard Time" territory="NA" type="Africa/Windhoek"/>
<!-- (UTC+03:00) Baghdad -->
<mapZone other="Arabic Standard Time" territory="001" type="Asia/Baghdad"/>
<mapZone other="Arabic Standard Time" territory="IQ" type="Asia/Baghdad"/>
<!-- (UTC+03:00) Istanbul -->
<mapZone other="Turkey Standard Time" territory="001" type="Europe/Istanbul"/>
<mapZone other="Turkey Standard Time" territory="TR" type="Europe/Istanbul"/>
<!-- (UTC+03:00) Kuwait, Riyadh -->
<mapZone other="Arab Standard Time" territory="001" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="BH" type="Asia/Bahrain"/>
<mapZone other="Arab Standard Time" territory="KW" type="Asia/Kuwait"/>
<mapZone other="Arab Standard Time" territory="QA" type="Asia/Qatar"/>
<mapZone other="Arab Standard Time" territory="SA" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="YE" type="Asia/Aden"/>
<!-- (UTC+03:00) Minsk -->
<mapZone other="Belarus Standard Time" territory="001" type="Europe/Minsk"/>
<mapZone other="Belarus Standard Time" territory="BY" type="Europe/Minsk"/>
<!-- (UTC+03:00) Moscow, St. Petersburg -->
<mapZone other="Russian Standard Time" territory="001" type="Europe/Moscow"/>
<mapZone other="Russian Standard Time" territory="RU" type="Europe/Moscow Europe/Kirov"/>
<mapZone other="Russian Standard Time" territory="UA" type="Europe/Simferopol"/>
<!-- (UTC+03:00) Nairobi -->
<mapZone other="E. Africa Standard Time" territory="001" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="AQ" type="Antarctica/Syowa"/>
<mapZone other="E. Africa Standard Time" territory="DJ" type="Africa/Djibouti"/>
<mapZone other="E. Africa Standard Time" territory="ER" type="Africa/Asmera"/>
<mapZone other="E. Africa Standard Time" territory="ET" type="Africa/Addis_Ababa"/>
<mapZone other="E. Africa Standard Time" territory="KE" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="KM" type="Indian/Comoro"/>
<mapZone other="E. Africa Standard Time" territory="MG" type="Indian/Antananarivo"/>
<mapZone other="E. Africa Standard Time" territory="SO" type="Africa/Mogadishu"/>
<mapZone other="E. Africa Standard Time" territory="TZ" type="Africa/Dar_es_Salaam"/>
<mapZone other="E. Africa Standard Time" territory="UG" type="Africa/Kampala"/>
<mapZone other="E. Africa Standard Time" territory="YT" type="Indian/Mayotte"/>
<mapZone other="E. Africa Standard Time" territory="ZZ" type="Etc/GMT-3"/>
<!-- (UTC+03:30) Tehran -->
<mapZone other="Iran Standard Time" territory="001" type="Asia/Tehran"/>
<mapZone other="Iran Standard Time" territory="IR" type="Asia/Tehran"/>
<!-- (UTC+04:00) Abu Dhabi, Muscat -->
<mapZone other="Arabian Standard Time" territory="001" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="AE" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="OM" type="Asia/Muscat"/>
<mapZone other="Arabian Standard Time" territory="ZZ" type="Etc/GMT-4"/>
<!-- (UTC+04:00) Astrakhan, Ulyanovsk -->
<mapZone other="Astrakhan Standard Time" territory="001" type="Europe/Astrakhan"/>
<mapZone other="Astrakhan Standard Time" territory="RU" type="Europe/Astrakhan Europe/Ulyanovsk"/>
<!-- (UTC+04:00) Baku -->
<mapZone other="Azerbaijan Standard Time" territory="001" type="Asia/Baku"/>
<mapZone other="Azerbaijan Standard Time" territory="AZ" type="Asia/Baku"/>
<!-- (UTC+04:00) Izhevsk, Samara -->
<mapZone other="Russia Time Zone 3" territory="001" type="Europe/Samara"/>
<mapZone other="Russia Time Zone 3" territory="RU" type="Europe/Samara"/>
<!-- (UTC+04:00) Port Louis -->
<mapZone other="Mauritius Standard Time" territory="001" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="MU" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="RE" type="Indian/Reunion"/>
<mapZone other="Mauritius Standard Time" territory="SC" type="Indian/Mahe"/>
<!-- (UTC+04:00) Saratov -->
<mapZone other="Saratov Standard Time" territory="001" type="Europe/Saratov"/>
<mapZone other="Saratov Standard Time" territory="RU" type="Europe/Saratov"/>
<!-- (UTC+04:00) Tbilisi -->
<mapZone other="Georgian Standard Time" territory="001" type="Asia/Tbilisi"/>
<mapZone other="Georgian Standard Time" territory="GE" type="Asia/Tbilisi"/>
<!-- (UTC+04:00) Volgograd -->
<mapZone other="Volgograd Standard Time" territory="001" type="Europe/Volgograd"/>
<mapZone other="Volgograd Standard Time" territory="RU" type="Europe/Volgograd"/>
<!-- (UTC+04:00) Yerevan -->
<mapZone other="Caucasus Standard Time" territory="001" type="Asia/Yerevan"/>
<mapZone other="Caucasus Standard Time" territory="AM" type="Asia/Yerevan"/>
<!-- (UTC+04:30) Kabul -->
<mapZone other="Afghanistan Standard Time" territory="001" type="Asia/Kabul"/>
<mapZone other="Afghanistan Standard Time" territory="AF" type="Asia/Kabul"/>
<!-- (UTC+05:00) Ashgabat, Tashkent -->
<mapZone other="West Asia Standard Time" territory="001" type="Asia/Tashkent"/>
<mapZone other="West Asia Standard Time" territory="AQ" type="Antarctica/Mawson"/>
<!-- Microsoft may create a new zone dedicated for Almaty and Qostanay. -->
<mapZone other="West Asia Standard Time" territory="KZ" type="Asia/Oral Asia/Almaty Asia/Aqtau Asia/Aqtobe Asia/Atyrau Asia/Qostanay"/>
<mapZone other="West Asia Standard Time" territory="MV" type="Indian/Maldives"/>
<mapZone other="West Asia Standard Time" territory="TF" type="Indian/Kerguelen"/>
<mapZone other="West Asia Standard Time" territory="TJ" type="Asia/Dushanbe"/>
<mapZone other="West Asia Standard Time" territory="TM" type="Asia/Ashgabat"/>
<mapZone other="West Asia Standard Time" territory="UZ" type="Asia/Tashkent Asia/Samarkand"/>
<mapZone other="West Asia Standard Time" territory="ZZ" type="Etc/GMT-5"/>
<!-- (UTC+05:00) Ekaterinburg -->
<mapZone other="Ekaterinburg Standard Time" territory="001" type="Asia/Yekaterinburg"/>
<mapZone other="Ekaterinburg Standard Time" territory="RU" type="Asia/Yekaterinburg"/>
<!-- (UTC+05:00) Islamabad, Karachi -->
<mapZone other="Pakistan Standard Time" territory="001" type="Asia/Karachi"/>
<mapZone other="Pakistan Standard Time" territory="PK" type="Asia/Karachi"/>
<!-- (UTC+05:00) Qyzylorda -->
<mapZone other="Qyzylorda Standard Time" territory="001" type="Asia/Qyzylorda"/>
<mapZone other="Qyzylorda Standard Time" territory="KZ" type="Asia/Qyzylorda"/>
<!-- (UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi -->
<mapZone other="India Standard Time" territory="001" type="Asia/Calcutta"/>
<mapZone other="India Standard Time" territory="IN" type="Asia/Calcutta"/>
<!-- (UTC+05:30) Sri Jayawardenepura -->
<mapZone other="Sri Lanka Standard Time" territory="001" type="Asia/Colombo"/>
<mapZone other="Sri Lanka Standard Time" territory="LK" type="Asia/Colombo"/>
<!-- (UTC+05:45) Kathmandu -->
<mapZone other="Nepal Standard Time" territory="001" type="Asia/Katmandu"/>
<mapZone other="Nepal Standard Time" territory="NP" type="Asia/Katmandu"/>
<!-- (UTC+06:00) Astana --> <!-- Microsoft probably keeps Central Asia Standard Time, but change Astana to something else. -->
<mapZone other="Central Asia Standard Time" territory="001" type="Asia/Bishkek"/>
<mapZone other="Central Asia Standard Time" territory="AQ" type="Antarctica/Vostok"/>
<mapZone other="Central Asia Standard Time" territory="CN" type="Asia/Urumqi"/>
<mapZone other="Central Asia Standard Time" territory="IO" type="Indian/Chagos"/>
<mapZone other="Central Asia Standard Time" territory="KG" type="Asia/Bishkek"/>
<mapZone other="Central Asia Standard Time" territory="ZZ" type="Etc/GMT-6"/>
<!-- (UTC+06:00) Dhaka -->
<mapZone other="Bangladesh Standard Time" territory="001" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BD" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BT" type="Asia/Thimphu"/>
<!-- (UTC+06:00) Omsk -->
<mapZone other="Omsk Standard Time" territory="001" type="Asia/Omsk"/>
<mapZone other="Omsk Standard Time" territory="RU" type="Asia/Omsk"/>
<!-- (UTC+06:30) Yangon (Rangoon) -->
<mapZone other="Myanmar Standard Time" territory="001" type="Asia/Rangoon"/>
<mapZone other="Myanmar Standard Time" territory="CC" type="Indian/Cocos"/>
<mapZone other="Myanmar Standard Time" territory="MM" type="Asia/Rangoon"/>
<!-- (UTC+07:00) Bangkok, Hanoi, Jakarta -->
<mapZone other="SE Asia Standard Time" territory="001" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="AQ" type="Antarctica/Davis"/>
<mapZone other="SE Asia Standard Time" territory="CX" type="Indian/Christmas"/>
<mapZone other="SE Asia Standard Time" territory="ID" type="Asia/Jakarta Asia/Pontianak"/>
<mapZone other="SE Asia Standard Time" territory="KH" type="Asia/Phnom_Penh"/>
<mapZone other="SE Asia Standard Time" territory="LA" type="Asia/Vientiane"/>
<mapZone other="SE Asia Standard Time" territory="TH" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="VN" type="Asia/Saigon"/>
<mapZone other="SE Asia Standard Time" territory="ZZ" type="Etc/GMT-7"/>
<!-- (UTC+07:00) Barnaul, Gorno-Altaysk -->
<mapZone other="Altai Standard Time" territory="001" type="Asia/Barnaul"/>
<mapZone other="Altai Standard Time" territory="RU" type="Asia/Barnaul"/>
<!-- (UTC+07:00) Hovd -->
<mapZone other="W. Mongolia Standard Time" territory="001" type="Asia/Hovd"/>
<mapZone other="W. Mongolia Standard Time" territory="MN" type="Asia/Hovd"/>
<!-- (UTC+07:00) Krasnoyarsk -->
<mapZone other="North Asia Standard Time" territory="001" type="Asia/Krasnoyarsk"/>
<mapZone other="North Asia Standard Time" territory="RU" type="Asia/Krasnoyarsk Asia/Novokuznetsk"/>
<!-- (UTC+07:00) Novosibirsk -->
<mapZone other="N. Central Asia Standard Time" territory="001" type="Asia/Novosibirsk"/>
<mapZone other="N. Central Asia Standard Time" territory="RU" type="Asia/Novosibirsk"/>
<!-- (UTC+07:00) Tomsk -->
<mapZone other="Tomsk Standard Time" territory="001" type="Asia/Tomsk"/>
<mapZone other="Tomsk Standard Time" territory="RU" type="Asia/Tomsk"/>
<!-- (UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi -->
<mapZone other="China Standard Time" territory="001" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="CN" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="HK" type="Asia/Hong_Kong"/>
<mapZone other="China Standard Time" territory="MO" type="Asia/Macau"/>
<!-- (UTC+08:00) Irkutsk -->
<mapZone other="North Asia East Standard Time" territory="001" type="Asia/Irkutsk"/>
<mapZone other="North Asia East Standard Time" territory="RU" type="Asia/Irkutsk"/>
<!-- (UTC+08:00) Kuala Lumpur, Singapore -->
<mapZone other="Singapore Standard Time" territory="001" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="BN" type="Asia/Brunei"/>
<mapZone other="Singapore Standard Time" territory="ID" type="Asia/Makassar"/>
<mapZone other="Singapore Standard Time" territory="MY" type="Asia/Kuala_Lumpur Asia/Kuching"/>
<mapZone other="Singapore Standard Time" territory="PH" type="Asia/Manila"/>
<mapZone other="Singapore Standard Time" territory="SG" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="ZZ" type="Etc/GMT-8"/>
<!-- (UTC+08:00) Perth -->
<mapZone other="W. Australia Standard Time" territory="001" type="Australia/Perth"/>
<mapZone other="W. Australia Standard Time" territory="AU" type="Australia/Perth"/>
<!-- (UTC+08:00) Taipei -->
<mapZone other="Taipei Standard Time" territory="001" type="Asia/Taipei"/>
<mapZone other="Taipei Standard Time" territory="TW" type="Asia/Taipei"/>
<!-- (UTC+08:00) Ulaanbaatar -->
<mapZone other="Ulaanbaatar Standard Time" territory="001" type="Asia/Ulaanbaatar"/>
<mapZone other="Ulaanbaatar Standard Time" territory="MN" type="Asia/Ulaanbaatar"/>
<!-- (UTC+08:45) Eucla -->
<mapZone other="Aus Central W. Standard Time" territory="001" type="Australia/Eucla"/>
<mapZone other="Aus Central W. Standard Time" territory="AU" type="Australia/Eucla"/>
<!-- (UTC+09:00) Chita -->
<mapZone other="Transbaikal Standard Time" territory="001" type="Asia/Chita"/>
<mapZone other="Transbaikal Standard Time" territory="RU" type="Asia/Chita"/>
<!-- (UTC+09:00) Osaka, Sapporo, Tokyo -->
<mapZone other="Tokyo Standard Time" territory="001" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="ID" type="Asia/Jayapura"/>
<mapZone other="Tokyo Standard Time" territory="JP" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="PW" type="Pacific/Palau"/>
<mapZone other="Tokyo Standard Time" territory="TL" type="Asia/Dili"/>
<mapZone other="Tokyo Standard Time" territory="ZZ" type="Etc/GMT-9"/>
<!-- (UTC+09:00) Pyongyang -->
<mapZone other="North Korea Standard Time" territory="001" type="Asia/Pyongyang"/>
<mapZone other="North Korea Standard Time" territory="KP" type="Asia/Pyongyang"/>
<!-- (UTC+09:00) Seoul -->
<mapZone other="Korea Standard Time" territory="001" type="Asia/Seoul"/>
<mapZone other="Korea Standard Time" territory="KR" type="Asia/Seoul"/>
<!-- (UTC+09:00) Yakutsk -->
<mapZone other="Yakutsk Standard Time" territory="001" type="Asia/Yakutsk"/>
<mapZone other="Yakutsk Standard Time" territory="RU" type="Asia/Yakutsk Asia/Khandyga"/>
<!-- (UTC+09:30) Adelaide -->
<mapZone other="Cen. Australia Standard Time" territory="001" type="Australia/Adelaide"/>
<mapZone other="Cen. Australia Standard Time" territory="AU" type="Australia/Adelaide Australia/Broken_Hill"/>
<!-- (UTC+09:30) Darwin -->
<mapZone other="AUS Central Standard Time" territory="001" type="Australia/Darwin"/>
<mapZone other="AUS Central Standard Time" territory="AU" type="Australia/Darwin"/>
<!-- (UTC+10:00) Brisbane -->
<mapZone other="E. Australia Standard Time" territory="001" type="Australia/Brisbane"/>
<mapZone other="E. Australia Standard Time" territory="AU" type="Australia/Brisbane Australia/Lindeman"/>
<!-- (UTC+10:00) Canberra, Melbourne, Sydney -->
<mapZone other="AUS Eastern Standard Time" territory="001" type="Australia/Sydney"/>
<mapZone other="AUS Eastern Standard Time" territory="AU" type="Australia/Sydney Australia/Melbourne"/>
<!-- (UTC+10:00) Guam, Port Moresby -->
<mapZone other="West Pacific Standard Time" territory="001" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="AQ" type="Antarctica/DumontDUrville"/>
<mapZone other="West Pacific Standard Time" territory="FM" type="Pacific/Truk"/>
<mapZone other="West Pacific Standard Time" territory="GU" type="Pacific/Guam"/>
<mapZone other="West Pacific Standard Time" territory="MP" type="Pacific/Saipan"/>
<mapZone other="West Pacific Standard Time" territory="PG" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="ZZ" type="Etc/GMT-10"/>
<!-- (UTC+10:00) Hobart -->
<mapZone other="Tasmania Standard Time" territory="001" type="Australia/Hobart"/>
<mapZone other="Tasmania Standard Time" territory="AU" type="Australia/Hobart Antarctica/Macquarie"/>
<!-- (UTC+10:00) Vladivostok -->
<mapZone other="Vladivostok Standard Time" territory="001" type="Asia/Vladivostok"/>
<mapZone other="Vladivostok Standard Time" territory="RU" type="Asia/Vladivostok Asia/Ust-Nera"/>
<!-- (UTC+10:30) Lord Howe Island -->
<mapZone other="Lord Howe Standard Time" territory="001" type="Australia/Lord_Howe"/>
<mapZone other="Lord Howe Standard Time" territory="AU" type="Australia/Lord_Howe"/>
<!-- (UTC+11:00) Bougainville Island -->
<mapZone other="Bougainville Standard Time" territory="001" type="Pacific/Bougainville"/>
<mapZone other="Bougainville Standard Time" territory="PG" type="Pacific/Bougainville"/>
<!-- (UTC+11:00) Chokurdakh -->
<mapZone other="Russia Time Zone 10" territory="001" type="Asia/Srednekolymsk"/>
<mapZone other="Russia Time Zone 10" territory="RU" type="Asia/Srednekolymsk"/>
<!-- (UTC+11:00) Magadan -->
<mapZone other="Magadan Standard Time" territory="001" type="Asia/Magadan"/>
<mapZone other="Magadan Standard Time" territory="RU" type="Asia/Magadan"/>
<!-- (UTC+11:00) Norfolk Island -->
<mapZone other="Norfolk Standard Time" territory="001" type="Pacific/Norfolk"/>
<mapZone other="Norfolk Standard Time" territory="NF" type="Pacific/Norfolk"/>
<!-- (UTC+11:00) Sakhalin -->
<mapZone other="Sakhalin Standard Time" territory="001" type="Asia/Sakhalin"/>
<mapZone other="Sakhalin Standard Time" territory="RU" type="Asia/Sakhalin"/>
<!-- (UTC+11:00) Solomon Is., New Caledonia -->
<mapZone other="Central Pacific Standard Time" territory="001" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="AQ" type="Antarctica/Casey"/>
<mapZone other="Central Pacific Standard Time" territory="FM" type="Pacific/Ponape Pacific/Kosrae"/>
<mapZone other="Central Pacific Standard Time" territory="NC" type="Pacific/Noumea"/>
<mapZone other="Central Pacific Standard Time" territory="SB" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="VU" type="Pacific/Efate"/>
<mapZone other="Central Pacific Standard Time" territory="ZZ" type="Etc/GMT-11"/>
<!-- (UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky -->
<mapZone other="Russia Time Zone 11" territory="001" type="Asia/Kamchatka"/>
<mapZone other="Russia Time Zone 11" territory="RU" type="Asia/Kamchatka Asia/Anadyr"/>
<!-- (UTC+12:00) Auckland, Wellington -->
<mapZone other="New Zealand Standard Time" territory="001" type="Pacific/Auckland"/>
<mapZone other="New Zealand Standard Time" territory="AQ" type="Antarctica/McMurdo"/>
<mapZone other="New Zealand Standard Time" territory="NZ" type="Pacific/Auckland"/>
<!-- (UTC+12:00) Coordinated Universal Time+12 -->
<mapZone other="UTC+12" territory="001" type="Etc/GMT-12"/>
<mapZone other="UTC+12" territory="KI" type="Pacific/Tarawa"/>
<mapZone other="UTC+12" territory="MH" type="Pacific/Majuro Pacific/Kwajalein"/>
<mapZone other="UTC+12" territory="NR" type="Pacific/Nauru"/>
<mapZone other="UTC+12" territory="TV" type="Pacific/Funafuti"/>
<mapZone other="UTC+12" territory="UM" type="Pacific/Wake"/>
<mapZone other="UTC+12" territory="WF" type="Pacific/Wallis"/>
<mapZone other="UTC+12" territory="ZZ" type="Etc/GMT-12"/>
<!-- (UTC+12:00) Fiji -->
<mapZone other="Fiji Standard Time" territory="001" type="Pacific/Fiji"/>
<mapZone other="Fiji Standard Time" territory="FJ" type="Pacific/Fiji"/>
<!-- (UTC+12:45) Chatham Islands -->
<mapZone other="Chatham Islands Standard Time" territory="001" type="Pacific/Chatham"/>
<mapZone other="Chatham Islands Standard Time" territory="NZ" type="Pacific/Chatham"/>
<!-- (UTC+13:00) Coordinated Universal Time+13 -->
<mapZone other="UTC+13" territory="001" type="Etc/GMT-13"/>
<mapZone other="UTC+13" territory="KI" type="Pacific/Enderbury"/>
<mapZone other="UTC+13" territory="TK" type="Pacific/Fakaofo"/>
<mapZone other="UTC+13" territory="ZZ" type="Etc/GMT-13"/>
<!-- (UTC+13:00) Nuku'alofa -->
<mapZone other="Tonga Standard Time" territory="001" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="TO" type="Pacific/Tongatapu"/>
<!-- (UTC+13:00) Samoa -->
<mapZone other="Samoa Standard Time" territory="001" type="Pacific/Apia"/>
<mapZone other="Samoa Standard Time" territory="WS" type="Pacific/Apia"/>
<!-- (UTC+14:00) Kiritimati Island -->
<mapZone other="Line Islands Standard Time" territory="001" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="KI" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="ZZ" type="Etc/GMT-14"/>
</mapTimezones>
</windowsZones>
</supplementalData>
================================================
FILE: src/location.zig
================================================
//!This file is generated. Do not edit directly! Run `zig build generate` to update after obtaining
//!the latest dataset.
const builtin = @import("builtin");
pub const Location = enum {
@"Africa/Abidjan",
@"Africa/Accra",
@"Africa/Addis_Ababa",
@"Africa/Algiers",
@"Africa/Asmera",
@"Africa/Bamako",
@"Africa/Bangui",
@"Africa/Banjul",
@"Africa/Bissau",
@"Africa/Blantyre",
@"Africa/Brazzaville",
@"Africa/Bujumbura",
@"Africa/Cairo",
@"Africa/Casablanca",
@"Africa/Ceuta",
@"Africa/Conakry",
@"Africa/Dakar",
@"Africa/Dar_es_Salaam",
@"Africa/Djibouti",
@"Africa/Douala",
@"Africa/El_Aaiun",
@"Africa/Freetown",
@"Africa/Gaborone",
@"Africa/Harare",
@"Africa/Johannesburg",
@"Africa/Juba",
@"Africa/Kampala",
@"Africa/Khartoum",
@"Africa/Kigali",
@"Africa/Kinshasa",
@"Africa/Lagos",
@"Africa/Libreville",
@"Africa/Lome",
@"Africa/Luanda",
@"Africa/Lubumbashi",
@"Africa/Lusaka",
@"Africa/Malabo",
@"Africa/Maputo",
@"Africa/Maseru",
@"Africa/Mbabane",
@"Africa/Mogadishu",
@"Africa/Monrovia",
@"Africa/Nairobi",
@"Africa/Ndjamena",
@"Africa/Niamey",
@"Africa/Nouakchott",
@"Africa/Ouagadougou",
@"Africa/Porto-Novo",
@"Africa/Sao_Tome",
@"Africa/Tripoli",
@"Africa/Tunis",
@"Africa/Windhoek",
@"America/Adak",
@"America/Anchorage",
@"America/Anguilla",
@"America/Antigua",
@"America/Araguaina",
@"America/Argentina/La_Rioja",
@"America/Argentina/Rio_Gallegos",
@"America/Argentina/Salta",
@"America/Argentina/San_Juan",
@"America/Argentina/San_Luis",
@"America/Argentina/Tucuman",
@"America/Argentina/Ushuaia",
@"America/Aruba",
@"America/Asuncion",
@"America/Bahia",
@"America/Bahia_Banderas",
@"America/Barbados",
@"America/Belem",
@"America/Belize",
@"America/Blanc-Sablon",
@"America/Boa_Vista",
@"America/Bogota",
@"America/Boise",
@"America/Buenos_Aires",
@"America/Cambridge_Bay",
@"America/Campo_Grande",
@"America/Cancun",
@"America/Caracas",
@"America/Catamarca",
@"America/Cayenne",
@"America/Cayman",
@"America/Chicago",
@"America/Chihuahua",
@"America/Ciudad_Juarez",
@"America/Coral_Harbour",
@"America/Cordoba",
@"America/Costa_Rica",
@"America/Creston",
@"America/Cuiaba",
@"America/Curacao",
@"America/Danmarkshavn",
@"America/Dawson",
@"America/Dawson_Creek",
@"America/Denver",
@"America/Detroit",
@"America/Dominica",
@"America/Edmonton",
@"America/Eirunepe",
@"America/El_Salvador",
@"America/Fort_Nelson",
@"America/Fortaleza",
@"America/Glace_Bay",
@"America/Godthab",
@"America/Goose_Bay",
@"America/Grand_Turk",
@"America/Grenada",
@"America/Guadeloupe",
@"America/Guatemala",
@"America/Guayaquil",
@"America/Guyana",
@"America/Halifax",
@"America/Havana",
@"America/Hermosillo",
@"America/Indiana/Knox",
@"America/Indiana/Marengo",
@"America/Indiana/Petersburg",
@"America/Indiana/Tell_City",
@"America/Indiana/Vevay",
@"America/Indiana/Vincennes",
@"America/Indiana/Winamac",
@"America/Indianapolis",
@"America/Inuvik",
@"America/Iqaluit",
@"America/Jamaica",
@"America/Jujuy",
@"America/Juneau",
@"America/Kentucky/Monticello",
@"America/Kralendijk",
@"America/La_Paz",
@"America/Lima",
@"America/Los_Angeles",
@"America/Louisville",
@"America/Lower_Princes",
@"America/Maceio",
@"America/Managua",
@"America/Manaus",
@"America/Marigot",
@"America/Martinique",
@"America/Matamoros",
@"America/Mazatlan",
@"America/Mendoza",
@"America/Menominee",
@"America/Merida",
@"America/Metlakatla",
@"America/Mexico_City",
@"America/Miquelon",
@"America/Moncton",
@"America/Monterrey",
@"America/Montevideo",
@"America/Montserrat",
@"America/Nassau",
@"America/New_York",
@"America/Nome",
@"America/Noronha",
@"America/North_Dakota/Beulah",
@"America/North_Dakota/Center",
@"America/North_Dakota/New_Salem",
@"America/Ojinaga",
@"America/Panama",
@"America/Paramaribo",
@"America/Phoenix",
@"America/Port-au-Prince",
@"America/Port_of_Spain",
@"America/Porto_Velho",
@"America/Puerto_Rico",
@"America/Punta_Arenas",
@"America/Rankin_Inlet",
@"America/Recife",
@"America/Regina",
@"America/Resolute",
@"America/Rio_Branco",
@"America/Santarem",
@"America/Santiago",
@"America/Santo_Domingo",
@"America/Sao_Paulo",
@"America/Scoresbysund",
@"America/Sitka",
@"America/St_Barthelemy",
@"America/St_Johns",
@"America/St_Kitts",
@"America/St_Lucia",
@"America/St_Thomas",
@"America/St_Vincent",
@"America/Swift_Current",
@"America/Tegucigalpa",
@"America/Thule",
@"America/Tijuana",
@"America/Toronto",
@"America/Tortola",
@"America/Vancouver",
@"America/Whitehorse",
@"America/Winnipeg",
@"America/Yakutat",
@"Antarctica/Casey",
@"Antarctica/Davis",
@"Antarctica/DumontDUrville",
@"Antarctica/Macquarie",
@"Antarctica/Mawson",
@"Antarctica/McMurdo",
@"Antarctica/Palmer",
@"Antarctica/Rothera",
@"Antarctica/Syowa",
@"Antarctica/Vostok",
@"Arctic/Longyearbyen",
@"Asia/Aden",
@"Asia/Almaty",
@"Asia/Amman",
@"Asia/Anadyr",
@"Asia/Aqtau",
@"Asia/Aqtobe",
@"Asia/Ashgabat",
@"Asia/Atyrau",
@"Asia/Baghdad",
@"Asia/Bahrain",
@"Asia/Baku",
@"Asia/Bangkok",
@"Asia/Barnaul",
@"Asia/Beirut",
@"Asia/Bishkek",
@"Asia/Brunei",
@"Asia/Calcutta",
@"Asia/Chita",
@"Asia/Colombo",
@"Asia/Damascus",
@"Asia/Dhaka",
@"Asia/Dili",
@"Asia/Dubai",
@"Asia/Dushanbe",
@"Asia/Famagusta",
@"Asia/Gaza",
@"Asia/Hebron",
@"Asia/Hong_Kong",
@"Asia/Hovd",
@"Asia/Irkutsk",
@"Asia/Jakarta",
@"Asia/Jayapura",
@"Asia/Jerusalem",
@"Asia/Kabul",
@"Asia/Kamchatka",
@"Asia/Karachi",
@"Asia/Katmandu",
@"Asia/Khandyga",
@"Asia/Krasnoyarsk",
@"Asia/Kuala_Lumpur",
@"Asia/Kuching",
@"Asia/Kuwait",
@"Asia/Macau",
@"Asia/Magadan",
@"Asia/Makassar",
@"Asia/Manila",
@"Asia/Muscat",
@"Asia/Nicosia",
@"Asia/Novokuznetsk",
@"Asia/Novosibirsk",
@"Asia/Omsk",
@"Asia/Oral",
@"Asia/Phnom_Penh",
@"Asia/Pontianak",
@"Asia/Pyongyang",
@"Asia/Qatar",
@"Asia/Qostanay",
@"Asia/Qyzylorda",
@"Asia/Rangoon",
@"Asia/Riyadh",
@"Asia/Saigon",
@"Asia/Sakhalin",
@"Asia/Samarkand",
@"Asia/Seoul",
@"Asia/Shanghai",
@"Asia/Singapore",
@"Asia/Srednekolymsk",
@"Asia/Taipei",
@"Asia/Tashkent",
@"Asia/Tbilisi",
@"Asia/Tehran",
@"Asia/Thimphu",
@"Asia/Tokyo",
@"Asia/Tomsk",
@"Asia/Ulaanbaatar",
@"Asia/Urumqi",
@"Asia/Ust-Nera",
@"Asia/Vientiane",
@"Asia/Vladivostok",
@"Asia/Yakutsk",
@"Asia/Yekaterinburg",
@"Asia/Yerevan",
@"Atlantic/Azores",
@"Atlantic/Bermuda",
@"Atlantic/Canary",
@"Atlantic/Cape_Verde",
@"Atlantic/Faeroe",
@"Atlantic/Madeira",
@"Atlantic/Reykjavik",
@"Atlantic/South_Georgia",
@"Atlantic/St_Helena",
@"Atlantic/Stanley",
@"Australia/Adelaide",
@"Australia/Brisbane",
@"Australia/Broken_Hill",
@"Australia/Darwin",
@"Australia/Eucla",
@"Australia/Hobart",
@"Australia/Lindeman",
@"Australia/Lord_Howe",
@"Australia/Melbourne",
@"Australia/Perth",
@"Australia/Sydney",
@"Etc/GMT",
@"Etc/GMT+1",
@"Etc/GMT+10",
@"Etc/GMT+11",
@"Etc/GMT+12",
@"Etc/GMT+2",
@"Etc/GMT+3",
@"Etc/GMT+4",
@"Etc/GMT+5",
@"Etc/GMT+6",
@"Etc/GMT+7",
@"Etc/GMT+8",
@"Etc/GMT+9",
@"Etc/GMT-1",
@"Etc/GMT-10",
@"Etc/GMT-11",
@"Etc/GMT-12",
@"Etc/GMT-13",
@"Etc/GMT-14",
@"Etc/GMT-2",
@"Etc/GMT-3",
@"Etc/GMT-4",
@"Etc/GMT-5",
@"Etc/GMT-6",
@"Etc/GMT-7",
@"Etc/GMT-8",
@"Etc/GMT-9",
@"Etc/UTC",
@"Europe/Amsterdam",
@"Europe/Andorra",
@"Europe/Astrakhan",
@"Europe/Athens",
@"Europe/Belgrade",
@"Europe/Berlin",
@"Europe/Bratislava",
@"Europe/Brussels",
@"Europe/Bucharest",
@"Europe/Budapest",
@"Europe/Busingen",
@"Europe/Chisinau",
@"Europe/Copenhagen",
@"Europe/Dublin",
@"Europe/Gibraltar",
@"Europe/Guernsey",
@"Europe/Helsinki",
@"Europe/Isle_of_Man",
@"Europe/Istanbul",
@"Europe/Jersey",
@"Europe/Kaliningrad",
@"Europe/Kiev",
@"Europe/Kirov",
@"Europe/Lisbon",
@"Europe/Ljubljana",
@"Europe/London",
@"Europe/Luxembourg",
@"Europe/Madrid",
@"Europe/Malta",
@"Europe/Mariehamn",
@"Europe/Minsk",
@"Europe/Monaco",
@"Europe/Moscow",
@"Europe/Oslo",
@"Europe/Paris",
@"Europe/Podgorica",
@"Europe/Prague",
@"Europe/Riga",
@"Europe/Rome",
@"Europe/Samara",
@"Europe/San_Marino",
@"Europe/Sarajevo",
@"Europe/Saratov",
@"Europe/Simferopol",
@"Europe/Skopje",
@"Europe/Sofia",
@"Europe/Stockholm",
@"Europe/Tallinn",
@"Europe/Tirane",
@"Europe/Ulyanovsk",
@"Europe/Vaduz",
@"Europe/Vatican",
@"Europe/Vienna",
@"Europe/Vilnius",
@"Europe/Volgograd",
@"Europe/Warsaw",
@"Europe/Zagreb",
@"Europe/Zurich",
@"Indian/Antananarivo",
@"Indian/Chagos",
@"Indian/Christmas",
@"Indian/Cocos",
@"Indian/Comoro",
@"Indian/Kerguelen",
@"Indian/Mahe",
@"Indian/Maldives",
@"Indian/Mauritius",
@"Indian/Mayotte",
@"Indian/Reunion",
@"Pacific/Apia",
@"Pacific/Auckland",
@"Pacific/Bougainville",
@"Pacific/Chatham",
@"Pacific/Easter",
@"Pacific/Efate",
@"Pacific/Enderbury",
@"Pacific/Fakaofo",
@"Pacific/Fiji",
@"Pacific/Funafuti",
@"Pacific/Galapagos",
@"Pacific/Gambier",
@"Pacific/Guadalcanal",
@"Pacific/Guam",
@"Pacific/Honolulu",
@"Pacific/Kiritimati",
@"Pacific/Kosrae",
@"Pacific/Kwajalein",
@"Pacific/Majuro",
@"Pacific/Marquesas",
@"Pacific/Midway",
@"Pacific/Nauru",
@"Pacific/Niue",
@"Pacific/Norfolk",
@"Pacific/Noumea",
@"Pacific/Pago_Pago",
@"Pacific/Palau",
@"Pacific/Pitcairn",
@"Pacific/Ponape",
@"Pacific/Port_Moresby",
@"Pacific/Rarotonga",
@"Pacific/Saipan",
@"Pacific/Tahiti",
@"Pacific/Tarawa",
@"Pacific/Tongatapu",
@"Pacific/Truk",
@"Pacific/Wake",
@"Pacific/Wallis",
pub fn asText(self: Location) []const u8 {
switch (builtin.os.tag) {
.windows => {},
else => return @tagName(self),
}
return switch (self) {
.@"Africa/Abidjan" => "Greenwich Standard Time",
.@"Africa/Accra" => "Greenwich Standard Time",
.@"Africa/Addis_Ababa" => "E. Africa Standard Time",
.@"Africa/Algiers" => "W. Central Africa Standard Time",
.@"Africa/Asmera" => "E. Africa Standard Time",
.@"Africa/Bamako" => "Greenwich Standard Time",
.@"Africa/Bangui" => "W. Central Africa Standard Time",
.@"Africa/Banjul" => "Greenwich Standard Time",
.@"Africa/Bissau" => "Greenwich Standard Time",
.@"Africa/Blantyre" => "South Africa Standard Time",
.@"Africa/Brazzaville" => "W. Central Africa Standard Time",
.@"Africa/Bujumbura" => "South Africa Standard Time",
.@"Africa/Cairo" => "Egypt Standard Time",
.@"Africa/Casablanca" => "Morocco Standard Time",
.@"Africa/Ceuta" => "Romance Standard Time",
.@"Africa/Conakry" => "Greenwich Standard Time",
.@"Africa/Dakar" => "Greenwich Standard Time",
.@"Africa/Dar_es_Salaam" => "E. Africa Standard Time",
.@"Africa/Djibouti" => "E. Africa Standard Time",
.@"Africa/Douala" => "W. Central Africa Standard Time",
.@"Africa/El_Aaiun" => "Morocco Standard Time",
.@"Africa/Freetown" => "Greenwich Standard Time",
.@"Africa/Gaborone" => "South Africa Standard Time",
.@"Africa/Harare" => "South Africa Standard Time",
.@"Africa/Johannesburg" => "South Africa Standard Time",
.@"Africa/Juba" => "South Sudan Standard Time",
.@"Africa/Kampala" => "E. Africa Standard Time",
.@"Africa/Khartoum" => "Sudan Standard Time",
.@"Africa/Kigali" => "South Africa Standard Time",
.@"Africa/Kinshasa" => "W. Central Africa Standard Time",
.@"Africa/Lagos" => "W. Central Africa Standard Time",
.@"Africa/Libreville" => "W. Central Africa Standard Time",
.@"Africa/Lome" => "Greenwich Standard Time",
.@"Africa/Luanda" => "W. Central Africa Standard Time",
.@"Africa/Lubumbashi" => "South Africa Standard Time",
.@"Africa/Lusaka" => "South Africa Standard Time",
.@"Africa/Malabo" => "W. Central Africa Standard Time",
.@"Africa/Maputo" => "South Africa Standard Time",
.@"Africa/Maseru" => "South Africa Standard Time",
.@"Africa/Mbabane" => "South Africa Standard Time",
.@"Africa/Mogadishu" => "E. Africa Standard Time",
.@"Africa/Monrovia" => "Greenwich Standard Time",
.@"Africa/Nairobi" => "E. Africa Standard Time",
.@"Africa/Ndjamena" => "W. Central Africa Standard Time",
.@"Africa/Niamey" => "W. Central Africa Standard Time",
.@"Africa/Nouakchott" => "Greenwich Standard Time",
.@"Africa/Ouagadougou" => "Greenwich Standard Time",
.@"Africa/Porto-Novo" => "W. Central Africa Standard Time",
.@"Africa/Sao_Tome" => "Sao Tome Standard Time",
.@"Africa/Tripoli" => "Libya Standard Time",
.@"Africa/Tunis" => "W. Central Africa Standard Time",
.@"Africa/Windhoek" => "Namibia Standard Time",
.@"America/Adak" => "Aleutian Standard Time",
.@"America/Anchorage" => "Alaskan Standard Time",
.@"America/Anguilla" => "SA Western Standard Time",
.@"America/Antigua" => "SA Western Standard Time",
.@"America/Araguaina" => "Tocantins Standard Time",
.@"America/Argentina/La_Rioja" => "Argentina Standard Time",
.@"America/Argentina/Rio_Gallegos" => "Argentina Standard Time",
.@"America/Argentina/Salta" => "Argentina Standard Time",
.@"America/Argentina/San_Juan" => "Argentina Standard Time",
.@"America/Argentina/San_Luis" => "Argentina Standard Time",
.@"America/Argentina/Tucuman" => "Argentina Standard Time",
.@"America/Argentina/Ushuaia" => "Argentina Standard Time",
.@"America/Aruba" => "SA Western Standard Time",
.@"America/Asuncion" => "Paraguay Standard Time",
.@"America/Bahia" => "Bahia Standard Time",
.@"America/Bahia_Banderas" => "Central Standard Time (Mexico)",
.@"America/Barbados" => "SA Western Standard Time",
.@"America/Belem" => "SA Eastern Standard Time",
.@"America/Belize" => "Central America Standard Time",
.@"America/Blanc-Sablon" => "SA Western Standard Time",
.@"America/Boa_Vista" => "SA Western Standard Time",
.@"America/Bogota" => "SA Pacific Standard Time",
.@"America/Boise" => "Mountain Standard Time",
.@"America/Buenos_Aires" => "Argentina Standard Time",
.@"America/Cambridge_Bay" => "Mountain Standard Time",
.@"America/Campo_Grande" => "Central Brazilian Standard Time",
.@"America/Cancun" => "Eastern Standard Time (Mexico)",
.@"America/Caracas" => "Venezuela Standard Time",
.@"America/Catamarca" => "Argentina Standard Time",
.@"America/Cayenne" => "SA Eastern Standard Time",
.@"America/Cayman" => "SA Pacific Standard Time",
.@"America/Chicago" => "Central Standard Time",
.@"America/Chihuahua" => "Central Standard Time (Mexico)",
.@"America/Ciudad_Juarez" => "Mountain Standard Time",
.@"America/Coral_Harbour" => "SA Pacific Standard Time",
.@"America/Cordoba" => "Argentina Standard Time",
.@"America/Costa_Rica" => "Central America Standard Time",
.@"America/Creston" => "US Mountain Standard Time",
.@"America/Cuiaba" => "Central Brazilian Standard Time",
.@"America/Curacao" => "SA Western Standard Time",
.@"America/Danmarkshavn" => "Greenwich Standard Time",
.@"America/Dawson" => "Yukon Standard Time",
.@"America/Dawson_Creek" => "US Mountain Standard Time",
.@"America/Denver" => "Mountain Standard Time",
.@"America/Detroit" => "Eastern Standard Time",
.@"America/Dominica" => "SA Western Standard Time",
.@"America/Edmonton" => "Mountain Standard Time",
.@"America/Eirunepe" => "SA Pacific Standard Time",
.@"America/El_Salvador" => "Central America Standard Time",
.@"America/Fort_Nelson" => "US Mountain Standard Time",
.@"America/Fortaleza" => "SA Eastern Standard Time",
.@"America/Glace_Bay" => "Atlantic Standard Time",
.@"America/Godthab" => "Greenland Standard Time",
.@"America/Goose_Bay" => "Atlantic Standard Time",
.@"America/Grand_Turk" => "Turks And Caicos Standard Time",
.@"America/Grenada" => "SA Western Standard Time",
.@"America/Guadeloupe" => "SA Western Standard Time",
.@"America/Guatemala" => "Central America Standard Time",
.@"America/Guayaquil" => "SA Pacific Standard Time",
.@"America/Guyana" => "SA Western Standard Time",
.@"America/Halifax" => "Atlantic Standard Time",
.@"America/Havana" => "Cuba Standard Time",
.@"America/Hermosillo" => "US Mountain Standard Time",
.@"America/Indiana/Knox" => "Central Standard Time",
.@"America/Indiana/Marengo" => "US Eastern Standard Time",
.@"America/Indiana/Petersburg" => "Eastern Standard Time",
.@"America/Indiana/Tell_City" => "Central Standard Time",
.@"America/Indiana/Vevay" => "US Eastern Standard Time",
.@"America/Indiana/Vincennes" => "Eastern Standard Time",
.@"America/Indiana/Winamac" => "Eastern Standard Time",
.@"America/Indianapolis" => "US Eastern Standard Time",
.@"America/Inuvik" => "Mountain Standard Time",
.@"America/Iqaluit" => "Eastern Standard Time",
.@"America/Jamaica" => "SA Pacific Standard Time",
.@"America/Jujuy" => "Argentina Standard Time",
.@"America/Juneau" => "Alaskan Standard Time",
.@"America/Kentucky/Monticello" => "Eastern Standard Time",
.@"America/Kralendijk" => "SA Western Standard Time",
.@"America/La_Paz" => "SA Western Standard Time",
.@"America/Lima" => "SA Pacific Standard Time",
.@"America/Los_Angeles" => "Pacific Standard Time",
.@"America/Louisville" => "Eastern Standard Time",
.@"America/Lower_Princes" => "SA Western Standard Time",
.@"America/Maceio" => "SA Eastern Standard Time",
.@"America/Managua" => "Central America Standard Time",
.@"America/Manaus" => "SA Western Standard Time",
.@"America/Marigot" => "SA Western Standard Time",
.@"America/Martinique" => "SA Western Standard Time",
.@"America/Matamoros" => "Central Standard Time",
.@"America/Mazatlan" => "Mountain Standard Time (Mexico)",
.@"America/Mendoza" => "Argentina Standard Time",
.@"America/Menominee" => "Central Standard Time",
.@"America/Merida" => "Central Standard Time (Mexico)",
.@"America/Metlakatla" => "Alaskan Standard Time",
.@"America/Mexico_City" => "Central Standard Time (Mexico)",
.@"America/Miquelon" => "Saint Pierre Standard Time",
.@"America/Moncton" => "Atlantic Standard Time",
.@"America/Monterrey" => "Central Standard Time (Mexico)",
.@"America/Montevideo" => "Montevideo Standard Time",
.@"America/Montserrat" => "SA Western Standard Time",
.@"America/Nassau" => "Eastern Standard Time",
.@"America/New_York" => "Eastern Standard Time",
.@"America/Nome" => "Alaskan Standard Time",
.@"America/Noronha" => "UTC-02",
.@"America/North_Dakota/Beulah" => "Central Standard Time",
.@"America/North_Dakota/Center" => "Central Standard Time",
.@"America/North_Dakota/New_Salem" => "Central Standard Time",
.@"America/Ojinaga" => "Central Standard Time",
.@"America/Panama" => "SA Pacific Standard Time",
.@"America/Paramaribo" => "SA Eastern Standard Time",
.@"America/Phoenix" => "US Mountain Standard Time",
.@"America/Port-au-Prince" => "Haiti Standard Time",
.@"America/Port_of_Spain" => "SA Western Standard Time",
.@"America/Porto_Velho" => "SA Western Standard Time",
.@"America/Puerto_Rico" => "SA Western Standard Time",
.@"America/Punta_Arenas" => "Magallanes Standard Time",
.@"America/Rankin_Inlet" => "Central Standard Time",
.@"America/Recife" => "SA Eastern Standard Time",
.@"America/Regina" => "Canada Central Standard Time",
.@"America/Resolute" => "Central Standard Time",
.@"America/Rio_Branco" => "SA Pacific Standard Time",
.@"America/Santarem" => "SA Eastern Standard Time",
.@"America/Santiago" => "Pacific SA Standard Time",
.@"America/Santo_Domingo" => "SA Western Standard Time",
.@"America/Sao_Paulo" => "E. South America Standard Time",
.@"America/Scoresbysund" => "Azores Standard Time",
.@"America/Sitka" => "Alaskan Standard Time",
.@"America/St_Barthelemy" => "SA Western Standard Time",
.@"America/St_Johns" => "Newfoundland Standard Time",
.@"America/St_Kitts" => "SA Western Standard Time",
.@"America/St_Lucia" => "SA Western Standard Time",
.@"America/St_Thomas" => "SA Western Standard Time",
.@"America/St_Vincent" => "SA Western Standard Time",
.@"America/Swift_Current" => "Canada Central Standard Time",
.@"America/Tegucigalpa" => "Central America Standard Time",
.@"America/Thule" => "Atlantic Standard Time",
.@"America/Tijuana" => "Pacific Standard Time (Mexico)",
.@"America/Toronto" => "Eastern Standard Time",
.@"America/Tortola" => "SA Western Standard Time",
.@"America/Vancouver" => "Pacific Standard Time",
.@"America/Whitehorse" => "Yukon Standard Time",
.@"America/Winnipeg" => "Central Standard Time",
.@"America/Yakutat" => "Alaskan Standard Time",
.@"Antarctica/Casey" => "Central Pacific Standard Time",
.@"Antarctica/Davis" => "SE Asia Standard Time",
.@"Antarctica/DumontDUrville" => "West Pacific Standard Time",
.@"Antarctica/Macquarie" => "Tasmania Standard Time",
.@"Antarctica/Mawson" => "West Asia Standard Time",
.@"Antarctica/McMurdo" => "New Zealand Standard Time",
.@"Antarctica/Palmer" => "SA Eastern Standard Time",
.@"Antarctica/Rothera" => "SA Eastern Standard Time",
.@"Antarctica/Syowa" => "E. Africa Standard Time",
.@"Antarctica/Vostok" => "Central Asia Standard Time",
.@"Arctic/Longyearbyen" => "W. Europe Standard Time",
.@"Asia/Aden" => "Arab Standard Time",
.@"Asia/Almaty" => "West Asia Standard Time",
.@"Asia/Amman" => "Jordan Standard Time",
.@"Asia/Anadyr" => "Russia Time Zone 11",
.@"Asia/Aqtau" => "West Asia Standard Time",
.@"Asia/Aqtobe" => "West Asia Standard Time",
.@"Asia/Ashgabat" => "West Asia Standard Time",
.@"Asia/Atyrau" => "West Asia Standard Time",
.@"Asia/Baghdad" => "Arabic Standard Time",
.@"Asia/Bahrain" => "Arab Standard Time",
.@"Asia/Baku" => "Azerbaijan Standard Time",
.@"Asia/Bangkok" => "SE Asia Standard Time",
.@"Asia/Barnaul" => "Altai Standard Time",
.@"Asia/Beirut" => "Middle East Standard Time",
.@"Asia/Bishkek" => "Central Asia Standard Time",
.@"Asia/Brunei" => "Singapore Standard Time",
.@"Asia/Calcutta" => "India Standard Time",
.@"Asia/Chita" => "Transbaikal Standard Time",
.@"Asia/Colombo" => "Sri Lanka Standard Time",
.@"Asia/Damascus" => "Syria Standard Time",
.@"Asia/Dhaka" => "Bangladesh Standard Time",
.@"Asia/Dili" => "Tokyo Standard Time",
.@"Asia/Dubai" => "Arabian Standard Time",
.@"Asia/Dushanbe" => "West Asia Standard Time",
.@"Asia/Famagusta" => "GTB Standard Time",
.@"Asia/Gaza" => "West Bank Standard Time",
.@"Asia/Hebron" => "West Bank Standard Time",
.@"Asia/Hong_Kong" => "China Standard Time",
.@"Asia/Hovd" => "W. Mongolia Standard Time",
.@"Asia/Irkutsk" => "North Asia East Standard Time",
.@"Asia/Jakarta" => "SE Asia Standard Time",
.@"Asia/Jayapura" => "Tokyo Standard Time",
.@"Asia/Jerusalem" => "Israel Standard Time",
.@"Asia/Kabul" => "Afghanistan Standard Time",
.@"Asia/Kamchatka" => "Russia Time Zone 11",
.@"Asia/Karachi" => "Pakistan Standard Time",
.@"Asia/Katmandu" => "Nepal Standard Time",
.@"Asia/Khandyga" => "Yakutsk Standard Time",
.@"Asia/Krasnoyarsk" => "North Asia Standard Time",
.@"Asia/Kuala_Lumpur" => "Singapore Standard Time",
.@"Asia/Kuching" => "Singapore Standard Time",
.@"Asia/Kuwait" => "Arab Standard Time",
.@"Asia/Macau" => "China Standard Time",
.@"Asia/Magadan" => "Magadan Standard Time",
.@"Asia/Makassar" => "Singapore Standard Time",
.@"Asia/Manila" => "Singapore Standard Time",
.@"Asia/Muscat" => "Arabian Standard Time",
.@"Asia/Nicosia" => "GTB Standard Time",
.@"Asia/Novokuznetsk" => "North Asia Standard Time",
.@"Asia/Novosibirsk" => "N. Central Asia Standard Time",
.@"Asia/Omsk" => "Omsk Standard Time",
.@"Asia/Oral" => "West Asia Standard Time",
.@"Asia/Phnom_Penh" => "SE Asia Standard Time",
.@"Asia/Pontianak" => "SE Asia Standard Time",
.@"Asia/Pyongyang" => "North Korea Standard Time",
.@"Asia/Qatar" => "Arab Standard Time",
.@"Asia/Qostanay" => "West Asia Standard Time",
.@"Asia/Qyzylorda" => "Qyzylorda Standard Time",
.@"Asia/Rangoon" => "Myanmar Standard Time",
.@"Asia/Riyadh" => "Arab Standard Time",
.@"Asia/Saigon" => "SE Asia Standard Time",
.@"Asia/Sakhalin" => "Sakhalin Standard Time",
.@"Asia/Samarkand" => "West Asia Standard Time",
.@"Asia/Seoul" => "Korea Standard Time",
.@"Asia/Shanghai" => "China Standard Time",
.@"Asia/Singapore" => "Singapore Standard Time",
.@"Asia/Srednekolymsk" => "Russia Time Zone 10",
.@"Asia/Taipei" => "Taipei Standard Time",
.@"Asia/Tashkent" => "West Asia Standard Time",
.@"Asia/Tbilisi" => "Georgian Standard Time",
.@"Asia/Tehran" => "Iran Standard Time",
.@"Asia/Thimphu" => "Bangladesh Standard Time",
.@"Asia/Tokyo" => "Tokyo Standard Time",
.@"Asia/Tomsk" => "Tomsk Standard Time",
.@"Asia/Ulaanbaatar" => "Ulaanbaatar Standard Time",
.@"Asia/Urumqi" => "Central Asia Standard Time",
.@"Asia/Ust-Nera" => "Vladivostok Standard Time",
.@"Asia/Vientiane" => "SE Asia Standard Time",
.@"Asia/Vladivostok" => "Vladivostok Standard Time",
.@"Asia/Yakutsk" => "Yakutsk Standard Time",
.@"Asia/Yekaterinburg" => "Ekaterinburg Standard Time",
.@"Asia/Yerevan" => "Caucasus Standard Time",
.@"Atlantic/Azores" => "Azores Standard Time",
.@"Atlantic/Bermuda" => "Atlantic Standard Time",
.@"Atlantic/Canary" => "GMT Standard Time",
.@"Atlantic/Cape_Verde" => "Cape Verde Standard Time",
.@"Atlantic/Faeroe" => "GMT Standard Time",
.@"Atlantic/Madeira" => "GMT Standard Time",
.@"Atlantic/Reykjavik" => "Greenwich Standard Time",
.@"Atlantic/South_Georgia" => "UTC-02",
.@"Atlantic/St_Helena" => "Greenwich Standard Time",
.@"Atlantic/Stanley" => "SA Eastern Standard Time",
.@"Australia/Adelaide" => "Cen. Australia Standard Time",
.@"Australia/Brisbane" => "E. Australia Standard Time",
.@"Australia/Broken_Hill" => "Cen. Australia Standard Time",
.@"Australia/Darwin" => "AUS Central Standard Time",
.@"Australia/Eucla" => "Aus Central W. Standard Time",
.@"Australia/Hobart" => "Tasmania Standard Time",
.@"Australia/Lindeman" => "E. Australia Standard Time",
.@"Australia/Lord_Howe" => "Lord Howe Standard Time",
.@"Australia/Melbourne" => "AUS Eastern Standard Time",
.@"Australia/Perth" => "W. Australia Standard Time",
.@"Australia/Sydney" => "AUS Eastern Standard Time",
.@"Etc/GMT" => "UTC",
.@"Etc/GMT+1" => "Cape Verde Standard Time",
.@"Etc/GMT+10" => "Hawaiian Standard Time",
.@"Etc/GMT+11" => "UTC-11",
.@"Etc/GMT+12" => "Dateline Standard Time",
.@"Etc/GMT+2" => "UTC-02",
.@"Etc/GMT+3" => "SA Eastern Standard Time",
.@"Etc/GMT+4" => "SA Western Standard Time",
.@"Etc/GMT+5" => "SA Pacific Standard Time",
.@"Etc/GMT+6" => "Central America Standard Time",
.@"Etc/GMT+7" => "US Mountain Standard Time",
.@"Etc/GMT+8" => "UTC-08",
.@"Etc/GMT+9" => "UTC-09",
.@"Etc/GMT-1" => "W. Central Africa Standard Time",
.@"Etc/GMT-10" => "West Pacific Standard Time",
.@"Etc/GMT-11" => "Central Pacific Standard Time",
.@"Etc/GMT-12" => "UTC+12",
.@"Etc/GMT-13" => "UTC+13",
.@"Etc/GMT-14" => "Line Islands Standard Time",
.@"Etc/GMT-2" => "South Africa Standard Time",
.@"Etc/GMT-3" => "E. Africa Standard Time",
.@"Etc/GMT-4" => "Arabian Standard Time",
.@"Etc/GMT-5" => "West Asia Standard Time",
.@"Etc/GMT-6" => "Central Asia Standard Time",
.@"Etc/GMT-7" => "SE Asia Standard Time",
.@"Etc/GMT-8" => "Singapore Standard Time",
.@"Etc/GMT-9" => "Tokyo Standard Time",
.@"Etc/UTC" => "UTC",
.@"Europe/Amsterdam" => "W. Europe Standard Time",
.@"Europe/Andorra" => "W. Europe Standard Time",
.@"Europe/Astrakhan" => "Astrakhan Standard Time",
.@"Europe/Athens" => "GTB Standard Time",
.@"Europe/Belgrade" => "Central Europe Standard Time",
.@"Europe/Berlin" => "W. Europe Standard Time",
.@"Europe/Bratislava" => "Central Europe Standard Time",
.@"Europe/Brussels" => "Romance Standard Time",
.@"Europe/Bucharest" => "GTB Standard Time",
.@"Europe/Budapest" => "Central Europe Standard Time",
.@"Europe/Busingen" => "W. Europe Standard Time",
.@"Europe/Chisinau" => "E. Europe Standard Time",
.@"Europe/Copenhagen" => "Romance Standard Time",
.@"Europe/Dublin" => "GMT Standard Time",
.@"Europe/Gibraltar" => "W. Europe Standard Time",
.@"Europe/Guernsey" => "GMT Standard Time",
.@"Europe/Helsinki" => "FLE Standard Time",
.@"Europe/Isle_of_Man" => "GMT Standard Time",
.@"Europe/Istanbul" => "Turkey Standard Time",
.@"Europe/Jersey" => "GMT Standard Time",
.@"Europe/Kaliningrad" => "Kaliningrad Standard Time",
.@"Europe/Kiev" => "FLE Standard Time",
.@"Europe/Kirov" => "Russian Standard Time",
.@"Europe/Lisbon" => "GMT Standard Time",
.@"Europe/Ljubljana" => "Central Europe Standard Time",
.@"Europe/London" => "GMT Standard Time",
.@"Europe/Luxembourg" => "W. Europe Standard Time",
.@"Europe/Madrid" => "Romance Standard Time",
.@"Europe/Malta" => "W. Europe Standard Time",
.@"Europe/Mariehamn" => "FLE Standard Time",
.@"Europe/Minsk" => "Belarus Standard Time",
.@"Europe/Monaco" => "W. Europe Standard Time",
.@"Europe/Moscow" => "Russian Standard Time",
.@"Europe/Oslo" => "W. Europe Standard Time",
.@"Europe/Paris" => "Romance Standard Time",
.@"Europe/Podgorica" => "Central Europe Standard Time",
.@"Europe/Prague" => "Central Europe Standard Time",
.@"Europe/Riga" => "FLE Standard Time",
.@"Europe/Rome" => "W. Europe Standard Time",
.@"Europe/Samara" => "Russia Time Zone 3",
.@"Europe/San_Marino" => "W. Europe Standard Time",
.@"Europe/Sarajevo" => "Central European Standard Time",
.@"Europe/Saratov" => "Saratov Standard Time",
.@"Europe/Simferopol" => "Russian Standard Time",
.@"Europe/Skopje" => "Central European Standard Time",
.@"Europe/Sofia" => "FLE Standard Time",
.@"Europe/Stockholm" => "W. Europe Standard Time",
.@"Europe/Tallinn" => "FLE Standard Time",
.@"Europe/Tirane" => "Central Europe Standard Time",
.@"Europe/Ulyanovsk" => "Astrakhan Standard Time",
.@"Europe/Vaduz" => "W. Europe Standard Time",
.@"Europe/Vatican" => "W. Europe Standard Time",
.@"Europe/Vienna" => "W. Europe Standard Time",
.@"Europe/Vilnius" => "FLE Standard Time",
.@"Europe/Volgograd" => "Volgograd Standard Time",
.@"Europe/Warsaw" => "Central European Standard Time",
.@"Europe/Zagreb" => "Central European Standard Time",
.@"Europe/Zurich" => "W. Europe Standard Time",
.@"Indian/Antananarivo" => "E. Africa Standard Time",
.@"Indian/Chagos" => "Central Asia Standard Time",
.@"Indian/Christmas" => "SE Asia Standard Time",
.@"Indian/Cocos" => "Myanmar Standard Time",
.@"Indian/Comoro" => "E. Africa Standard Time",
.@"Indian/Kerguelen" => "West Asia Standard Time",
.@"Indian/Mahe" => "Mauritius Standard Time",
.@"Indian/Maldives" => "West Asia Standard Time",
.@"Indian/Mauritius" => "Mauritius Standard Time",
.@"Indian/Mayotte" => "E. Africa Standard Time",
.@"Indian/Reunion" => "Mauritius Standard Time",
.@"Pacific/Apia" => "Samoa Standard Time",
.@"Pacific/Auckland" => "New Zealand Standard Time",
.@"Pacific/Bougainville" => "Bougainville Standard Time",
.@"Pacific/Chatham" => "Chatham Islands Standard Time",
.@"Pacific/Easter" => "Easter Island Standard Time",
.@"Pacific/Efate" => "Central Pacific Standard Time",
.@"Pacific/Enderbury" => "UTC+13",
.@"Pacific/Fakaofo" => "UTC+13",
.@"Pacific/Fiji" => "Fiji Standard Time",
.@"Pacific/Funafuti" => "UTC+12",
.@"Pacific/Galapagos" => "Central America Standard Time",
.@"Pacific/Gambier" => "UTC-09",
.@"Pacific/Guadalcanal" => "Central Pacific Standard Time",
.@"Pacific/Guam" => "West Pacific Standard Time",
.@"Pacific/Honolulu" => "Hawaiian Standard Time",
.@"Pacific/Kiritimati" => "Line Islands Standard Time",
.@"Pacific/Kosrae" => "Central Pacific Standard Time",
.@"Pacific/Kwajalein" => "UTC+12",
.@"Pacific/Majuro" => "UTC+12",
.@"Pacific/Marquesas" => "Marquesas Standard Time",
.@"Pacific/Midway" => "UTC-11",
.@"Pacific/Nauru" => "UTC+12",
.@"Pacific/Niue" => "UTC-11",
.@"Pacific/Norfolk" => "Norfolk Standard Time",
.@"Pacific/Noumea" => "Central Pacific Standard Time",
.@"Pacific/Pago_Pago" => "UTC-11",
.@"Pacific/Palau" => "Tokyo Standard Time",
.@"Pacific/Pitcairn" => "UTC-08",
.@"Pacific/Ponape" => "Central Pacific Standard Time",
.@"Pacific/Port_Moresby" => "West Pacific Standard Time",
.@"Pacific/Rarotonga" => "Hawaiian Standard Time",
.@"Pacific/Saipan" => "West Pacific Standard Time",
.@"Pacific/Tahiti" => "Hawaiian Standard Time",
.@"Pacific/Tarawa" => "UTC+12",
.@"Pacific/Tongatapu" => "Tonga Standard Time",
.@"Pacific/Truk" => "West Pacific Standard Time",
.@"Pacific/Wake" => "UTC+12",
.@"Pacific/Wallis" => "UTC+12",
};
}
};
================================================
FILE: src/timezone.zig
================================================
const std = @import("std");
const builtin = @import("builtin");
const zeit = @import("zeit.zig");
const assert = std.debug.assert;
const Month = zeit.Month;
const Seconds = zeit.Seconds;
const Weekday = zeit.Weekday;
const s_per_min = std.time.s_per_min;
const s_per_hour = std.time.s_per_hour;
const s_per_day = std.time.s_per_day;
pub const TimeZone = union(enum) {
fixed: Fixed,
posix: Posix,
tzinfo: TZInfo,
windows: switch (builtin.os.tag) {
.windows => Windows,
else => Noop,
},
pub fn adjust(self: TimeZone, timestamp: Seconds) AdjustedTime {
return switch (self) {
inline else => |tz| tz.adjust(timestamp),
};
}
pub fn deinit(self: TimeZone) void {
return switch (self) {
.fixed => {},
.posix => {},
.tzinfo => |tz| tz.deinit(),
.windows => |tz| tz.deinit(),
};
}
};
pub const AdjustedTime = struct {
designation: []const u8,
timestamp: Seconds,
is_dst: bool,
};
/// A Noop timezone we use for the windows struct when not on windows
pub const Noop = struct {
pub fn adjust(_: Noop, timestamp: Seconds) AdjustedTime {
return .{
.designation = "noop",
.timestamp = timestamp,
.is_dst = false,
};
}
pub fn deinit(_: Noop) void {}
};
/// A fixed timezone
pub const Fixed = struct {
name: []const u8,
offset: Seconds,
is_dst: bool,
pub fn adjust(self: Fixed, timestamp: Seconds) AdjustedTime {
return .{
.designation = self.name,
.timestamp = timestamp + self.offset,
.is_dst = self.is_dst,
};
}
};
/// A parsed representation of a Posix TZ string
/// std offset dst [offset],start[/time],end[/time]
/// std and dst can be quoted with <>
/// offsets and times can be [+-]hh[:mm[:ss]]
/// start and end are of the form J<n>, <n> or M<m>.<w>.<d>
pub const Posix = struct {
/// abbreviation for standard time
std: []const u8,
/// standard time offset in seconds
std_offset: Seconds,
/// abbreviation for daylight saving time
dst: ?[]const u8 = null,
/// offset when in dst, defaults to one hour less than std_offset if not present
dst_offset: ?Seconds = null,
start: ?DSTSpec = null,
end: ?DSTSpec = null,
const DSTSpec = union(enum) {
/// J<n>: julian day between 1 and 365, Leap day is never counted even in leap
/// years
julian: struct {
day: u9,
time: Seconds = 7200,
},
/// <n>: julian day between 0 and 365. Leap day counts
julian_leap: struct {
day: u9,
time: Seconds = 7200,
},
/// M<m>.<w>.<d>: day d of week w of month m. Day is 0 (sunday) to 6. week
/// is 1 to 5, where 5 would mean last d day of the month.
mwd: struct {
month: Month,
week: u6,
day: Weekday,
time: Seconds = 7200,
},
fn parse(str: []const u8) !DSTSpec {
assert(str.len > 0);
switch (str[0]) {
'J' => {
const julian = try std.fmt.parseInt(u9, str[1..], 10);
return .{ .julian = .{ .day = julian } };
},
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
const julian = try std.fmt.parseInt(u9, str, 10);
return .{ .julian_leap = .{ .day = julian } };
},
'M' => {
var i: usize = 1;
const m_end = std.mem.indexOfScalarPos(u8, str, i, '.') orelse return error.InvalidPosix;
const month = try std.fmt.parseInt(u4, str[i..m_end], 10);
i = m_end + 1;
const w_end = std.mem.indexOfScalarPos(u8, str, i, '.') orelse return error.InvalidPosix;
const week = try std.fmt.parseInt(u6, str[i..w_end], 10);
i = w_end + 1;
const day = try std.fmt.parseInt(u3, str[i..], 10);
return .{
.mwd = .{
.month = @enumFromInt(month),
.week = week,
.day = @enumFromInt(day),
},
};
},
else => {},
}
return error.InvalidPosix;
}
};
pub fn parse(str: []const u8) !Posix {
var std_: []const u8 = "";
var std_offset: Seconds = 0;
var dst: ?[]const u8 = null;
var dst_offset: ?Seconds = null;
var start: ?DSTSpec = null;
var end: ?DSTSpec = null;
const State = enum {
std,
std_offset,
dst,
dst_offset,
start,
end,
};
var state: State = .std;
var i: usize = 0;
while (i < str.len) : (i += 1) {
switch (state) {
.std => {
switch (str[i]) {
'<' => {
// quoted. Consume until >
const end_qt = std.mem.indexOfScalar(u8, str[i..], '>') orelse return error.InvalidPosix;
std_ = str[i + 1 .. end_qt + i];
i = end_qt;
state = .std_offset;
},
else => {
i = std.mem.indexOfAnyPos(u8, str, i, "+-0123456789") orelse return error.InvalidPosix;
std_ = str[0..i];
// backup one so this gets parsed as an offset
i -= 1;
state = .std_offset;
},
}
},
.std_offset => {
const offset_start = i;
while (i < str.len) : (i += 1) {
switch (str[i]) {
'+',
'-',
':',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
=> {
if (i == str.len - 1)
std_offset = parseTime(str[offset_start..]);
},
else => {
std_offset = parseTime(str[offset_start..i]);
i -= 1;
state = .dst;
break;
},
}
}
},
.dst => {
switch (str[i]) {
'<' => {
// quoted. Consume until >
const dst_start = i + 1;
i = std.mem.indexOfScalarPos(u8, str, i, '>') orelse return error.InvalidPosix;
dst = str[dst_start..i];
},
else => {
const dst_start = i;
i += 1;
while (i < str.len) : (i += 1) {
switch (str[i]) {
',' => {
dst = str[dst_start..i];
state = .start;
break;
},
'+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
dst = str[dst_start..i];
// backup one so this gets parsed as an offset
i -= 1;
state = .dst_offset;
break;
},
else => {
if (i == str.len - 1)
dst = str[dst_start..];
},
}
}
},
}
},
.dst_offset => {
const offset_start = i;
while (i < str.len) : (i += 1) {
switch (str[i]) {
'+',
'-',
':',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
=> {
if (i == str.len - 1)
std_offset = parseTime(str[offset_start..]);
},
',' => {
dst_offset = parseTime(str[offset_start..i]);
state = .start;
break;
},
else => {},
}
}
},
.start => {
const comma_idx = std.mem.indexOfScalarPos(u8, str, i, ',') orelse return error.InvalidPosix;
if (std.mem.indexOfScalarPos(u8, str[0..comma_idx], i, '/')) |idx| {
start = try DSTSpec.parse(str[i..idx]);
switch (start.?) {
.julian => |*j| j.time = parseTime(str[idx + 1 .. comma_idx]),
.julian_leap => |*j| j.time = parseTime(str[idx + 1 .. comma_idx]),
.mwd => |*m| m.time = parseTime(str[idx + 1 .. comma_idx]),
}
} else {
start = try DSTSpec.parse(str[i..comma_idx]);
}
state = .end;
i = comma_idx;
},
.end => {
if (std.mem.indexOfScalarPos(u8, str, i, '/')) |idx| {
end = try DSTSpec.parse(str[i..idx]);
switch (end.?) {
.julian => |*j| j.time = parseTime(str[idx + 1 ..]),
.julian_leap => |*j| j.time = parseTime(str[idx + 1 ..]),
.mwd => |*m| m.time = parseTime(str[idx + 1 ..]),
}
} else {
end = try DSTSpec.parse(str[i..]);
}
break;
},
}
}
return .{
.std = std_,
.std_offset = std_offset,
.dst = dst,
.dst_offset = dst_offset,
.start = start,
.end = end,
};
}
fn parseTime(str: []const u8) Seconds {
const State = enum {
hour,
minute,
second,
};
var is_neg = false;
var state: State = .hour;
var offset_h: i64 = 0;
var offset_m: i64 = 0;
var offset_s: i64 = 0;
var i: usize = 0;
while (i < str.len) : (i += 1) {
switch (state) {
.hour => {
switch (str[i]) {
'-' => is_neg = true,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => |d| {
offset_h = offset_h * 10 + @as(i64, d - '0');
},
':' => state = .minute,
else => {},
}
},
.minute => {
switch (str[i]) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => |d| {
offset_m = offset_m * 10 + @as(i64, d - '0');
},
':' => state = .second,
else => {},
}
},
.second => {
switch (str[i]) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => |d| {
offset_s = offset_s * 10 + @as(i64, d - '0');
},
else => {},
}
},
}
}
const offset = offset_h * s_per_hour + offset_m * s_per_min + offset_s;
return if (is_neg) -offset else offset;
}
/// reports true if the unix timestamp occurs when DST is in effect
fn isDST(self: Posix, timestamp: Seconds) bool {
const start = self.start orelse return false;
const end = self.end orelse return false;
const days_from_epoch: zeit.Days = @intCast(@divFloor(timestamp, s_per_day));
const civil = zeit.civilFromDays(days_from_epoch);
const civil_month = @intFromEnum(civil.month);
const start_s: Seconds = switch (start) {
.julian => |rule| blk: {
const days = days_from_epoch - civil.month.daysBefore(civil.year) - civil.day + rule.day + 1;
var s = (@as(i64, days - 1)) * s_per_day + rule.time;
if (zeit.isLeapYear(civil.year) and rule.day >= 60) {
s += s_per_day;
}
break :blk s + self.std_offset;
},
.julian_leap => |rule| blk: {
const days = days_from_epoch - civil.month.daysBefore(civil.year) - civil.day + rule.day;
break :blk @as(i64, days) * s_per_day + rule.time + self.std_offset;
},
.mwd => |rule| blk: {
const rule_month = @intFromEnum(rule.month);
if (civil_month < rule_month) return false;
// bail early if we are greater than this month. we know we only
// rely on the end time. We yield a value that is before the
// timestamp
if (civil_month > rule_month) break :blk timestamp - 1;
// we are in the same month
// first_of_month is the weekday on the first of the month
const first_of_month = zeit.weekdayFromDays(days_from_epoch - civil.day + 1);
// days is the first "rule day" of the month (ie the first
// Sunday of the month)
var days: u9 = first_of_month.daysUntil(rule.day) + 1;
var i: usize = 1;
while (i < rule.week) : (i += 1) {
if (days + 7 >= rule.month.lastDay(civil.year)) break;
days += 7;
}
// days_from_epoch is the number of days to the DST day from the
// epoch
const dst_days_from_epoch: i64 = days_from_epoch - civil.day + days;
break :blk @as(i64, dst_days_from_epoch) * s_per_day + rule.time + self.std_offset;
},
};
const end_s: Seconds = switch (end) {
.julian => |rule| blk: {
const days = days_from_epoch - civil.month.daysBefore(civil.year) - civil.day + rule.day + 1;
var s = (@as(i64, days) - 1) * s_per_day + rule.time;
if (zeit.isLeapYear(civil.year) and rule.day >= 60) {
s += s_per_day;
}
break :blk s + self.std_offset;
},
.julian_leap => |rule| blk: {
const days = days_from_epoch - civil.month.daysBefore(civil.year) - civil.day + rule.day + 1;
break :blk @as(i64, days) * s_per_day + rule.time + self.std_offset;
},
.mwd => |rule| blk: {
const rule_month = @intFromEnum(rule.month);
if (civil_month > rule_month) return false;
// bail early if we are less than this month. we know we only
// rely on the start time. We yield a value that is after the
// timestamp
if (civil_month < rule_month) break :blk timestamp + 1;
// first_of_month is the weekday on the first of the month
const first_of_month = zeit.weekdayFromDays(days_from_epoch - civil.day + 1);
// days is the first "rule day" of the month (ie the first
// Sunday of the month)
var days: u9 = first_of_month.daysUntil(rule.day) + 1;
var i: usize = 1;
while (i < rule.week) : (i += 1) {
if (days + 7 >= rule.month.lastDay(civil.year)) break;
days += 7;
}
// days_from_epoch is the number of days to the DST day from the
// epoch
const dst_days_from_epoch: i64 = days_from_epoch - civil.day + days;
break :blk @as(i64, dst_days_from_epoch) * s_per_day + rule.time + self.std_offset;
},
};
return timestamp >= start_s and timestamp < end_s;
}
pub fn adjust(self: Posix, timestamp: Seconds) AdjustedTime {
if (self.isDST(timestamp)) {
return .{
.designation = self.dst orelse self.std,
.timestamp = timestamp - (self.dst_offset orelse self.std_offset - s_per_hour),
.is_dst = true,
};
}
return .{
.designation = self.std,
.timestamp = timestamp - self.std_offset,
.is_dst = false,
};
}
};
pub const TZInfo = struct {
allocator: std.mem.Allocator,
transitions: []const Transition,
timetypes: []const Timetype,
leapseconds: []const Leapsecond,
footer: ?[]const u8,
posix_tz: ?Posix,
const Leapsecond = struct {
occurrence: i48,
correction: i16,
};
const Timetype = struct {
offset: i32,
flags: u8,
name_data: [6:0]u8,
pub fn name(self: *const Timetype) [:0]const u8 {
return std.mem.sliceTo(self.name_data[0..], 0);
}
pub fn isDst(self: Timetype) bool {
return (self.flags & 0x01) > 0;
}
pub fn standardTimeIndicator(self: Timetype) bool {
return (self.flags & 0x02) > 0;
}
pub fn utIndicator(self: Timetype) bool {
return (self.flags & 0x04) > 0;
}
};
const Transition = struct {
ts: Seconds,
timetype: *Timetype,
};
const Header = extern struct {
magic: [4]u8,
version: u8,
reserved: [15]u8,
counts: extern struct {
isutcnt: u32,
isstdcnt: u32,
leapcnt: u32,
timecnt: u32,
typecnt: u32,
charcnt: u32,
},
};
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !TZInfo {
var legacy_header = try reader.takeStruct(Header, .big); // handles endianness for us
if (!std.mem.eql(u8, &legacy_header.magic, "TZif")) return error.BadHeader;
if (legacy_header.version != 0 and legacy_header.version != '2' and legacy_header.version != '3') return error.BadVersion;
if (legacy_header.version == 0) {
return parseBlock(allocator, reader, legacy_header, true);
} else {
// If the format is modern, just skip over the legacy data
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;
const skipped = try reader.discard(.limited(skipv));
// this should be unreachable, as discard above handles EndOfStream and ReadFailed:
if (skipped != skipv) return error.BadHeader;
var header = try reader.takeStruct(Header, .big); // handles endianness for us
if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader;
if (header.version != '2' and header.version != '3') return error.BadVersion;
return parseBlock(allocator, reader, header, false);
}
}
fn parseBlock(allocator: std.mem.Allocator, reader: *std.Io.Reader, header: Header, legacy: bool) !TZInfo {
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"
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"
if (header.counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero
if (header.counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero
if (header.counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical
var leapseconds = try allocator.alloc(Leapsecond, header.counts.leapcnt);
errdefer allocator.free(leapseconds);
var transitions = try allocator.alloc(Transition, header.counts.timecnt);
errdefer allocator.free(transitions);
var timetypes = try allocator.alloc(Timetype, header.counts.typecnt);
errdefer allocator.free(timetypes);
// Parse transition types
var i: usize = 0;
while (i < header.counts.timecnt) : (i += 1) {
transitions[i].ts = if (legacy) try reader.takeInt(i32, .big) else try reader.takeInt(i64, .big);
}
i = 0;
while (i < header.counts.timecnt) : (i += 1) {
const tt = try reader.takeByte();
if (tt >= timetypes.len) return error.Malformed; // rfc8536: Each type index MUST be in the range [0, "typecnt" - 1]
transitions[i].timetype = &timetypes[tt];
}
// Parse time types
i = 0;
while (i < header.counts.typecnt) : (i += 1) {
const offset = try reader.takeInt(i32, .big);
if (offset < -2147483648) return error.Malformed; // rfc8536: utoff [...] MUST NOT be -2**31
const dst = try reader.takeByte();
if (dst != 0 and dst != 1) return error.Malformed; // rfc8536: (is)dst [...] The value MUST be 0 or 1.
const idx = try reader.takeByte();
if (idx > header.counts.charcnt - 1) return error.Malformed; // rfc8536: (desig)idx [...] Each index MUST be in the range [0, "charcnt" - 1]
timetypes[i] = .{
.offset = offset,
.flags = dst,
.name_data = undefined,
};
// Temporarily cache idx in name_data to be processed after we've read the designator names below
timetypes[i].name_data[0] = idx;
}
var designators_data: [256 + 6]u8 = undefined;
try reader.readSliceAll(designators_data[0..header.counts.charcnt]);
const designators = designators_data[0..header.counts.charcnt];
if (designators[designators.len - 1] != 0) return error.Malformed; // rfc8536: charcnt [...] includes the trailing NUL (0x00) octet
// Iterate through the timetypes again, setting the designator names
for (timetypes) |*tt| {
const name = std.mem.sliceTo(designators[tt.name_data[0]..], 0);
// We are mandating the "SHOULD" 6-character limit so we can pack the struct better, and to conform to POSIX.
if (name.len > 6) return error.Malformed; // rfc8536: Time zone designations SHOULD consist of at least three (3) and no more than six (6) ASCII characters.
@memcpy(tt.name_data[0..name.len], name);
tt.name_data[name.len] = 0;
}
// Parse leap seconds
i = 0;
while (i < header.counts.leapcnt) : (i += 1) {
const occur: i64 = if (legacy) try reader.takeInt(i32, .big) else try reader.takeInt(i64, .big);
if (occur < 0) return error.Malformed; // rfc8536: occur [...] MUST be nonnegative
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
if (occur > std.math.maxInt(i48)) return error.Malformed; // Unreasonably far into the future
const corr = try reader.takeInt(i32, .big);
if (i == 0 and corr != -1 and corr != 1) return error.Malformed; // rfc8536: The correction value in the first leap-second record, if present, MUST be either one (1) or minus one (-1)
if (i > 0 and leapseconds[i - 1].correction != corr + 1 and leapseconds[i - 1].correction != corr - 1) return error.Malformed; // rfc8536: The correction values in adjacent leap-second records MUST differ by exactly one (1)
if (corr > std.math.maxInt(i16)) return error.Malformed; // Unreasonably large correction
leapseconds[i] = .{
.occurrence = @as(i48, @intCast(occur)),
.correction = @as(i16, @intCast(corr)),
};
}
// Parse standard/wall indicators
i = 0;
while (i < header.counts.isstdcnt) : (i += 1) {
const stdtime = try reader.takeByte();
if (stdtime == 1) {
timetypes[i].flags |= 0x02;
}
}
// Parse UT/local indicators
i = 0;
while (i < header.counts.isutcnt) : (i += 1) {
const ut = try reader.takeByte();
if (ut == 1) {
timetypes[i].flags |= 0x04;
if (!timetypes[i].standardTimeIndicator()) return error.Malformed; // rfc8536: standard/wall value MUST be one (1) if the UT/local value is one (1)
}
}
// Footer
var footer: ?[]const u8 = null;
var posix: ?Posix = null;
if (!legacy) {
if ((try reader.takeByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline
const footer_mem = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
error.StreamTooLong => return error.OverlargeFooter, // Read more than reader buffer bytes, much larger than any reasonable POSIX TZ string
else => return err,
};
if (footer_mem.len != 0) {
footer = try allocator.dupe(u8, footer_mem);
posix = try Posix.parse(footer.?);
}
}
errdefer if (footer) |ft| allocator.free(ft);
return .{
.allocator = allocator,
.transitions = transitions,
.timetypes = timetypes,
.leapseconds = leapseconds,
.footer = footer,
.posix_tz = posix,
};
}
pub fn deinit(self: TZInfo) void {
if (self.footer) |footer| {
self.allocator.free(footer);
}
self.allocator.free(self.leapseconds);
self.allocator.free(self.transitions);
self.allocator.free(self.timetypes);
}
/// adjust a unix timestamp to the timezone
pub fn adjust(self: TZInfo, timestamp: Seconds) AdjustedTime {
// if we are past the last transition and have a footer, we use the
// footer data
if ((self.transitions.len == 0 or self.transitions[self.transitions.len - 1].ts <= timestamp) and
self.posix_tz != null)
{
const posix = self.posix_tz.?;
return posix.adjust(timestamp);
}
const transition: Transition = blk: for (self.transitions, 0..) |transition, i| {
// TODO: implement what go does, which is a copy of c for how to
// handle times before the first transition how to handle this
if (i == 0 and transition.ts > timestamp) @panic("unimplemented. please complain to tim");
if (transition.ts <= timestamp) continue;
// we use the latest transition before ts, which is one less than
// our current iter
break :blk self.transitions[i - 1];
} else self.transitions[self.transitions.len - 1];
return .{
.designation = transition.timetype.name(),
.timestamp = timestamp + transition.timetype.offset,
.is_dst = transition.timetype.isDst(),
};
}
};
pub const Windows = struct {
const windows = struct {
const BOOL = std.os.windows.BOOL;
const BOOLEAN = std.os.windows.BOOLEAN;
const DWORD = std.os.windows.DWORD;
const FILETIME = std.os.windows.FILETIME;
const LONG = std.os.windows.LONG;
const USHORT = std.os.windows.USHORT;
const WCHAR = std.os.windows.WCHAR;
const WORD = std.os.windows.WORD;
const epoch = std.time.epoch.windows;
const ERROR_SUCCESS = 0x00;
const ERROR_NO_MORE_ITEMS = 0x103;
pub const TIME_ZONE_ID_INVALID = @as(DWORD, std.math.maxInt(DWORD));
const DYNAMIC_TIME_ZONE_INFORMATION = extern struct {
Bias: LONG,
StandardName: [32]WCHAR,
StandardDate: SYSTEMTIME,
StandardBias: LONG,
DaylightName: [32]WCHAR,
DaylightDate: SYSTEMTIME,
DaylightBias: LONG,
TimeZoneKeyName: [128]WCHAR,
DynamicDaylightTimeDisabled: BOOLEAN,
};
const SYSTEMTIME = extern struct {
wYear: WORD,
wMonth: WORD,
wDayOfWeek: WORD,
wDay: WORD,
wHour: WORD,
wMinute: WORD,
wSecond: WORD,
wMilliseconds: WORD,
};
const TIME_ZONE_INFORMATION = extern struct {
Bias: LONG,
StandardName: [32]WCHAR,
StandardDate: SYSTEMTIME,
StandardBias: LONG,
DaylightName: [32]WCHAR,
DaylightDate: SYSTEMTIME,
DaylightBias: LONG,
};
pub extern "advapi32" fn EnumDynamicTimeZoneInformation(dwIndex: DWORD, lpTimeZoneInformation: *DYNAMIC_TIME_ZONE_INFORMATION) callconv(.winapi) DWORD;
pub extern "kernel32" fn GetDynamicTimeZoneInformation(pTimeZoneInformation: *DYNAMIC_TIME_ZONE_INFORMATION) callconv(.winapi) DWORD;
pub extern "kernel32" fn GetTimeZoneInformationForYear(wYear: USHORT, pdtzi: ?*const DYNAMIC_TIME_ZONE_INFORMATION, ptzi: *TIME_ZONE_INFORMATION) callconv(.winapi) BOOL;
pub extern "kernel32" fn SystemTimeToTzSpecificLocalTimeEx(lpTimeZoneInfo: ?*const DYNAMIC_TIME_ZONE_INFORMATION, lpUniversalTime: *const SYSTEMTIME, lpLocalTime: *SYSTEMTIME) callconv(.winapi) BOOL;
};
zoneinfo: windows.DYNAMIC_TIME_ZONE_INFORMATION,
allocator: std.mem.Allocator,
standard_name: []const u8,
dst_name: []const u8,
/// retrieves the local timezone settings for this machine
pub fn local(allocator: std.mem.Allocator) !Windows {
var info: windows.DYNAMIC_TIME_ZONE_INFORMATION = undefined;
const result = windows.GetDynamicTimeZoneInformation(&info);
if (result == windows.TIME_ZONE_ID_INVALID) return error.TimeZoneIdInvalid;
const std_idx = std.mem.indexOfScalar(u16, &info.StandardName, 0x00) orelse info.StandardName.len;
const dst_idx = std.mem.indexOfScalar(u16, &info.DaylightName, 0x00) orelse info.DaylightName.len;
const standard_name = try std.unicode.utf16LeToUtf8Alloc(allocator, info.StandardName[0..std_idx]);
const dst_name = try std.unicode.utf16LeToUtf8Alloc(allocator, info.DaylightName[0..dst_idx]);
return .{
.zoneinfo = info,
.allocator = allocator,
.standard_name = standard_name,
.dst_name = dst_name,
};
}
pub fn deinit(self: Windows) void {
self.allocator.free(self.standard_name);
self.allocator.free(self.dst_name);
}
/// Adjusts the time to the timezone
/// 1. Convert timestamp to windows.SYSTEMTIME using internal methods
/// 2. Convert SYSTEMTIME to target timezone using windows api
/// 3. Get the relevant TIME_ZONE_INFORMATION for the year
/// 4. Determine if we are in DST or not
/// 5. Return result
pub fn adjust(self: Windows, timestamp: Seconds) AdjustedTime {
const instant = zeit.instant(.{ .unix_timestamp = timestamp }, &zeit.utc);
const time = instant.time();
const systemtime: windows.SYSTEMTIME = .{
.wYear = @intCast(time.year),
.wMonth = @intFromEnum(time.month),
.wDayOfWeek = 0, // not used in calculation
.wDay = time.day,
.wHour = time.hour,
.wMinute = time.minute,
.wSecond = time.second,
.wMilliseconds = time.millisecond,
};
var localtime: windows.SYSTEMTIME = undefined;
if (windows.SystemTimeToTzSpecificLocalTimeEx(&self.zoneinfo, &systemtime, &localtime) == .FALSE) {
const err = std.os.windows.GetLastError();
std.log.err("{}", .{err});
@panic("TODO");
}
var tzi: windows.TIME_ZONE_INFORMATION = undefined;
if (windows.GetTimeZoneInformationForYear(localtime.wYear, &self.zoneinfo, &tzi) == .FALSE) {
const err = std.os.windows.GetLastError();
std.log.err("{}", .{err});
@panic("TODO");
}
const is_dst = isDST(timestamp, &tzi, &localtime);
return .{
.designation = if (is_dst) self.dst_name else self.standard_name,
.timestamp = systemtimeToUnixTimestamp(localtime),
.is_dst = is_dst,
};
}
fn systemtimeToUnixTimestamp(sys: windows.SYSTEMTIME) Seconds {
const lzt = systemtimetoZeitTime(sys);
return lzt.instant().unixTimestamp();
}
fn systemtimetoZeitTime(sys: windows.SYSTEMTIME) zeit.Time {
return .{
.year = sys.wYear,
.month = @enumFromInt(sys.wMonth),
.day = @intCast(sys.wDay),
.hour = @intCast(sys.wHour),
.minute = @intCast(sys.wMinute),
.second = @intCast(sys.wSecond),
.millisecond = @intCast(sys.wMilliseconds),
};
}
fn isDST(timestamp: Seconds, tzi: *const windows.TIME_ZONE_INFORMATION, time: *const windows.SYSTEMTIME) bool {
// If wMonth on StandardDate is 0, the timezone doesn't have DST
if (tzi.StandardDate.wMonth == 0) return false;
const start = tzi.DaylightDate;
const end = tzi.StandardDate;
// Before DST starts
if (time.wMonth < start.wMonth) return false;
// After DST ends
if (time.wMonth > end.wMonth) return false;
// In the months between
if (time.wMonth > start.wMonth and time.wMonth < end.wMonth) return true;
const days_from_epoch: zeit.Days = @intCast(@divFloor(timestamp, s_per_day));
// first_of_month is the weekday on the first of the month
const first_of_month = zeit.weekdayFromDays(
days_from_epoch - @as(zeit.Days, @intCast(time.wDay)) + 1,
);
// In the start transition month
if (time.wMonth == start.wMonth) {
// days is the first "rule day" of the month (ie the first
// Sunday of the month)
var days: u9 = first_of_month.daysUntil(@enumFromInt(start.wDayOfWeek)) + 1;
var i: usize = 1;
while (i < start.wDay) : (i += 1) {
const month: zeit.Month = @enumFromInt(start.wMonth);
if (days + 7 >= month.lastDay(time.wYear)) break;
days += 7;
}
if (time.wDay == days) {
if (time.wHour == start.wHour) {
return time.wMinute >= start.wMinute;
}
return time.wHour >= start.wHour;
}
return time.wDay >= days;
}
// In the end transition month
if (time.wMonth == end.wMonth) {
// days is the first "rule day" of the month (ie the first
// Sunday of the month)
var days: u9 = first_of_month.daysUntil(@enumFromInt(end.wDayOfWeek)) + 1;
var i: usize = 1;
while (i < end.wDay) : (i += 1) {
const month: zeit.Month = @enumFromInt(end.wMonth);
if (days + 7 >= month.lastDay(time.wYear)) break;
days += 7;
}
if (time.wDay == days) {
if (time.wHour == end.wHour) {
return time.wMinute < end.wMinute;
}
return time.wHour < end.wHour;
}
return time.wDay < days;
}
return false;
}
pub fn loadFromName(allocator: std.mem.Allocator, name: []const u8) !Windows {
var buf: [128]u16 = undefined;
const n = try std.unicode.utf8ToUtf16Le(&buf, name);
const target = buf[0..n];
var result: windows.DWORD = windows.ERROR_SUCCESS;
var i: windows.DWORD = 0;
var dtzi: windows.DYNAMIC_TIME_ZONE_INFORMATION = undefined;
while (result == windows.ERROR_SUCCESS) : (i += 1) {
result = windows.EnumDynamicTimeZoneInformation(i, &dtzi);
const name_idx = std.mem.indexOfScalar(u16, &dtzi.TimeZoneKeyName, 0x00) orelse dtzi.TimeZoneKeyName.len;
if (std.mem.eql(u16, target, dtzi.TimeZoneKeyName[0..name_idx])) break;
} else return error.TimezoneNotFound;
const std_idx = std.mem.indexOfScalar(u16, &dtzi.StandardName, 0x00) orelse dtzi.StandardName.len;
const dst_idx = std.mem.indexOfScalar(u16, &dtzi.DaylightName, 0x00) orelse dtzi.DaylightName.len;
const standard_name = try std.unicode.utf16LeToUtf8Alloc(allocator, dtzi.StandardName[0..std_idx]);
const dst_name = try std.unicode.utf16LeToUtf8Alloc(allocator, dtzi.DaylightName[0..dst_idx]);
return .{
.zoneinfo = dtzi,
.allocator = allocator,
.standard_name = standard_name,
.dst_name = dst_name,
};
}
};
test "timezone.zig: test Fixed" {
const fixed: Fixed = .{
.name = "test",
.offset = -600,
.is_dst = false,
};
const adjusted = fixed.adjust(0);
try std.testing.expectEqual(-600, adjusted.timestamp);
}
test "timezone.zig: Posix.isDST" {
const t = try Posix.parse("CST6CDT,M3.2.0,M11.1.0");
try std.testing.expectEqual(false, t.isDST(1704088800)); // Jan 1 2024 00:00:00 CST
try std.testing.expectEqual(false, t.isDST(1733032800)); // Dec 1 2024 00:00:00 CST
try std.testing.expectEqual(true, t.isDST(1717218000)); // Jun 1 2024 00:00:00 CST
// One second after DST starts
try std.testing.expectEqual(true, t.isDST(1710057601));
// One second before DST starts
try std.testing.expectEqual(false, t.isDST(1710057599));
// One second before DST ends
try std.testing.expectEqual(true, t.isDST(1730620799));
// One second after DST ends
try std.testing.expectEqual(false, t.isDST(1730620801));
const j = try Posix.parse("CST6CDT,J1,J4");
try std.testing.expectEqual(true, j.isDST(1704268800));
}
test "timezone.zig: Posix.parseTime" {
try std.testing.expectEqual(0, Posix.parseTime("00:00:00"));
try std.testing.expectEqual(-3600, Posix.parseTime("-1"));
try std.testing.expectEqual(-7200, Posix.parseTime("-02:00:00"));
try std.testing.expectEqual(3660, Posix.parseTime("+1:01"));
}
test "timezone.zig: Posix.parse" {
{
const t = try Posix.parse("<UTC>-1");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(-3600, t.std_offset);
}
{
const t = try Posix.parse("<UTC>1");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(3600, t.std_offset);
}
{
const t = try Posix.parse("<UTC>+1");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(3600, t.std_offset);
}
{
const t = try Posix.parse("UTC+1");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(3600, t.std_offset);
}
{
const t = try Posix.parse("UTC+1:01");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(3660, t.std_offset);
}
{
const t = try Posix.parse("UTC-1:01:01");
try std.testing.expectEqualStrings("UTC", t.std);
try std.testing.expectEqual(-3661, t.std_offset);
}
{
const t = try Posix.parse("CST1CDT");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
}
{
const t = try Posix.parse("CST1<CDT>");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
}
{
const t = try Posix.parse("CST1CDT,J100,J200");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
try std.testing.expectEqual(100, t.start.?.julian.day);
try std.testing.expectEqual(200, t.end.?.julian.day);
}
{
const t = try Posix.parse("CST1CDT,100,200");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
try std.testing.expectEqual(100, t.start.?.julian_leap.day);
try std.testing.expectEqual(200, t.end.?.julian_leap.day);
}
{
const t = try Posix.parse("CST1CDT,M3.5.1,M11.3.0");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
try std.testing.expectEqual(.mar, t.start.?.mwd.month);
try std.testing.expectEqual(5, t.start.?.mwd.week);
try std.testing.expectEqual(.mon, t.start.?.mwd.day);
try std.testing.expectEqual(.nov, t.end.?.mwd.month);
try std.testing.expectEqual(3, t.end.?.mwd.week);
try std.testing.expectEqual(.sun, t.end.?.mwd.day);
}
{
const t = try Posix.parse("CST1CDT,M3.5.1/02:00:00,M11.3.0/1");
try std.testing.expectEqualStrings("CST", t.std);
try std.testing.expectEqual(3600, t.std_offset);
try std.testing.expectEqualStrings("CDT", t.dst.?);
try std.testing.expectEqual(.mar, t.start.?.mwd.month);
try std.testing.expectEqual(5, t.start.?.mwd.week);
try std.testing.expectEqual(.mon, t.start.?.mwd.day);
try std.testing.expectEqual(7200, t.start.?.mwd.time);
try std.testing.expectEqual(.nov, t.end.?.mwd.month);
try std.testing.expectEqual(3, t.end.?.mwd.week);
try std.testing.expectEqual(.sun, t.end.?.mwd.day);
try std.testing.expectEqual(3600, t.end.?.mwd.time);
}
}
test "timezone.zig: Posix.adjust" {
{
const t = try Posix.parse("UTC+1");
const adjusted = t.adjust(0);
try std.testing.expectEqual(-3600, adjusted.timestamp);
}
{
const t = try Posix.parse("CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00");
const adjusted = t.adjust(1704088800);
try std.testing.expectEqual(1704067200, adjusted.timestamp);
try std.testing.expectEqualStrings("CST", adjusted.designation);
const adjusted_dst = t.adjust(1710057600);
try std.testing.expectEqual(1710039600, adjusted_dst.timestamp);
try std.testing.expectEqualStrings("CDT", adjusted_dst.designation);
}
}
test "timezone.zig: Posix.DSTSpec.parse" {
{
const spec = try Posix.DSTSpec.parse("J365");
try std.testing.expectEqual(365, spec.julian.day);
}
{
const spec = try Posix.DSTSpec.parse("365");
try std.testing.expectEqual(365, spec.julian_leap.day);
}
{
const spec = try Posix.DSTSpec.parse("M3.5.1");
try std.testing.expectEqual(.mar, spec.mwd.month);
try std.testing.expectEqual(5, spec.mwd.week);
try std.testing.expectEqual(.mon, spec.mwd.day);
}
{
const spec = try Posix.DSTSpec.parse("M11.3.0");
try std.testing.expectEqual(.nov, spec.mwd.month);
try std.testing.expectEqual(3, spec.mwd.week);
try std.testing.expectEqual(.sun, spec.mwd.day);
}
}
================================================
FILE: src/zeit.zig
================================================
const std = @import("std");
const builtin = @import("builtin");
const location = @import("location.zig");
pub const timezone = @import("timezone.zig");
const assert = std.debug.assert;
pub const TimeZone = timezone.TimeZone;
pub const Location = location.Location;
pub const Days = i32;
pub const Nanoseconds = i128;
pub const Milliseconds = i128;
pub const Seconds = i64;
pub const EnvConfig = struct {
tz: ?[]const u8 = null,
tzdir: ?[]const u8 = null,
};
const ns_per_us = std.time.ns_per_us;
const ns_per_ms = std.time.ns_per_ms;
const ns_per_s = std.time.ns_per_s;
const ns_per_min = std.time.ns_per_min;
const ns_per_hour = std.time.ns_per_hour;
const ns_per_day = std.time.ns_per_day;
const s_per_min = std.time.s_per_min;
const s_per_hour = std.time.s_per_hour;
const s_per_day = std.time.s_per_day;
const days_per_era = 365 * 400 + 97;
pub const utc: TimeZone = .{ .fixed = .{
.name = "UTC",
.offset = 0,
.is_dst = false,
} };
pub fn local(alloc: std.mem.Allocator, io: std.Io, env: EnvConfig) !TimeZone {
switch (builtin.os.tag) {
.windows => {
const win = try timezone.Windows.local(alloc);
return .{ .windows = win };
},
else => {
if (env.tz) |tz| {
return localFromEnv(alloc, io, tz, env);
}
const f = try std.Io.Dir.cwd().openFile(io, "/etc/localtime", .{});
defer f.close(io);
var io_buffer: [2048]u8 = undefined;
var reader = f.reader(io, &io_buffer);
return .{ .tzinfo = try timezone.TZInfo.parse(alloc, &reader.interface) };
},
}
}
// Returns the local time zone from the given TZ environment variable
// TZ can be one of three things:
// 1. A POSIX TZ string (TZ=CST6CDT,M3.2.0,M11.1.0)
// 2. An absolute path, prefixed with ':' (TZ=:/etc/localtime)
// 3. A relative path, prefixed with ':'
fn localFromEnv(
alloc: std.mem.Allocator,
io: std.Io,
tz: []const u8,
env: EnvConfig,
) !TimeZone {
assert(tz.len != 0); // TZ is empty string
// Return early we we are a posix TZ string
if (tz[0] != ':') return .{ .posix = try timezone.Posix.parse(tz) };
assert(tz.len > 1); // TZ not long enough
if (tz[1] == '/') {
const f = try std.Io.Dir.cwd().openFile(io, tz[1..], .{});
defer f.close(io);
var io_buffer: [1024]u8 = undefined;
var reader = f.reader(io, &io_buffer);
return .{ .tzinfo = try timezone.TZInfo.parse(alloc, &reader.interface) };
}
if (std.meta.stringToEnum(Location, tz[1..])) |loc|
return loadTimeZone(alloc, io, loc, env)
else
return error.UnknownLocation;
}
pub fn loadTimeZone(
alloc: std.mem.Allocator,
io: std.Io,
loc: Location,
env: EnvConfig,
) !TimeZone {
switch (builtin.os.tag) {
.windows => {
const tz = try timezone.Windows.loadFromName(alloc, loc.asText());
return .{ .windows = tz };
},
else => {},
}
var dir: std.Io.Dir = blk: {
// If we have an env and a TZDIR, use that
if (env.tzdir) |tzdir| {
const d = try std.Io.Dir.cwd().openDir(io, tzdir, .{});
break :blk d;
}
// Otherwise check well-known locations
const zone_dirs = [_][]const u8{
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
"/share/zoneinfo/",
"/etc/zoneinfo/",
};
for (zone_dirs) |zone_dir| {
const d = std.Io.Dir.cwd().openDir(io, zone_dir, .{}) catch continue;
break :blk d;
} else return error.FileNotFound;
};
defer dir.close(io);
const f = try dir.openFile(io, loc.asText(), .{});
defer f.close(io);
var io_buffer: [2048]u8 = undefined;
var reader = f.reader(io, &io_buffer);
return .{ .tzinfo = try timezone.TZInfo.parse(alloc, &reader.interface) };
}
/// An Instant in time. Instants occur at a precise time and place, thus must
/// always carry with them a timezone.
pub const Instant = struct {
/// the instant of time, in nanoseconds
timestamp: Nanoseconds = 0,
/// every instant occurs in a timezone. This is the timezone
timezone: *const TimeZone,
/// possible sources to create an Instant
pub const Source = union(enum) {
/// current wall clock determined using std.Io
now: std.Io,
/// a specific unix timestamp (in seconds)
unix_timestamp: Seconds,
/// a specific unix timestamp (in nanoseconds)
unix_nano: Nanoseconds,
/// create an Instant from a calendar date and time
time: Time,
};
/// text time format for parsing
pub const TextFormat = enum {
/// parse a datetime from an ISO8601 string
/// Supports most ISO8601 formats, _except_:
/// - Week numbers (ie YYYY-Www)
/// - Fractional minutes (ie YYYY-MM-DDTHH:MM.mmm)
///
/// Strings can be in the extended or compact format and use ' ' or "T"
/// as the time delimiter
/// Examples of paresable strings:
/// YYYY-MM-DD
/// YYYY-MM-DDTHH
/// YYYY-MM-DDTHH:MM
/// YYYY-MM-DDTHH:MM:SS
/// YYYY-MM-DDTHH:MM:SS.sss
/// YYYY-MM-DDTHH:MM:SS.ssssss
/// YYYY-MM-DDTHH:MM:SSZ
/// YYYY-MM-DDTHH:MM:SS+hh:mm
/// YYYYMMDDTHHMMSSZ
iso8601,
/// Parse a datetime from an RFC3339 string. RFC3339 is similar to
/// ISO8601 but is more strict, and allows for arbitrary fractional
/// seconds. Using this field will use the same parser `iso8601`, but is
/// provided for clarity
/// has nanoseconds precision (9 digits after period), same as rfc3339Nano could be
/// Format: YYYY-MM-DDTHH:MM:SS.s{,9}+hh:mm
rfc3339,
/// Parse a datetime from an RFC5322 date-time spec
rfc5322,
/// Parse a datetime from an RFC2822 date-time spec. This is an alias for RFC5322
rfc2822,
/// Parse a datetime from an RFC1123 date-time spec
rfc1123,
};
/// convert this Instant to another timezone
pub fn in(self: Instant, zone: *const TimeZone) Instant {
return .{
.timestamp = self.timestamp,
.timezone = zone,
};
}
// convert the nanosecond timestamp into a unix timestamp (in seconds)
pub fn unixTimestamp(self: Instant) Seconds {
return @intCast(@divFloor(self.timestamp, ns_per_s));
}
pub fn milliTimestamp(self: Instant) Milliseconds {
return @intCast(@divFloor(self.timestamp, ns_per_ms));
}
// generate a calendar date and time for this instant
pub fn time(self: Instant) Time {
const adjusted = self.timezone.adjust(self.unixTimestamp());
const days = daysSinceEpoch(adjusted.timestamp);
const date = civilFromDays(days);
var seconds = @mod(adjusted.timestamp, s_per_day);
const hours = @divFloor(seconds, s_per_hour);
seconds -= hours * s_per_hour;
const minutes = @divFloor(seconds, s_per_min);
seconds -= minutes * s_per_min;
// get the nanoseconds from the original timestamp
var nanos = @mod(self.timestamp, ns_per_s);
const millis = @divFloor(nanos, ns_per_ms);
nanos -= millis * ns_per_ms;
const micros = @divFloor(nanos, ns_per_us);
nanos -= micros * ns_per_us;
return .{
.year = date.year,
.month = date.month,
.day = date.day,
.hour = @intCast(hours),
.minute = @intCast(minutes),
.second = @intCast(seconds),
.millisecond = @intCast(millis),
.microsecond = @intCast(micros),
.nanosecond = @intCast(nanos),
.offset = @intCast(adjusted.timestamp - self.unixTimestamp()),
.designation = adjusted.designation,
};
}
/// add the duration to the Instant
pub fn add(self: Instant, duration: Duration) error{Overflow}!Instant {
const ns = try duration.inNanoseconds();
// check for addition with overflow
const timestamp = @addWithOverflow(self.timestamp, ns);
if (timestamp[1] == 1) return error.Overflow;
return .{
.timestamp = timestamp[0],
.timezone = self.timezone,
};
}
/// subtract the duration from the Instant
pub fn subtract(self: Instant, duration: Duration) error{Overflow}!Instant {
const ns = try duration.inNanoseconds();
// check for subtraction with overflow
const timestamp = @subWithOverflow(self.timestamp, ns);
if (timestamp[1] == 1) return error.Overflow;
return .{
.timestamp = timestamp[0],
.timezone = self.timezone,
};
}
};
/// Creates a new Instant from time value specified by *source*.
///
/// See also .instantFromText().
pub fn instant(source: Instant.Source, tz: *const TimeZone) Instant {
const ts: Nanoseconds = switch (source) {
.now => |io| std.Io.Clock.now(.real, io).nanoseconds,
.unix_timestamp => |unix| @as(i128, unix) * ns_per_s,
.unix_nano => |nano| nano,
.time => |time| time.instant().timestamp,
};
return .{
.timestamp = ts,
.timezone = tz,
};
}
test "instant" {
const original = instant(.{ .time = .{} }, &utc);
const time = original.time();
const round_trip = time.instant();
try std.testing.expectEqual(original.timestamp, round_trip.timestamp);
}
/// Creates a new Instant by parsing text.
///
/// See also .instant()
pub fn instantFromText(format: Instant.TextFormat, text: []const u8, tz: *const TimeZone) !Instant {
const ts: Nanoseconds = switch (format) {
.iso8601,
.rfc3339,
=> blk: {
const t = try Time.fromISO8601(text);
break :blk t.instant().timestamp;
},
.rfc2822,
.rfc5322,
=> blk: {
const t = try Time.fromRFC5322(text);
break :blk t.instant().timestamp;
},
.rfc1123 => blk: {
const t = try Time.fromRFC1123(text);
break :blk t.instant().timestamp;
},
};
return .{
.timestamp = ts,
.timezone = tz,
};
}
test "instantFromText" {
const original_text = "2001-09-09T03:46:40+0200";
const original_tz: TimeZone = .{ .fixed = .{
.name = "foo",
.offset = 2 * std.time.s_per_hour,
.is_dst = true,
} };
// ^ not a real TZ but close enough (only offset matters here)
const round_trip_instant = try instantFromText(.iso8601, original_text, &original_tz);
const round_trip_text = try std.fmt.allocPrint(
std.testing.allocator,
"{f}",
.{round_trip_instant.time().timeFmt(.strftime, "%Y-%m-%dT%H:%M:%S%z")},
);
defer std.testing.allocator.free(round_trip_text);
try std.testing.expectEqualStrings(original_text, round_trip_text);
}
pub const Month = enum(u4) {
jan = 1,
feb,
mar,
apr,
may,
jun,
jul,
aug,
sep,
oct,
nov,
dec,
/// returns the last day of the month
/// Neri/Schneider algorithm
pub fn lastDay(self: Month, year: i32) u5 {
const m: u5 = @intFromEnum(self);
if (m == 2) return if (isLeapYear(year)) 29 else 28;
return 30 | (m ^ (m >> 3));
}
/// returns the full name of the month, eg "January"
pub fn name(self: Month) []const u8 {
return switch (self) {
.jan => "January",
.feb => "February",
.mar => "March",
.apr => "April",
.may => "May",
.jun => "June",
.jul => "July",
.aug => "August",
.sep => "September",
.oct => "October",
.nov => "November",
.dec => "December",
};
}
/// returns the short name of the month, eg "Jan"
pub fn shortName(self: Month) []const u8 {
return self.name()[0..3];
}
test "lastDayOfMonth" {
try std.testing.expectEqual(29, Month.feb.lastDay(2000));
try std.testing.expectEqual(31, Month.jan.lastDay(2001));
try std.testing.expectEqual(28, Month.feb.lastDay(2001));
try std.testing.expectEqual(31, Month.mar.lastDay(2001));
try std.testing.expectEqual(30, Month.apr.lastDay(2001));
try std.testing.expectEqual(31, Month.may.lastDay(2001));
try std.testing.expectEqual(30, Month.jun.lastDay(2001));
try std.testing.expectEqual(31, Month.jul.lastDay(2001));
try std.testing.expectEqual(31, Month.aug.lastDay(2001));
try std.testing.expectEqual(30, Month.sep.lastDay(2001));
try std.testing.expectEqual(31, Month.oct.lastDay(2001));
try std.testing.expectEqual(30, Month.nov.lastDay(2001));
try std.testing.expectEqual(31, Month.dec.lastDay(2001));
}
/// the number of days in a year before this month
pub fn daysBefore(self: Month, year: i32) u9 {
var m = @intFromEnum(self) - 1;
var result: u9 = 0;
while (m > 0) : (m -= 1) {
const month: Month = @enumFromInt(m);
result += month.lastDay(year);
}
return result;
}
test "daysBefore" {
try std.testing.expectEqual(60, Month.mar.daysBefore(2000));
try std.testing.expectEqual(0, Month.jan.daysBefore(2001));
try std.testing.expectEqual(31, Month.feb.daysBefore(2001));
try std.testing.expectEqual(59, Month.mar.daysBefore(2001));
}
};
pub const Duration = struct {
days: usize = 0,
hours: usize = 0,
minutes: usize = 0,
seconds: usize = 0,
milliseconds: usize = 0,
microseconds: usize = 0,
nanoseconds: usize = 0,
/// duration expressed as the total number of nanoseconds
pub fn inNanoseconds(self: Duration) error{Overflow}!u64 {
// check for multiplication with overflow
const days_in_ns = @mulWithOverflow(self.days, ns_per_day);
const hours_in_ns = @mulWithOverflow(self.hours, ns_per_hour);
const minutes_in_ns = @mulWithOverflow(self.minutes, ns_per_min);
const seconds_in_ns = @mulWithOverflow(self.seconds, ns_per_s);
const milliseconds_in_ns = @mulWithOverflow(self.milliseconds, ns_per_ms);
const microseconds_in_ns = @mulWithOverflow(self.microseconds, ns_per_us);
if (days_in_ns[1] == 1 or
hours_in_ns[1] == 1 or
minutes_in_ns[1] == 1 or
seconds_in_ns[1] == 1 or
milliseconds_in_ns[1] == 1 or
microseconds_in_ns[1] == 1) return error.Overflow;
// check for addition with overflow
var ns = days_in_ns[0];
const components = [_]usize{
hours_in_ns[0],
minutes_in_ns[0],
seconds_in_ns[0],
milliseconds_in_ns[0],
microseconds_in_ns[0],
self.nanoseconds,
};
for (components) |value| {
const sum_with_overflow = @addWithOverflow(ns, value);
if (sum_with_overflow[1] == 1) return error.Overflow;
ns = sum_with_overflow[0];
}
return ns;
}
};
pub const Weekday = enum(u3) {
sun = 0,
mon,
tue,
wed,
thu,
fri,
sat,
/// number of days from self until other. Returns 0 when self == other
pub fn daysUntil(self: Weekday, other: Weekday) u3 {
const d: u8 = @as(u8, @intFromEnum(other)) -% @as(u8, @intFromEnum(self));
return if (d <= 6) @intCast(d) else @intCast(d +% 7);
}
/// returns the full name of the day, eg "Tuesday"
pub fn name(self: Weekday) []const u8 {
return switch (self) {
.sun => "Sunday",
.mon => "Monday",
.tue => "Tuesday",
.wed => "Wednesday",
.thu => "Thursday",
.fri => "Friday",
.sat => "Saturday",
};
}
/// returns the short name of the day, eg "Tue"
pub fn shortName(self: Weekday) []const u8 {
return self.name()[0..3];
}
test "daysUntil" {
const wed: Weekday = .wed;
try std.testing.expectEqual(0, wed.daysUntil(.wed));
try std.testing.expectEqual(6, wed.daysUntil(.tue));
try std.testing.expectEqual(5, wed.daysUntil(.mon));
try std.testing.expectEqual(4, wed.daysUntil(.sun));
}
};
pub const Date = struct {
year: i32,
month: Month,
day: u5, // 1-31
/// Checks for equality of two dates
pub fn eql(date1: Date, date2: Date) bool {
return date1.year == date2.year and
date1.month == date2.month and
date1.day == date2.day;
}
test "Date-Equality" {
const date: Date = .{
.year = 2025,
.month = Month.sep,
.day = 13,
};
try std.testing.expect(date.eql(Date{ .year = 2025, .month = Month.sep, .day = 13 }));
try std.testing.expect(!date.eql(Date{ .year = 2025, .month = Month.sep, .day = 12 }));
try std.testing.expect(!date.eql(Date{ .year = 2025, .month = Month.aug, .day = 13 }));
try std.testing.expect(!date.eql(Date{ .year = 2024, .month = Month.sep, .day = 13 }));
}
/// Compares two dates with another. If `date2` happens after `date1`, then the `TimeComparison.after` is returned.
/// If `date2` happens before `date1`, then `TimeComparison.before` is returned. If both represent the same date, `TimeComparison.equal` is returned;
pub fn compare(date1: Date, date2: Date) TimeComparison {
if (date1.year > date2.year) {
return .before;
} else if (date1.year < date2.year) {
return .after;
}
if (@intFromEnum(date1.month) > @intFromEnum(date2.month)) {
return .before;
} else if (@intFromEnum(date1.month) < @intFromEnum(date2.month)) {
return .after;
}
if (date1.day > date2.day) {
return .before;
} else if (date1.day < date2.day) {
return .after;
}
return .equal;
}
test "Date-Comparison" {
const date: Date = .{
.year = 2025,
.month = Month.sep,
.day = 13,
};
try std.testing.expectEqual(TimeComparison.before, date.compare(Date{ .year = 2025, .month = Month.sep, .day = 12 }));
try std.testing.expectEqual(TimeComparison.before, date.compare(Date{ .year = 2025, .month = Month.aug, .day = 13 }));
try std.testing.expectEqual(TimeComparison.before, date.compare(Date{ .year = 2024, .month = Month.sep, .day = 13 }));
try std.testing.expectEqual(TimeComparison.before, date.compare(Date{ .year = 2024, .month = Month.dec, .day = 31 }));
try std.testing.expectEqual(TimeComparison.before, date.compare(Date{ .year = 2025, .month = Month.aug, .day = 31 }));
try std.testing.expectEqual(TimeComparison.after, date.compare(Date{ .year = 2025, .month = Month.sep, .day = 14 }));
try std.testing.expectEqual(TimeComparison.after, date.compare(Date{ .year = 2025, .month = Month.oct, .day = 13 }));
try std.testing.expectEqual(TimeComparison.after, date.compare(Date{ .year = 2026, .month = Month.sep, .day = 13 }));
try std.testing.expectEqual(TimeComparison.after, date.compare(Date{ .year = 2026, .month = Month.jan, .day = 1 }));
try std.testing.expectEqual(TimeComparison.after, date.compare(Date{ .year = 2025, .month = Month.oct, .day = 1 }));
try std.testing.expectEqual(TimeComparison.equal, date.compare(Date{ .year = 2025, .month = Month.sep, .day = 13 }));
}
};
pub const TimeComparison = enum(u2) {
after,
before,
equal,
};
pub const Time = struct {
year: i32 = 1970,
month: Month = .jan,
day: u5 = 1, // 1-31
hour: u5 = 0, // 0-23
minute: u6 = 0, // 0-59
second: u6 = 0, // 0-60
millisecond: u10 = 0, // 0-999
microsecond: u10 = 0, // 0-999
nanosecond: u10 = 0, // 0-999
offset: i32 = 0, // offset from UTC in seconds
designation: []const u8 = "",
/// Creates a UTC Instant for this time
pub fn instant(self: Time) Instant {
const days = daysFromCivil(.{
.year = self.year,
.month = self.month,
.day = self.day,
});
return .{
.timestamp = @as(i128, days) * ns_per_day +
@as(i128, self.hour) * ns_per_hour +
@as(i128, self.minute) * ns_per_min +
@as(i128, self.second) * ns_per_s +
@as(i128, self.millisecond) * ns_per_ms +
@as(i128, self.microsecond) * ns_per_us +
@as(i128, self.nanosecond) -
@as(i128, self.offset) * ns_per_s,
.timezone = &utc,
};
}
pub fn fromISO8601(iso: []const u8) !Time {
const parseInt = std.fmt.parseInt;
var time: Time = .{};
const State = enum {
year,
month_or_ordinal,
day,
hour,
minute,
minute_fraction_or_second,
second_fraction_or_offset,
};
var state: State = .year;
var i: usize = 0;
while (i < iso.len) {
switch (state) {
.year => {
if (iso.len <= 4) {
// year only data
const int = try parseInt(i32, iso, 10);
time.year = int * std.math.pow(i32, 10, @as(i32, @intCast(4 - iso.len)));
break;
} else {
time.year = try parseInt(i32, iso[0..4], 10);
state = .month_or_ordinal;
i += 4;
if (iso[i] == '-') i += 1;
}
},
.month_or_ordinal => {
const token_end = std.mem.indexOfAnyPos(u8, iso, i, "- T") orelse iso.len;
switch (token_end - i) {
2 => {
const m: u4 = try parseInt(u4, iso[i..token_end], 10);
time.month = @enumFromInt(m);
state = .day;
},
3 => { // ordinal
const doy = try parseInt(u9, iso[i..token_end], 10);
var m: u4 = 1;
var days: u9 = 0;
while (m <= 12) : (m += 1) {
const month: Month = @enumFromInt(m);
if (days + month.lastDay(time.year) < doy) {
days += month.lastDay(time.year);
continue;
}
time.month = month;
time.day = @intCast(doy - days);
break;
}
state = .hour;
},
4 => { // MMDD
const m: u4 = try parseInt(u4, iso[i .. i + 2], 10);
time.month = @enumFromInt(m);
time.day = try parseInt(u5, iso[i + 2 .. token_end], 10);
state = .hour;
},
else => return error.InvalidISO8601,
}
i = token_end + 1;
},
.day => {
time.day = try parseInt(u5, iso[i .. i + 2], 10);
// add 3 instead of 2 because we either have a trailing ' ',
// 'T', or EOF
i += 3;
state = .hour;
},
.hour => {
time.hour = try parseInt(u5, iso[i .. i + 2], 10);
i += 2;
state = .minute;
},
.minute => {
if (iso[i] == ':') i += 1;
time.minute = try parseInt(u6, iso[i .. i + 2], 10);
i += 2;
state = .minute_fraction_or_second;
},
.minute_fraction_or_second => {
const b = iso[i];
if (b == '.') return error.UnhandledFormat; // TODO:
if (b == ':') i += 1;
if (std.ascii.isDigit(iso[i])) {
time.second = try parseInt(u6, iso[i .. i + 2], 10);
i += 2;
}
state = .second_fraction_or_offset;
},
.second_fraction_or_offset => {
switch (iso[i]) {
'Z' => break,
'+', '-' => {
const sign: i32 = if (iso[i] == '-') -1 else 1;
i += 1;
const hour = try parseInt(u5, iso[i .. i + 2], 10);
i += 2;
time.offset = sign * hour * s_per_hour;
if (i >= iso.len - 1) break;
if (iso[i] == ':') i += 1;
const minute = try parseInt(u6, iso[i .. i + 2], 10);
time.offset += sign * minute * s_per_min;
i += 2;
break;
},
'.' => {
i += 1;
const frac_end = std.mem.indexOfAnyPos(u8, iso, i, "Z+-") orelse iso.len;
const rhs = try parseInt(u64, iso[i..frac_end], 10);
const sigs = frac_end - i;
// convert sigs to nanoseconds
const pow = std.math.pow(u64, 10, @as(u64, @intCast(9 - sigs)));
var nanos = rhs * pow;
time.millisecond = @intCast(@divFloor(nanos, ns_per_ms));
nanos -= @as(u64, time.millisecond) * ns_per_ms;
time.microsecond = @intCast(@divFloor(nanos, ns_per_us));
nanos -= @as(u64, time.microsecond) * ns_per_us;
time.nanosecond = @intCast(nanos);
i = frac_end;
},
else => return error.InvalidISO8601,
}
},
}
}
return time;
}
test "fromISO8601" {
{
const year = try Time.fromISO8601("2000");
try std.testing.expectEqual(2000, year.year);
}
{
const ym = try Time.fromISO8601("200002");
try std.testing.expectEqual(2000, ym.year);
try std.testing.expectEqual(.feb, ym.month);
const ym_ext = try Time.fromISO8601("2000-02");
try std.testing.expectEqual(2000, ym_ext.year);
try std.testing.expectEqual(.feb, ym_ext.month);
}
{
const ymd = try Time.fromISO8601("20000212");
try std.testing.expectEqual(2000, ymd.year);
try std.testing.expectEqual(.feb, ymd.month);
try std.testing.expectEqual(12, ymd.day);
const ymd_ext = try Time.fromISO8601("2000-02-12");
try std.testing.expectEqual(2000, ymd_ext.year);
try std.testing.expectEqual(.feb, ymd_ext.month);
try std.testing.expectEqual(12, ymd_ext.day);
}
{
const ordinal = try Time.fromISO8601("2000031");
try std.testing.expectEqual(2000, ordinal.year);
try std.testing.expectEqual(.jan, ordinal.month);
try std.testing.expectEqual(31, ordinal.day);
const ordinal_ext = try Time.fromISO8601("2000-043");
try std.testing.expectEqual(2000, ordinal_ext.year);
try std.testing.expectEqual(.feb, ordinal_ext.month);
try std.testing.expectEqual(12, ordinal_ext.day);
}
{
const ymdh = try Time.fromISO8601("20000212 11");
try std.testing.expectEqual(2000, ymdh.year);
try std.testing.expectEqual(.feb, ymdh.month);
try std.testing.expectEqual(12, ymdh.day);
try std.testing.expectEqual(11, ymdh.hour);
const ymdh_ext = try Time.fromISO8601("2000-02-12T11");
try std.testing.expectEqual(2000, ymdh_ext.year);
try std.testing.expectEqual(.feb, ymdh_ext.month);
try std.testing.expectEqual(12, ymdh_ext.day);
try std.testing.expectEqual(11, ymdh_ext.hour);
}
{
const ymdhm = try Time.fromISO8601("2025-05-19T11:23");
try std.testing.expectEqual(2025, ymdhm.year);
try std.testing.expectEqual(.may, ymdhm.month);
try std.testing.expectEqual(19, ymdhm.day);
try std.testing.expectEqual(11, ymdhm.hour);
try std.testing.expectEqual(23, ymdhm.minute);
}
{
const full = try Time.fromISO8601("20000212 111213Z");
try std.testing.expectEqual(2000, full.year);
try std.testing.expectEqual(.feb, full.month);
try std.testing.expectEqual(12, full.day);
try std.testing.expectEqual(11, full.hour);
try std.testing.expectEqual(12, full.minute);
try std.testing.expectEqual(13, full.second);
const full_ext = try Time.fromISO8601("2000-02-12T11:12:13Z");
try std.testing.expectEqual(2000, full_ext.year);
try std.testing.expectEqual(.feb, full_ext.month);
try std.testing.expectEqual(12, full_ext.day);
try std.testing.expectEqual(11, full_ext.hour);
try std.testing.expectEqual(12, full_ext.minute);
try std.testing.expectEqual(13, full_ext.second);
}
{
const s_frac = try Time.fromISO8601("2000-02-12T11:12:13.123Z");
try std.testing.expectEqual(123, s_frac.millisecond);
try std.testing.expectEqual(0, s_frac.microsecond);
try std.testing.expectEqual(0, s_frac.nanosecond);
}
{
const offset = try Time.fromISO8601("2000-02-12T11:12:13.123-12:00");
try std.testing.expectEqual(-12 * s_per_hour, offset.offset);
}
{
const offset = try Time.fromISO8601("2000-02-12T11:12:13+12:30");
try std.testing.expectEqual(12 * s_per_hour + 30 * s_per_min, offset.offset);
}
{
const offset = try Time.fromISO8601("2025-05-19T11:23+0200");
try std.testing.expectEqual(2 * s_per_hour, offset.offset);
}
{
const offset = try Time.fromISO8601("20000212T111213+1230");
try std.testing.expectEqual(12 * s_per_hour + 30 * s_per_min, offset.offset);
}
{
const basic = try Time.fromISO8601("20240224T154944");
try std.testing.expectEqual(2024, basic.year);
try std.testing.expectEqual(Month.feb, basic.month);
try std.testing.expectEqual(24, basic.day);
try std.testing.expectEqual(15, basic.hour);
try std.testing.expectEqual(49, basic.minute);
try std.testing.expectEqual(44, basic.second);
try std.testing.expectEqual(0, basic.offset);
}
{
const basic = try Time.fromISO8601("20240224T154944Z");
try std.testing.expectEqual(2024, basic.year);
try std.testing.expectEqual(Month.feb, basic.month);
try std.testing.expectEqual(24, basic.day);
try std.testing.expectEqual(15, basic.hour);
try std.testing.expectEqual(49, basic.minute);
try std.testing.expectEqual(44, basic.second);
try std.testing.expectEqual(0, basic.offset);
}
}
/// Parse an RFC 5322 date-time string (e.g., "Thu, 13 Feb 1969 23:32:54 -0330").
///
/// Supports obsolete timezone names from RFC 5322 section 4.3:
/// - UT, GMT: UTC (+0000)
/// - US timezones: EST/EDT, CST/CDT, MST/MDT, PST/PDT
/// - Single-letter military timezones (A-I, K-M, N-Y, Z)
///
/// Note: Military timezones use conventional offsets (A=+1, N=-1, etc.) rather than
/// RFC 5322's recommendation to treat them as "-0000" (unknown offset).
pub fn fromRFC5322(eml: []const u8) !Time {
const parseInt = std.fmt.parseInt;
var time: Time = .{};
var i: usize = 0;
// day
{
// consume until a digit
while (i < eml.len and !std.ascii.isDigit(eml[i])) : (i += 1) {}
const end = std.mem.indexOfScalarPos(u8, eml, i, ' ') orelse return error.InvalidFormat;
time.day = try parseInt(u5, eml[i..end], 10);
i = end + 1;
}
// month
{
// consume until an alpha
while (i < eml.len and !std.ascii.isAlphabetic(eml[i])) : (i += 1) {}
assert(eml.len >= i + 3);
var buf: [3]u8 = undefined;
buf[0] = std.ascii.toLower(eml[i]);
buf[1] = std.ascii.toLower(eml[i + 1]);
buf[2] = std.ascii.toLower(eml[i + 2]);
time.month = std.meta.stringToEnum(Month, &buf) orelse return error.InvalidFormat;
i += 3;
}
// year
{
// consume until a digit
while (i < eml.len and !std.ascii.isDigit(eml[i])) : (i += 1) {}
assert(eml.len >= i + 4);
time.year = try parseInt(i32, eml[i .. i + 4], 10);
i += 4;
}
// hour
{
// consume until a digit
while (i < eml.len and !std.ascii.isDigit(eml[i])) : (i += 1) {}
const end = std.mem.indexOfScalarPos(u8, eml, i, ':') orelse return error.InvalidFormat;
time.hour = try parseInt(u5, eml[i..end], 10);
i = end + 1;
}
// minute
{
// consume until a digit
while (i < eml.len and !std.ascii.isDigit(eml[i])) : (i += 1) {}
assert(i + 2 < eml.len);
time.minute = try parseInt(u6, eml[i .. i + 2], 10);
i += 2;
}
// second and zone
{
assert(i < eml.len);
// seconds are optional
if (eml[i] == ':') {
i += 1;
assert(i + 2 < eml.len);
time.second = try parseInt(u6, eml[i .. i + 2], 10);
i += 2;
}
// consume whitespace
while (i < eml.len and std.ascii.isWhitespace(eml[i])) : (i += 1) {}
switch (eml.len - i) {
else => {
const hours = try parseInt(i32, eml[i .. i + 3], 10);
const minutes = try parseInt(i32, eml[i + 3 .. i + 5], 10);
const offset_minutes: i32 = if (hours > 0)
hours * 60 + minutes
else
hours * 60 - minutes;
time.offset = offset_minutes * 60;
},
4 => return error.InvalidFormat, // No formats should have a 4 character zone
3 => {
const ObsoleteZoneParseState = enum {
start,
g,
gm,
e,
ed,
es,
c,
cs,
cd,
m,
ms,
md,
p,
ps,
pd,
invalid,
};
const first = std.ascii.toUpper(eml[i]);
const second = std.ascii.toUpper(eml[i + 1]);
const third = std.ascii.toUpper(eml[i + 2]);
// The last of all should be 'T'
if (third != 'T') return error.InvalidFormat;
parse: switch (ObsoleteZoneParseState.start) {
.start => switch (first) {
'G' => continue :parse .g,
'E' => continue :parse .e,
'C' => continue :parse .c,
'M' => continue :parse .m,
'P' => continue :parse .p,
else => return error.InvalidFormat,
},
.g => if (second == 'M') continue :parse .gm else continue :parse .invalid,
.e => if (second == 'D') continue :parse .ed else if (second == 'S') continue :parse .es else continue :parse .invalid,
.c => if (second == 'D') continue :parse .cd else if (second == 'S') continue :parse .cs else continue :parse .invalid,
.m => if (second == 'D') continue :parse .md else if (second == 'S') continue :parse .ms else continue :parse .invalid,
.p => if (second == 'D') continue :parse .pd else if (second == 'S') continue :parse .ps else continue :parse .invalid,
.gm => {
time.offset = 0;
},
.ed => {
time.offset = -4 * 3600;
},
.es, .cd => {
time.offset = -5 * 3600;
},
.cs, .md => {
time.offset = -6 * 3600;
},
.ms, .pd => {
time.offset = -7 * 3600;
},
.ps => {
time.offset = -8 * 3600;
},
.invalid => return error.InvalidFormat,
}
},
2 => {
if (std.ascii.toUpper(eml[i]) == 'U' and std.ascii.toUpper(eml[i + 1]) == 'T') {
time.offset = 0;
} else {
return error.InvalidFormat;
}
},
1 => {
switch (eml[i]) {
'Z', 'z' => time.offset = 0,
'A'...'I' => time.offset = (@as(i32, eml[i] - 'A') + 1) * 3600,
'a'...'i' => time.offset = (@as(i32, eml[i] - 'a') + 1) * 3600,
'K'...'M' => time.offset = (@as(i32, eml[i] - 'A')) * 3600, // J is skipped, already offset by 1
'k'...'m' => time.offset = (@as(i32, eml[i] - 'a')) * 3600, // j is skipped, already offset by 1
'N'...'Y' => time.offset = -((@as(i32, eml[i] - 'N') + 1) * 3600),
'n'...'y' => time.offset = -((@as(i32, eml[i] - 'n') + 1) * 3600),
else => return error.InvalidFormat,
}
},
}
}
return time;
}
test "fromRFC5322" {
{
const time = try Time.fromRFC5322("Thu, 13 Feb 1969 23:32:54 -0330");
try std.testing.expectEqual(1969, time.year);
try std.testing.expectEqual(.feb, time.month);
try std.testing.expectEqual(13, time.day);
try std.testing.expectEqual(23, time.hour);
try std.testing.expectEqual(32, time.minute);
try std.testing.expectEqual(54, time.second);
try std.testing.expectEqual(-12_600, time.offset);
}
{
// FWS everywhere
const time = try Time.fromRFC5322(" Thu, 13 \tFeb 1969\t\r\n 23:32:54 -0330");
try std.testing.expectEqual(1969, time.year);
try std.testing.expectEqual(.feb, time.month);
try std.testing.expectEqual(13, time.day);
try std.testing.expectEqual(23, time.hour);
try std.testing.expectEqual(32, time.minute);
try std.testing.expectEqual(54, time.second);
try std.testing.expectEqual(-12_600, time.offset);
}
{
const Test = struct {
name: []const u8,
value: i32,
};
const tests = [_]Test{
.{ .name = "UT", .value = 0 },
.{ .name = "ut", .value = 0 },
.{ .name = "GMT", .value = 0 },
.{ .name = "gmt", .value = 0 },
.{ .name = "EDT", .value = -4 * 3600 },
.{ .name = "edt", .value = -4 * 3600 },
.{ .name = "EST", .value = -5 * 3600 },
.{ .name = "est", .value = -5 * 3600 },
.{ .name = "CDT", .value = -5 * 3600 },
.{ .name = "cdt", .value = -5 * 3600 },
.{ .name = "CST", .value = -6 * 3600 },
.{ .name = "cst", .value = -6 * 3600 },
.{ .name = "MDT", .value = -6 * 3600 },
.{ .name = "mdt", .value = -6 * 3600 },
.{ .name = "MST", .value = -7 * 3600 },
.{ .name = "mst", .value = -7 * 3600 },
.{ .name = "PDT", .value = -7 * 3600 },
.{ .name = "pdt", .value = -7 * 3600 },
.{ .name = "PST", .value = -8 * 3600 },
.{ .name = "pst", .value = -8 * 3600 },
.{ .name = "I", .value = 9 * 3600 },
.{ .name = "K", .value = 10 * 3600 },
.{ .name = "M", .value = 12 * 3600 },
.{ .name = "n", .value = -1 * 3600 },
.{ .name = "y", .value = -12 * 3600 },
.{ .name = "z", .value = 0 },
.{ .name = "Z", .value = 0 },
};
for (tests) |t| {
var buf: [64]u8 = undefined;
const rfc5322_str = std.fmt.bufPrint(&buf,
gitextract_55wlgbp2/
├── .github/
│ └── workflows/
│ ├── docs.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── bench/
│ ├── bench_benjoffe.zig
│ ├── bench_days_benjoffe.zig
│ ├── bench_days_current.zig
│ ├── bench_hinnant.zig
│ ├── bench_leap_benjoffe.zig
│ ├── bench_leap_current.zig
│ ├── test_days.zig
│ └── test_leap.zig
├── build.zig
├── build.zig.zon
├── gen/
│ ├── main.zig
│ └── windowsZones.xml
└── src/
├── location.zig
├── timezone.zig
└── zeit.zig
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (263K chars).
[
{
"path": ".github/workflows/docs.yml",
"chars": 784,
"preview": "name: docs\n\non:\n push:\n branches: [\"main\"]\n workflow_dispatch:\n\npermissions:\n contents: read\n pages: write\n id-t"
},
{
"path": ".github/workflows/test.yml",
"chars": 562,
"preview": "name: test\n\non:\n pull_request:\n branches: [\"main\", \"0.16\"]\n workflow_dispatch:\n\njobs:\n test:\n strategy:\n m"
},
{
"path": ".gitignore",
"chars": 21,
"preview": ".zig-cache/\nzig-out/\n"
},
{
"path": "LICENSE",
"chars": 1059,
"preview": "Copyright (c) 2024 Tim Culverhouse\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this"
},
{
"path": "README.md",
"chars": 2497,
"preview": "# zeit\n\nA time library written in zig.\n\n## Install \n```\nzig fetch --save git+https://github.com/rockorager/zeit?ref=main"
},
{
"path": "bench/bench_benjoffe.zig",
"chars": 2553,
"preview": "const std = @import(\"std\");\nconst builtin = @import(\"builtin\");\n\nconst Date = struct {\n year: i32,\n month: u4,\n "
},
{
"path": "bench/bench_days_benjoffe.zig",
"chars": 1063,
"preview": "const std = @import(\"std\");\n\nconst Date = struct {\n year: i32,\n month: u4,\n day: u5,\n};\n\n/// Ben Joffe's fast o"
},
{
"path": "bench/bench_days_current.zig",
"chars": 1049,
"preview": "const std = @import(\"std\");\n\nconst Date = struct {\n year: i32,\n month: u4,\n day: u5,\n};\n\nconst days_per_era = 3"
},
{
"path": "bench/bench_hinnant.zig",
"chars": 1398,
"preview": "const std = @import(\"std\");\n\nconst Date = struct {\n year: i32,\n month: u4,\n day: u5,\n};\n\nconst days_per_era = 3"
},
{
"path": "bench/bench_leap_benjoffe.zig",
"chars": 777,
"preview": "const std = @import(\"std\");\n\n/// Ben Joffe's fast full-range leap year algorithm\n/// https://www.benjoffe.com/fast-leap-"
},
{
"path": "bench/bench_leap_current.zig",
"chars": 467,
"preview": "const std = @import(\"std\");\n\n/// Current Neri/Schneider algorithm\nfn isLeapYear(year: i32) bool {\n const d: i32 = if "
},
{
"path": "bench/test_days.zig",
"chars": 2299,
"preview": "const std = @import(\"std\");\n\nconst Date = struct {\n year: i32,\n month: u4,\n day: u5,\n};\n\nconst days_per_era = 3"
},
{
"path": "bench/test_leap.zig",
"chars": 1227,
"preview": "const std = @import(\"std\");\n\nfn isLeapYearCurrent(year: i32) bool {\n const d: i32 = if (@mod(year, 100) != 0) 4 else "
},
{
"path": "build.zig",
"chars": 1682,
"preview": "const std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n const target = b.standardTargetOptions(.{});\n con"
},
{
"path": "build.zig.zon",
"chars": 271,
"preview": ".{\n .name = .zeit,\n .fingerprint = 0x888df3e4939b8ee4,\n .version = \"0.6.0\",\n .dependencies = .{},\n .paths"
},
{
"path": "gen/main.zig",
"chars": 4268,
"preview": "//! Generates zig code for well-known timezones. Makes an enum of the \"posix\" style name\n//! (\"America/Chicago\") and a f"
},
{
"path": "gen/windowsZones.xml",
"chars": 49359,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE supplementalData SYSTEM \"../../common/dtd/ldmlSupplemental.dtd\">\n<!--\n"
},
{
"path": "src/location.zig",
"chars": 38271,
"preview": "//!This file is generated. Do not edit directly! Run `zig build generate` to update after obtaining\n//!the latest datase"
},
{
"path": "src/timezone.zig",
"chars": 45426,
"preview": "const std = @import(\"std\");\nconst builtin = @import(\"builtin\");\nconst zeit = @import(\"zeit.zig\");\n\nconst assert = std.de"
},
{
"path": "src/zeit.zig",
"chars": 92852,
"preview": "const std = @import(\"std\");\nconst builtin = @import(\"builtin\");\nconst location = @import(\"location.zig\");\npub const time"
}
]
About this extraction
This page contains the full source code of the rockorager/zeit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (242.1 KB), approximately 66.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.