- Zig 100%
| .github/workflows | Don't run current timezone tests in CI | |
| bench | v0.6.0: | |
| src | tzdata 2026c | |
| test | Don't run current timezone tests in CI | |
| tools | bugfixes, expand tests | |
| .gitattributes | tempora | |
| .gitignore | add gh workflows | |
| build.zig | Don't run current timezone tests in CI | |
| build.zig.zon | tzdata 2026c | |
| license | v0.4: Add ISO week date support | |
| readme.md | documentation | |
Tempora
Simple Zig Dates, Times, and Timezones
Features
- Efficient storage (32b
Time, 32bDate, 64bDate_Time) - Conversion to/from unix timestamps
- Composition and decomposition (year, ordinal day/week, month, day, weekday, hour, minute, second, ms)
- Uses Ben Joffe's fast calendar algorithm
- 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.cor any system dependencies (just runzig build -Dcodegen)
- No dependencies
- Except when using
zig build -Dcodegenorzig build -Dbenchmarks
- Except when using
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")});// 00Parsing
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 midnightconsts:i32=1234;constt:Time=.from_seconds(s);// 20 minutes and 34 seconds after midnightconstm: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);// noonconsth:u31=20;constm:u8=30;consts:u8=0;constms:u10=0;constt:Time=.from_hmsm(h,m,s,ms);// 8:30 pmHourly 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.000Decomposition & 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:00Parsing
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 amParsing
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_americacubaeuropeafricamiddle_eastasiaoceania(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 validParsing
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",.{});// -9999Decomposition/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 = 7Parsing
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;// 366Decomposition/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 weekconstdc: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)});// 0453Parsing
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:
- zig-time - requires
zigmod - datetime
- tempus
- chrono-zig
None of these other libraries support zig 0.15.x or newer, so they are excluded from the above comparison.