1
0
Fork
You've already forked tempora
0
Simple Zig Dates/Times/Timezones
  • Zig 100%
2026年07月09日 17:09:35 -05:00
.github/workflows Don't run current timezone tests in CI 2026年06月06日 18:04:32 -05:00
bench v0.6.0: 2026年06月01日 22:28:05 -05:00
src tzdata 2026c 2026年07月09日 17:09:35 -05:00
test Don't run current timezone tests in CI 2026年06月06日 18:04:32 -05:00
tools bugfixes, expand tests 2026年06月04日 17:22:41 -05:00
.gitattributes tempora 2024年03月08日 20:03:58 -06:00
.gitignore add gh workflows 2026年02月28日 15:34:01 -06:00
build.zig Don't run current timezone tests in CI 2026年06月06日 18:04:32 -05:00
build.zig.zon tzdata 2026c 2026年07月09日 17:09:35 -05:00
license v0.4: Add ISO week date support 2026年02月17日 22:49:22 -06:00
readme.md documentation 2026年06月06日 16:08:10 -05:00

Tempora

Simple Zig Dates, Times, and Timezones

Features

  • Efficient storage (32b Time, 32b Date, 64b Date_Time)
  • Conversion to/from unix timestamps
  • Composition and decomposition (year, ordinal day/week, month, day, weekday, hour, minute, second, ms)
  • Find next/previous weekday/day/ordinal day
  • Add/subtract days/hours/minutes/seconds/ms
    • With or without timezone offset correction
    • With or without leap second correction
  • Compute duration between two dates/times
    • With or without timezone offset correction
    • With or without leap second correction
  • String formatting and parsing with custom formats (similar to moment.js and Java's SimpleDateFormat style)
  • Query current timezone on both posix and Windows systems
  • Embedded IANA timezone database (adds about 100k to binary size when the full database is referenced)
    • If only specific timezones or regions are needed, only a subset of the database needs to be embedded
    • Timezones can also be loaded from a filesystem zoneinfo database or the timezone database in the Windows registry
    • Regenerating the embedded database does not rely on zic.c or any system dependencies (just run zig build -Dcodegen)
  • No dependencies
    • Except when using zig build -Dcodegen or zig build -Dbenchmarks

Limitations

  • Times are only accurate to millisecond resolution
  • It's not possible to store most "out of bounds" dates/times (e.g. Jan 32)
  • Localized month and weekday names are not supported; only English
  • Non-Gregorian calendars are not supported

Why another zig date/time library?

There are a bunch of other zig date/time libraries, so why did I decide to build a new one? I was motivated to create tempora because the main data structures for other zig date/time libraries generally contain many separate decomposed fields. In addition to having many opportunities for non-canonical representations, the large memory footprint makes it very hard to stomache using these types inside structs or arrays, particularly when following data-oriented-design principles. Instead, I wanted something where the main data structures only provided a slight decomposition over timestamps - just separating the date and time parts. Any further decomposition can be done on demand, or using auxiliary temporary data structures. And ideally the solution should not use significantly more memory than a timestamp.

It turns out that "rata die" encoded dates stored in a 32 bit integer have enough range to cover over 10 million years. Ideally then, I wanted to fit a packed time-of-day representation into a 32 bit integer as well. Unfortunately, this is a little trickier, and I had to compromise on the resolution. A nanosecond-resolution time-of-day would require at least 47 bits to span 24 hours. Even a microsecond-resolution time-of-day would require 37 bits. While I would have preferred to support resolutions smaller than a millisecond, I think it's probably fine for almost all use cases. If you need more resolution, it's probably better to just process and store nanosecond timestamps and convert to rounded human-centric types only for display.

My second requirement was good support for timezones, including the ability to embed an IANA timezone database directly into the executable. Other than tempora, only zdt comes close to this, but I wanted even more flexibility in deciding how and when to load timezones, and I wanted a pure-zig solution to automatically updating the embedded timezone database.

API/Examples

Date

Dates are represented as a signed 32b number of days since 1 January 2000, embedded in an enum for type safety. This type of representation is sometimes referred to as "rata die" (although that name usually connotes a different epoch date) and it makes it impossible to represent invalid dates like January 32 or November 31.

Using this packed representation makes composition and decomposition slower, but greatly simplifies most other operations on dates, like modification and comparison.

Construction

consty:i32=1984constm:i32=3constd:i32=1constdate:Date=.from_ymd_numbers(y,m,d);
constymd:Date.YMD=.from_numbers(1984,3,1);constdate:Date=.from_ymd(ymd);
consty:Year=.epoch;constod:Ordinal_Day=.first;constdate:Date=.from_yod(y,od);
constyi:Year.Info=.from_year(.epoch);constod:Ordinal_Day=.first;constdate:Date=.from_yiod(yi,od);
consty:Year=.epoch;constiw:ISO_Week=.first;constwd:Week_Day=.sunday;constdate:Date=.from_ywd(y,iw,wd);
constyi:Year.Info=.from_year(.epoch);constiw:ISO_Week=.first;constwd:Week_Day=.sunday;constdate:Date=.from_yiwd(yi,iw,wd);
consty:Year=.epoch;conststarting_date:Date=.from_year(y);

Convenience Decls

vard:Date=.epoch;// 2000年01月01日d=.unix_epoch;// 1970年01月01日d=.ntp_epoch;// 1900年01月01日d=.ntfs_epoch;// 1601年01月01日

Decomposition & Conversion

constdate:Date=.epoch;consty:Year=date.year();constyi:Year_Info=date.year_info();constm:Month=date.month();constd:Day=date.day();constod:Ordinal_Day=date.ordinal_day();constow:Ordinal_Week=date.ordinal_week();constwd:Week_Day=date.week_day();constiw:ISO_Week=date.iso_week();constiwd:ISO_Week_Date=date.iso_week_date();constdi:Date.Info=date.info();constymd:Date.YMD=date.ymd();consttime:Time=.midnight;constdt:Date_Time=date.with_time(time);

Comparison

constdate1:Date=.epoch;constdate2:Date=Date.next(.epoch);std.debug.assert(date1.is_before(date2));std.debug.assert(!date1.is_before(date1));std.debug.assert(date2.is_after(date1));std.debug.assert(!date2.is_after(date2));

Modification

vardate:Date=.epoch;constdays:i32=10;date=date.plus_days(days);date=date.next();date=date.prev();constwd:Week_Day=.sunday;date=date.next_week_day(wd);date=date.prev_week_day(wd);constd:Day=.@"15";date=date.next_day_of_month(d);date=date.prev_day_of_month(d);constm:Month=.january;date=date.next_month_and_day(m,d);date=date.prev_month_and_day(m,d);

Formatting

constdate:Date=Date.next(.epoch);writer.print("{f}",.{date.fmt(Date.iso8601)});// 2000年01月02日writer.print("{f}",.{date.fmt(Date.rfc2822)});// 2000年1月02日writer.print("{f}",.{date.fmt(Date.us)});// January 2, 2000writer.print("{f}",.{date.fmt(Date.uk)});// 2 January 2000writer.print("{f}",.{date.fmt(Date.us_numeric)});// 1/2/2000writer.print("{f}",.{date.fmt(Date.uk_numeric)});// 2/1/2000writer.print("{f}",.{date.fmt("MMMM YYYY")});// January 2000writer.print("{f}",.{date.fmt("YY")});// 00

Parsing

vardate:Date=undefined;date=try.from_string(Date.iso8601,"2025年10月01日");date=try.from_string("YYYY","2025");// 2025年01月01日

Date.YMD

This struct represents a decomposed date, consisting of a year, month, and day of the month. In some cases it may be more convenient or efficient to use this over Date, but it requires twice as much memory, and some operations are not defined for this struct.

Construction

consty:i32=1984constm:i32=3constd:i32=1constymd:Date.YMD=.from_numbers(y,m,d);
consty:Year=.epoch;constm:Month=.january;constd:Day=.first;constymd:Date.YMD=.init(y,m,d);
constdate:Date=.epoch;constymd:Date.YMD=.from_date(date);

Decomposition/Conversion

constymd:Date.YMD=.from_date(.epoch);consty:Year=ymd.year;constm:Month=ymd.month;constd:Day=ymd.day;constyi:Year.Info=ymd.year_info();constdate:Date=ymd.date();constdi:Date.Info=ymd.info();constiwd:ISO_Week_Date=ymd.iso_week_date();

Comparison

constymd1:Date.YMD=.from_date(.epoch);constymd2:Date.YMD=.from_date(.next(.epoch));std.debug.assert(ymd1.is_before(ymd2));std.debug.assert(!ymd1.is_before(ymd1));std.debug.assert(ymd2.is_after(ymd1));std.debug.assert(!ymd2.is_after(ymd2));

Modification

varymd:Date.YMD=.from_date(.epoch);ymd=ymd.next();ymd=ymd.prev();constd:Day=.@"15";ymd=ymd.next_day_of_month(d);ymd=ymd.prev_day_of_month(d);constm:Month=.january;ymd=ymd.next_month_and_day(m,d);ymd=ymd.prev_month_and_day(m,d);

Date.Info

This struct is similar to Date.YMD, but also includes some more decomposed information: * The raw date as an integer, i.e. @intFromEnum(date) * The day of the week * The ordinal day (day of year) * Whether or not the current year is a leap year * A Date representing the start of the current week * A Date representing the start of the current month * A Date representing the start of the current year

Construction

constdate:Date=.epoch;constdi:Date.Info=.from_date(date);
constymd:Date.YMD=.from_date(.epoch);constdi:Date.Info=.from_ymd(ymd);
constyi:Year.Info=.from_year(.epoch);constm:Month=.january;constd:Day=.first;constdi:Date.Info=.from_yimd(yi,m,d);

Decomposition/Conversion

constdi:Date.Info=.from_date(.epoch);constraw:i32=di.raw;conststart_of_year:Date=di.start_of_year;conststart_of_month:Date=di.start_of_month;conststart_of_week:Date=di.start_of_week;constis_leap_year:bool=di.is_leap_year;constyear:Year=di.year;constmonth:Month=di.month;constday:Day=di.day;constweek_day:Week_Day=di.week_day;constordinal_day:Ordinal_Day=di.ordinal_day;constyi:Year.Info=di.year_info();constymd:Date.YMD=di.ymd();constdate:Date=di.date();constiwd:ISO_Week_Date=di.iso_week_date();

Time

The Time enum represents a millisecond-resolution offset from midnight (the start of an arbitrary day). It is backed by i32 which provides a range of around +/- 24 days, but canonical Time values (especially when combined with a Date) should be between 0 and 85,399,999.

A Time value may represent a time under the UTC or TAI standards, a fixed offset from UTC, or a wall-clock time in a specific local timezone. If this information cannot be inferred from context, you may want to use Time.With_Offset instead.

Construction

constms:i32=1234;constt:Time=.from_ms(ms);// 1.234 seconds after midnight
consts:i32=1234;constt:Time=.from_seconds(s);// 20 minutes and 34 seconds after midnight
constm:i32=-2;constt:Time=.from_minutes(m);// 2 minutes before midnight (non-canonical time; refers to the previous day)
consth:i32=12;constt:Time=.from_hours(h);// noon
consth:u31=20;constm:u8=30;consts:u8=0;constms:u10=0;constt:Time=.from_hmsm(h,m,s,ms);// 8:30 pm

Hourly Convenience Decls

conststart_of_day:Time=.midnight;constwakeup:Time=.@"7am";constlunch:Time=.noon;constbedtime:Time=.@"10pm";constend_of_day:Time=.midnight_eod;

Decomposition & Conversion

constt:Time=.@"1pm";constwhole_hours_since_midnight:i32=t.hours();constwhole_minutes_since_hour:i32=t.minutes();constwhole_seconds_since_minute:i32=t.seconds();constmilliseconds_since_second:i32=t.ms();constwhole_minutes_since_midnight:i32=t.minutes_since_midnight();constwhole_seconds_since_midnight:i32=t.seconds_since_midnight();constmilliseconds_since_midnight:i32=t.ms_since_midnight();constdt:Date_Time=time.with_date(date);constto1:Time.With_Offset=time.with_offset(utc_offset_ms);constto2:Time.With_Offset=time.with_timezone(tz,utc_offset_ms);

Comparison

constt1:Time=.noon;constt2:Time=.@"1pm";// assuming times from the same date:std.debug.assert(t1.is_before(t2));std.debug.assert(!t1.is_before(t1));std.debug.assert(t2.is_after(t1));std.debug.assert(!t2.is_after(t2));

Modification

vart:Time=.noon;constduration:std.Io.Duration=.fromSeconds(1);t=t.plus_duration(duration);t=t.minus_duration(duration);constms:i32=1234;t=t.plus_ms(ms);consts:i32=1;t=t.plus_seconds(s);constm:i32=12t=t.plus_minutes(m);consth:i32=-3;t=t.plus_hours(h);

Date_Time

This struct simply combines a Date and Time, representing a single instant, but without specifying whether that date and time is based on UTC, TAI, or some local time zone (see Date_Time.With_Offset for that).

Convenience Decls

vardt:Date_Time=.epoch;// 2000年01月01日T00:00:00.000dt=.unix_epoch;// 1970年01月01日T00:00:00.000dt=.ntp_epoch;// 1900年01月01日T00:00:00.000dt=.ntfs_epoch;// 1601年01月01日T00:00:00.000

Decomposition & Conversion

constdt:Date_Time=.epoch;constdate:Date=dt.date;constt:Time=dt.time;constutc_offset_ms:i32=0;constdto1:Date_Time.With_Offset=dt.with_offset(utc_offset_ms);consttz:*constTimezone=&Timezone.utc;constdto2:Date_Time.With_Offset=dt.with_timezone(tz);

Comparison

constdt1:Date_Time=.epoch;constdt2:Date_Time=Date_Time.next(.epoch);std.debug.assert(dt1.is_before(dt2));std.debug.assert(!dt1.is_before(dt1));std.debug.assert(dt2.is_after(dt1));std.debug.assert(!dt2.is_after(dt2));// These do not account for leap seconds; see `Date_Time.With_Offset` versions of these functionsconstduration:std.Io.Duration=dt2.duration_since(dt1);constms:i32=dt2.ms_since(dt1);

Modification

vardt:Date_Time=.epoch;// This does not account for leap seconds; see `Date_Time.With_Offset` versions of these functionsconstdays:i32=10;constms:i32=1234;dt=dt.plus_days_and_ms(days,ms);// This does not account for leap seconds; see `Date_Time.With_Offset` versions of these functionsconstduration:std.Io.Duration=.fromSeconds(60);dt=dt.plus_duration(duration);dt=dt.minus_duration(duration);

Date_Time.With_Offset

This struct combines a Date_Time with a UTC offset, allowing for conversions to/from unix timestamps. Optionally, it can also include a pointer to a Timezone, which can be helpful when formatting, parsing, and modifying the instant.

Construction

constio:std.Io=...constdto:Date_Time.With_Offset=tempora.now_utc(io);
constio:std.Io=...consttzdb:*constTZDB=...constdto:Date_Time.With_Offset=tempora.now_local(io,tzdb);
constio:std.Io=...consttz:*constTimezone=&Timezone.utc;constdto:Date_Time.With_Offset=tempora.now(io,tz);
constts:std.Io.Timestamp=.fromNanoseconds(0);consttz:?*constTimezone=null;constdto:Date_Time.With_Offset=.from_timestamp(ts,tz);
constts:i64=0;consttz:?*constTimezone=null;constdto:Date_Time.With_Offset=.from_timestamp_ms(ts,tz);
constts:i64=0;consttz:?*constTimezone=null;constdto:Date_Time.With_Offset=.from_timestamp_s(ts,tz);

Decomposition/Conversion

constio:std.Io=...constdto:Date_Time.With_Offset=tempora.now_utc(io);constts:std.Io.Timestamp=dto.timestamp();constts_ms:i64=dto.timestamp_ms();constts_s:i64=dto.timestamp_s();

Conversion Between Timezones

constio:std.Io=...vardto:Date_Time.With_Offset=tempora.now_utc(io);constother_tz:Timezone=.fixed(1,0);dto=dto.in_timezone(&other_tz);

Comparison

constdto1:Date_Time.With_Offset=.from_timestamp_s(0,null);constdto2:Date_Time.With_Offset=.from_timestamp_s(1,null);std.debug.assert(dto1.is_before(dto2));std.debug.assert(!dto1.is_before(dto1));std.debug.assert(dto2.is_after(dto1));std.debug.assert(!dto2.is_after(dto2));// These *do* provide accurate durations across leap second discontinuities in UTC:constduration:std.Io.Duration=dto2.duration_since(dto1);constms:i32=dto2.ms_since(dto1);// These *do not* provide accurate durations across leap second discontinuities in UTC:constduration:std.Io.Duration=dto2.duration_since_ignore_leap_seconds(dto1);constms:i32=dto2.ms_since_ignore_leap_seconds(dto1);

Modification

vardto:Date_Time.With_Offset=.from_timestamp_s(0,null);dto.dt.time=dto.dt.time.plus_hours(25);dto=dto.canonical();// This *does* account for leap second discontinuities in UTC:constdays:i32=10;constms:i32=1234;dto=dto.plus_days_and_ms(days,ms);// This *does* account for leap second discontinuities in UTC:constduration:std.Io.Duration=.fromSeconds(60);dto=dto.plus_duration(duration);dto=dto.minus_duration(duration);// This *does not* account for leap second discontinuities in UTC:constdays:i32=10;constms:i32=1234;dto=dto.plus_days_and_ms_ignore_leap_seconds(days,ms);// This *does not* account for leap second discontinuities in UTC:constduration:std.Io.Duration=.fromSeconds(60);dto=dto.plus_duration_ignore_leap_seconds(duration);dto=dto.minus_duration_ignore_leap_seconds(duration);

Formatting

constdto:Date_Time.With_Offset=.from_timestamp_s(0,&Timezone.utc);writer.print("{f}",.{dto.fmt(Date_Time.With_Offset.iso8601)});// 1970年01月01日T00:00:00.000+00:00writer.print("{f}",.{dto.fmt(Date_Time.With_Offset.iso8601_local)});// 1970年01月01日T00:00:00.000writer.print("{f}",.{dto.fmt(Date_Time.With_Offset.rfc2822)});// 1970年1月01日 00:00:00 +0000writer.print("{f}",.{dto.fmt(Date_Time.With_Offset.http)});// 1970年1月01日 00:00:00 GMTwriter.print("{f}",.{dto.fmt(Date_Time.With_Offset.sql_ms)});// 1970年01月01日 00:00:00.000 UTCwriter.print("{f}",.{dto.fmt(Date_Time.With_Offset.sql_ms_local)});// 1970年01月01日 00:00:00.000writer.print("{f}",.{dto.fmt(Date_Time.With_Offset.sql)});// 1970年01月01日 00:00:00 UTCwriter.print("{f}",.{dto.fmt(Date_Time.With_Offset.sql_local)});// 1970年01月01日 00:00:00

Parsing

vardto:Date_Time.With_Offset=undefined;dto=try.from_string(Date.iso8601,"2025年10月01日T12:12:12.000+00:00");dto=try.from_string("YYYY","2025");// 2025年01月01日T00:00:00.000+00:00consttz:Timezone=Timezone.fixed(-1,0);dto=try.from_string_tz(Date.iso8601_local,"2025年10月01日T12:12:12.000",&tz);consttzdb:*constTZDB=...dto=try.from_string_tzdb("YYYY-MM-DD HH:mm:ss z","2025年10月01日 12:12:12 CDT",tzdb);

Time.With_Offset

This struct is like Date_Time.With_Offset except without any Date. It is mostly only useful for formatting and parsing strings that do not contain date information. Otherwise you should prefer to work with Date_Time.With_Offset instead.

Conversion Between Timezones

constto:Time.With_Offset=(Time.midnight).with_offset(0);constother_tz:Timezone=.fixed(1,0);to=to.in_timezone(&other_tz);

Decomposition/Conversion

constto:Time.With_Offset=(Time.midnight).with_offset(0);constdate:Date=.epoch;constdto:Date_Time.With_Offset=to.with_date(date);

Formatting

constto:Time.With_Offset=(Time.midnight).with_offset(0);writer.print("{f}",.{cst.fmt(Time.With_Offset.iso8601)});// 00:00:00.000-00:00writer.print("{f}",.{cst.fmt(Time.With_Offset.iso8601_local)});// 00:00:00.000writer.print("{f}",.{cst.fmt(Time.With_Offset.rfc2822)});// 00:00:00 +0000writer.print("{f}",.{ct.fmt("h:mm a")});// 00:00 am

Parsing

varparsed_time:Time.With_Offset=undefined;parsed_time=try.from_string("h:mm a","1:00 pm");parsed_time=try.from_string_tz(Time.With_Offset.iso8601,"13:00:00.000-06:00",tz);parsed_time=try.from_string_tzdb("05:05:05 CDT",tzdb);

Timezone

A Timezone struct contains all the information required to convert between UTC and local wall clock times for a particular local time zone.

There are two built-in timezone constants, Timezone.utc and Timezone.tai. Neither of these "timezones" include any DST rules or UTC offset information, however Timezone.tai includes leap-second adjustment information which is needed when trying to work with durations that are accurate to the second or better (see Date_Time.With_Offset.duration_since, Date_Time.With_Offset.plus_duration, etc.)

TZDB

Most of the time it's convenient to use IANA-style timezone names (Region/City_Name or Region/Sub_Region/City_Name) to refer to timezones, but this means there needs to be something in your program that can map these strings to the actual Timezone data. In tempora you do this by initializing a TZDB struct, typically when your program starts:

pubfnmain(init:std.process.Init)!void{vartzdb:tempora.TZDB=.init(init);defertzdb.deinit();trytzdb.add(init.io,tempora.tz.all,.system_or_embedded(init.environ_map));trytzdb.add_current(init.io,.system_link(init.environ_map));// your program here ...}

This will load all of the timezones from the embedded IANA timezone database, but any timezones it can find on the system will be preferred, on the assumption that they're likely to be newer.

If you want to avoid bloating your executable, you can force all timezones to be loaded from the system:

pubfnmain(init:std.process.Init)!void{vartzdb:tempora.TZDB=.init(init);defertzdb.deinit();trytzdb.add(init.io,tempora.tz.all,.system(init.environ_map));trytzdb.add_current(init.io,.system(init.environ_map));// your program here ...}

Alternatively, you can include just a portion of the IANA database:

consttz=tempora.tz;trytzdb.add(init.io,.{tz.america,tz.europe,tz.pacific.honolulu},.system_or_embedded(init.environ_map));

You can also force tempora to use only the embedded IANA database:

trytzdb.add(init.io,tempora.tz.all,.embedded);

Normally each TZif blob in the embedded IANA database is compressed with zlib, but you can embed it uncompressed instead by using the tempora.tz.uncompressed namespace instead of tempora.tz.all. Similarly, if you want to exclude all the timezone IDs which are simply aliases to other zones, you can use tempora.tz.canonical or tempora.tz.canonical_uncompressed. This won't significantly affect binary size, but may reduce startup time slightly.

Some programs may want to avoid loading the timezone database at startup entirely (e.g. CLI tools, where even minor startup delays can be highly noticeable). In this case, TZDB can be instructed to only load/parse the timezone data lazily, the first time it is accessed:

trytzdb.add_lazy(tempora.tz.all,&.system_or_embedded(init.environ_map));

Note that for add_lazy, you must pass a pointer to the the add options, and it must remain valid for the lifetime of the TZDB.

You may also want to support lazily loading TZif files from the filesystem that were never specified by add or add_lazy, to allow usage of new zones that didn't exist when the program was built:

tzdb.default_lazy_options=&.system_or_embedded(init.environ_map);

Note however, that both of the two above examples have a downside: the TZDB can no longer be used concurrently from multiple threads unless external synchronization is provided, or each thread/task has its own separate TZDB.

Timezone offset designations

If you want to be able to parse date/time strings that contain colloquial timezone offset designators like PST/PDT, you'll need to initialize these in the TZDB:

trytzdb.add_designations(tempora.tz.designations.common);

In addition to the common namespace, there are a variety of other collections:

  • nato (military-style single-letter designations)
  • north_america
  • cuba
  • europe
  • africa
  • middle_east
  • asia
  • oceania (Australia, NZ, and Pacific Islands)

You can add several of these collections, however there are a few designations that have different meanings in different regions, like "IST". The common namespace includes almost everything except those ambiguous designations, and the nato collection. You can also add custom designations if you like, but you'll have to specify the UTC offset (in seconds) manually.

When parsing, make sure you use .from_string_tzdb() instead of .from_string() so that the parser can find your designations.

Year

A non-exhaustive enum for representing a year number in a type-safe way. The underlying integer value directly corresponds to years in the AD era.

Year.Info is a struct with mostly the same capabilities as Year, except that the starting date and leap year status is precomputed and stored as fields. This can be useful for performance optimization if these would otherwise end up being recomputed multiple times.

Year.Dominical_Letter is used internally for ISO Week Date computations, but is exposed publicly in case it's useful for other purposes.

Construction

consty:i32=1970;constyear:Year=.from_number(y);

Convenience Decls

vary:Year=.epoch;// 2000y=.unix_epoch;// 1970y=.ntp_epoch;// 1900y=.ntfs_epoch;// 1601y=.min;// -5877610 -- lowest value where `.starting_date()` and `.ending_date()` are both validy=.max;// 5881609 -- highest year where .starting_date() and .ending_date() are both valid

Parsing

varyear:Year=undefined;year=.from_string("1968",.{});year=.from_string("68",.{.allow_two_digit_year=true});// 1968year=.from_string("49",.{.allow_two_digit_year=true});// 2049year=.from_string("168 AD",.{});// 168year=.from_string("1 BC",.{});// 0year=.from_string("10000 BC",.{});// -9999

Decomposition/Conversion

constyear:Year=.epoch;consty_i32:i32=year.as_number();consty_u32:u32=year.as_unsigned();constleap:bool=year.is_leap();constyi:Year.Info=year.info();constdc:Year.Dominical_Letter=year.dominical_letter();conststarting_date:Date=year.starting_date();constending_date:Date=year.ending_date();constm:Month=.january;constd:Day=.@"10";constdate:Date=year.date(m,d);constdi:Date.Info=year.date_info(m,d);consyymd:Date.YMD=year.ymd(m,d);

Comparison

consty1:Year=.epoch;consty2:Year=.from_number(2001);std.debug.assert(y1.is_before(y2));std.debug.assert(!y1.is_before(y1));std.debug.assert(y2.is_after(y1));std.debug.assert(!y2.is_after(y2));

Modification

vary:Year=.epoch;constyears:i32=12;y=y.plus(years);y=y.next();y=y.prev();

Month

An enum representing each of the months in the gregorian calendar. Underlying integer values correspond to the traditional 1-based counting where January is 1 and December is 12.

Construction

constmonth:Month=.january;
constm:i32=12;constmonth:Month=.from_number(m);
consty:Year=.epoch;constod:Ordinal_Day=.from_number(60);constmonth:Month=.from_yod(y,od);
constyi:Year.Info=.from_number(2020);constod:Ordinal_Day=.from_number(60);constmonth:Month=.from_yiod(yi,od);
constod:Ordinal_Day=.from_number(60);constis_leap_year=true;constmonth:Month=.from_od(od,is_leap_year);

Parsing

varmonth:Month=undefined;month=.from_string("January",.{});month=.from_string("Jan",.{});month=.from_string("1",.{});

Decomposition/Conversion

constmonth:Month=.march;constm_i32:i32=month.as_number();constm_u32:u32=month.as_unsigned();constname:[]constu8=month.name();constshort:[]constu8=month.short_name();consty:Year=.epoch;constyi:Year.Info=year.info();vardays:u16=month.days(y);days=month.days_from_yi(yi);days=month.days_assume_non_leap_year();days=month.days_assume_leap_year();varod:Ordinal_Day=month.starting_ordinal_day(y);od=month.starting_ordinal_day_assume_non_leap_year();od=month.starting_ordinal_day_assume_leap_year();conststarting_date:Date=month.starting_date(y);

Comparison

constm1:Month=.january;constm2:Month=.february;std.debug.assert(m1.is_before(m2));std.debug.assert(!m1.is_before(m1));std.debug.assert(m2.is_after(m1));std.debug.assert(!m2.is_after(m2));

Modification

varm:Month=.june;constmonths:i32=3;m=m.plus(months);m=m.next();m=m.prev();

Day of month

A non-exhaustive enum representing a day-of-the-month. Underlying integer values correspond to the traditional 1-based counting where each month starts with day 1 and ends with day 28-31.

Construction

constd:Day=.first;
constd:i32=11;constday:Day=.from_number(d);
consty:Year=.epoch;constod:Ordinal_Day=.from_number(60);constday:Day=.from_yod(y,od);
constyi:Year.Info=.from_number(2020);constod:Ordinal_Day=.from_number(60);constday:Day=.from_yiod(yi,od);
constod:Ordinal_Day=.from_number(60);constis_leap_year=true;constday:Day=.from_od(od,is_leap_year);

Convenience Decls

vard:Day=.@"1";d=.@"2";d=.@"3";d=.@"4";d=.@"5";//...d=.@"28";d=.@"29";d=.@"30";d=.@"31";

Decomposition/Conversion

constday:Day=.@"15";constd_i32:i32=day.as_number();constd_u32:u32=day.as_unsigned();constdate:Date=.epoch;varnew_date:Date=day.on_or_after(date);new_date=day.on_or_before(date);constymd:Date.YMD=.from_numbers(1234,11,1);new_date=day.on_or_after_ymd(ymd);new_date=day.on_or_before_ymd(ymd);

Comparison

constd1:Day=.first;constd2:Day=.@"2";std.debug.assert(d1.is_before(d2));std.debug.assert(!d1.is_before(d1));std.debug.assert(d2.is_after(d1));std.debug.assert(!d2.is_after(d2));

Modification

vard:Day=.first;constdays:i32=3;d=d.plus(days);d=d.next();d=d.prev();

Week_Day

An enum representing each of the days of the week.

Construction

constwd:Week_Day=.sunday;
constnum:i32=7;constwd:Week_Day=.from_number(num);// sunday = 1, saturday = 7constwd:Week_Day=.from_iso(num);// monday = 1, sunday = 7

Parsing

varwd:Week_Day=undefined;wd=.from_string("Tuesday",.{});wd=.from_string("Tue",.{});wd=.from_string("Tu",.{});wd=.from_string("3",.{});

Decomposition/Conversion

constwd:Week_Day=.march;constwd_i32:i32=wd.as_number();constwd_u32:u32=wd.as_unsigned();constiso:u3=wd.as_iso();constname:[]constu8=wd.name();constshort:[]constu8=wd.short_name();constdate:Date=.epoch;varnew_date:Date=wd.on_or_after(date);new_date=wd.on_or_before(date);

Comparison

constwd1:Week_Day=.thursday;constwd2:Week_Day=.friday;std.debug.assert(wd1.is_before(wd2));std.debug.assert(!wd1.is_before(wd1));std.debug.assert(wd2.is_after(wd1));std.debug.assert(!wd2.is_after(wd2));

Modification

varwd:Week_Day=.monday;constdays:i32=3;wd=wd.plus(days);wd=wd.next();wd=wd.prev();

Ordinal_Day of year

A non-exhaustive enum corresponding to the day-of-the-year. When combined with a year, it forms what is sometimes colloquially called a "Julian date".

Construction

constod:Ordinal_Day=.first;
constd:i32=11;constod:Ordinal_Day=.from_number(d);
constymd:Date.YMD=.from_date(.epoch);constod:Ordinal_Day=.from_ymd(ymd);
constyi:Year.Info=.from_number(2020);constm:Month=.february;constd:Day=.@"5";constod:Ordinal_Day=.from_yimd(yi,m,d);
constm:Month=.february;constd:Day=.@"5";constod:Ordinal_Day=.from_md_assume_non_leap_year(m,d);

Convenience Decls

varod:Ordinal_Day=.first;// 1od=.leap_day;// 60od=.last_no_leap;// 365od=.last_leap;// 366

Decomposition/Conversion

constod:Ordinal_Day=.@"15";constod_i32:i32=od.as_number();constod_u32:u32=od.as_unsigned();constow:Ordinal_Week=od.ordinal_week();consty:Year=.epoch;vardate:Date=od.date_from_year(y);constyi:Year.Info=.from_number(1999);date=od.date_from_yi(yi);

Comparison

constod1:Ordinal_Day=.first;constod2:Ordinal_Day=.@"2";std.debug.assert(od1.is_before(od2));std.debug.assert(!od1.is_before(od1));std.debug.assert(od2.is_after(od1));std.debug.assert(!od2.is_after(od2));

Modification

varod:Ordinal_Day=.first;constdays:i32=3;od=od.plus(days);od=od.next();od=od.prev();

Ordinal_Week of year

This non-exhaustive enum corresponds to the number of full or partial weeks that have passed since the start of the calendar year. Note that this is not the same as the ISO week number and it is not necessarily aligned with the Sunday-Saturday or Monday-Sunday calendar weeks; rather the 1st through 7th of January is always in ordinal week 1, the 8th through 14th is always ordinal week 2, etc.

Construction

constow:Ordinal_Week=.first;
constw:i32=11;constow:Ordinal_Week=.from_number(w);
constod:Ordinal_Day=.first;constow:Ordinal_Week=.from_od(od);

Decomposition/Conversion

constow:Ordinal_Week=.first;constw_i32:i32=ow.as_number();constw_u32:u32=ow.as_unsigned();constod:Ordinal_Day=ow.starting_day();

Comparison

constow1:Ordinal_Week=.from_number(3);constow2:Ordinal_Week=.from_number(4);std.debug.assert(ow1.is_before(ow2));std.debug.assert(!ow1.is_before(ow1));std.debug.assert(ow2.is_after(ow1));std.debug.assert(!ow2.is_after(ow2));

Modification

varow:Ordinal_Week=.first;constweeks:i32=3;ow=ow.plus(weeks);ow=ow.next();ow=ow.prev();

ISO_Week

A non-exhaustive enum which corresponds to the ISO week number

Construction

constiw:ISO_Week=.first;
constw:i32=11;constiw:ISO_Week=.from_number(w);
consty:Year=.epoch;constiw:ISO_Week=.last(y);// the last valid ISO week in a particular year; either the 52nd 53rd week
constdc:Year.Dominical_Letter=.a;constiw:ISO_Week=.last_from_dc(dc);

Decomposition/Conversion

constiw:ISO_Week=.first;constw_i32:i32=iw.as_number();constw_u32:u32=iw.as_unsigned();

Comparison

constiw1:ISO_Week=.from_number(3);constiw2:ISO_Week=.from_number(4);std.debug.assert(iw1.is_before(iw2));std.debug.assert(!iw1.is_before(iw1));std.debug.assert(iw2.is_after(iw1));std.debug.assert(!iw2.is_after(iw2));

Modification

variw:ISO_Week=.first;constweeks:i32=3;iw=iw.plus(weeks);iw=iw.next();iw=iw.prev();

ISO_Week_Date

A decomposed date (like Date.YMD) that uses the ISO year, week number, and day-of-week instead of year, month, and day-of-month. Note that while the years in this struct use the same Year type as normal calendar dates, the ISO year begins on the monday of W01 and ends on the sunday of W52 or W53, so the corresponding calendar year may be different at the beginning/end of year.

Construction

constdate:Date=.epoch;constiwd:ISO_Week_Date=.from_date(date);
constyi:Year.Info=.from_number(2005);constod:Ordinal_Day=.first;constwd:Week_Day=.saturday;// note this is assumed to be the correct day-of-week for the given year/ODconstiwd:ISO_Week_Date=.from_yiodwd(yi,od,wd);

Decomposition/Conversion

constiwd:ISO_Week_Date=.from_date(date);consty:Year=iwd.year;constiw:ISO_Week=iwd.week;constwd:Week_Day=iwd.day;constdate:Date=iwd.date();

Comparison

constiwd1:ISO_Week_Date=.from_date(.epoch);constiwd2:ISO_Week_Date=.from_date(.next(.epoch));std.debug.assert(iwd1.is_before(iwd2));std.debug.assert(!iwd1.is_before(iwd1));std.debug.assert(iwd2.is_after(iwd1));std.debug.assert(!iwd2.is_after(iwd2));

Modification

variwd:ISO_Week_Date=.from_date(.epoch);constdays:i32=3;iwd=iwd.plus_days(days);iwd=iwd.next();iwd=iwd.prev();

Formatting

constiwd:ISO_Week_Date=.from_date(.from_year(2005));writer.print("{f}",.{iwd.fmt(ISO_Week_Date.iso8601_week_date)});// 2004-W53-6writer.print("{f}",.{iwd.fmt(ISO_Week_Date.iso8601_week)});// 2004-W53writer.print("{f}",.{iwd.fmt(ISO_Week_Date.datecode)});// 0453

Parsing

variwd:ISO_Week_Date=undefined;iwd=try.from_string(ISO_Week_Date.iso8601_week_date,"2004-W53-6");iwd=try.from_string(ISO_Week_Date.iso8601_week,"2000-W01");iwd=try.from_string(ISO_Week_Date.datecode,"2511");

The dump tool

A small demo/tool is provided that prints out the current time in one or more timezones and the last/next time a DST change will happen for that zone:

$ zig build dump -- America/Chicago Africa/Maputo
Current Time: 2026年06月02日 21:03:49 CDT offset=-18000s dst=dst source=posix_tz
 DST began: 2026年03月08日 03:00:00 CDT
 DST ends: 2026年11月01日 01:00:00 CST
Current Time: 2026年06月02日 28:03:49 CAT offset=7200s dst=std source=posix_tz
 This timezone has permanent standard time
 The current time rules for this zone began on 1908年12月31日 23:49:42 CAT

You can pass the --debug command line option to additionally print out all the internal timezone data in a format similar to TZif, but human-readable.

By default, dump will only use it's internal IANA timezone database, but if you use the --system command line option it will instead look for a system-provided timezone.

Comparison with other Zig date/time libraries

tempora Zeit zdt zig-datetime
Supported zig versions 0.15.1 - 0.17.0-dev 0.13.0 - 0.17.0-dev 0.15.1 - 0.17.0-dev 0.14.0 - 0.15.2
Time Resolution 1 millisecond 1 nanosecond 1 nanosecond 1 nanosecond
Minimum representable date 22 June 5,877,612 BC 1 January 2,147,483,649 BC 1 January 1 BC 1 January 0001
Maximum representable date 11 July 5,881,610 31 December 2,147,483,647 31 December 9999 31 December 9999
Gregorian Calendar algorithm Joffe Joffe Hinnant/Neri-Schneider unknown
Packed date/time epoch 1 January 2000 1 January 1970 1 January 1970 1 January 0001
Packed date Date - - u32 (Date.fromOrdinal(), Date.toOrdinal())
Decomposed date Date.YMD, Date.Info Date - datetime.Date
Packed datetime Date_Time Nanoseconds (i128) i128 i128
Decomposed datetime - - - -
Packed datetime (localized) Date_Time.With_Offset Instant - -
Decomposed datetime (localized) - Time Datetime datetime.Datetime
Packed time Time - - -
Decomposed time - - - datetime.Time
Packed time (localized) Time.With_Offset - - -
Decomposed time (localized) - - - -
Packed duration std.Io.Duration - Duration -
Decomposed duration - Duration RelativeDelta datetime.Datetime.Delta
Year Year, Year.Info i32 i16 u16
Month Month Month Datetime.Month datetime.Month
Day of month Day u5 u8 u8
Day of week Week_Day Weekday Datetime.Weekday datetime.Weekday
Day of year Ordinal_Day - u16 (Datetime.dayOfYear()) u8
Week of year Ordinal_Week - - -
ISO week date ISO_Week, ISO_Week_Date - Datetime.ISOCalendar datetime.ISOCalendar
Month/Week name localization English only English only English or current locale (Linux, MacOS, Windows) English only
Parsed input formats moment.js/SimpleDateFormat style ISO8601, RFC3339, RFC5322, RFC2822, RFC1123 strptime style ISO8601, RFC1123
Formatted output formats moment.js/SimpleDateFormat style strftime style, gofmt style strftime style ISO8601, RFC1123
Current date/time now_utc(io), now_local(io, tzdb), now(io, tz) instant(io, .{...}) Datetime.nowUTC(io), Datetime.nowTAI(io), Datetime.now(io, .{...}) datetime.Datetime.now()
Timezone Timezone TimeZone Timezone datetime.Timezone
Timezone Database TZDB - internal timezones
Current timezone TZDB.local (Posix via fs, Windows via registry/ntdll.dll) local(alloc, io, env) (Posix via fs, Windows via advapi.dll) Timezone.tzLocal(io, alloc) (Posix via fs, Windows via registry/advapi.dll) -
Embedded IANA tzdb? yes (configurable) no yes partial (no TZif support)
Filesystem tzdb? yes yes yes no
Windows tzdb? yes (via registry/ntdll.dll) yes (via advapi.dll) no no
Leap second database? Timezone.data.leap_seconds no internal no

Other zig date/time libraries include:

None of these other libraries support zig 0.15.x or newer, so they are excluded from the above comparison.