Timezone-aware datetime - zdt

  • datetime and timezone handling in one lib
  • no separate types for date and time
  • hybrid approach: datetime combines incremental and field based representation (Unix time, year, month, etc. fields)
  • relatively simple concept for time zone type (IANA-db file and/or offset from UTC)
  • uses the TZif parser from the Zig standard library

This post is sort of a follow-up to What’s the state of datetime in Zig?. I’m happy that writing a library to handle datetime with time zones worked out well, Zig is fun to code in and its context (the community etc.) is nice. The goal for zdt is “exploration”, not to build something according to some spec. There’s probably a lot to do better, and a lot of edge cases to catch.


Some context; the proposal to add datetime functionality to the Zig standard lib is still open. There also has been a pull request, which is now closed (2024-01-08).

18 Likes

Time for an update.

Thanks to you people here, answering all my noob question, I could add a couple of features :sunglasses: Notably,

  • an ISO8601 string parser, which infers the input format at runtime
  • implementation of the IANA time zone database within the package, so it can be used cross-platform
  • a method to obtain the local time zone (Linux and Windows)

You can find demos for these features in the examples. There’s a build-step to compile them, zig build examples.

5 Likes

DST transition coming up for folks in Europe :sunny:

var tz_Paris = try zdt.Timezone.fromTzfile("Europe/Paris", allocator);
defer tz_Paris.deinit();

_ = zdt.Datetime.fromFields(.{
    .year = 2024,
    .month = 3,
    .day = 31,
    .hour = 2,
    .tzinfo = tz_Paris,
}) catch |err| {
    std.debug.print("oops, cannot create datetime; {}", .{err});
};
>>> oops, cannot create datetime; error.NonexistentDatetime

concerning zdt code, I’ve made a “clone” on github,

…mainly to try github actions with Zig and autodoc generation on github pages (not working at the moment due to this problem).

6 Likes

That’s cool. Things like date time are so complex that some people find unattractive, but so fundamental that we need them for any mundane work.

Last time I wanted a decent date time library with Zig, I reached out for a very crude solution with strftime attached with a microsecond timestamp (as Python did). I believe established Zig users such as Bun and Tigerbeetle have their own implementation, too, but there seems not to be a standalone excerpt anyway.

Cool, man!

1 Like

BTW, how popular is Code Berg in Deutschland?

Thanks! Yeah there’s a couple of related projects out there, I’ve collected a few in the misc/advanced document in the docs. I think it is perfectly fine to make a case-specific date/time implementation; that’s part of Zig’s philosophy I guess. In many cases, you don’t need time zone support for example, which makes things a lot simpler. Performance and memory limitations might also play a role, if you think embedded devices for instance. And as you say, using a platform’s C library (time struct, strftime/strptime etc.) is also relatively easy to include in Zig.

BTW, how popular is Code Berg in Deutschland?

I have no idea actually; I started using it the same time I started dabbling in Zig, which was only end of last year ^^

Missing portability and cross-compilation capability annoyed me so much that I’ve added an embedding of the full IANA tz database :stuck_out_tongue:

I’m using a StaticStringMap for this, which just holds the TZif data for each time zone name. That means that the actual rules still need to be extracted using the TZif parser. :rocket: For maximum performance (trading for memory I guess), the TZ structure returned by the parser could be stored at comptime, so that parsing isn’t required at runtime. However, the parser requires memory allocation. So if this would be possible to do at compile time (compile time allocator), here might be an application. The other option could be to come up with a zero allocation structure, potentially trading even more memory for performance.

3 Likes

As this turned from a learn-to-zig project into something usable more and more, an API revision became due. More consistent now in v0.3.0:

5 Likes

Re-written datetime parser/formatter = new possibilities. Yes, it’s a switch-loop state machine :wink: - I did not use labeled switches though to make it work in Zig 0.13.

  • directives with modifier à la Rust chrono, e.g. %z gives a UTC offset +0100 whilst %:z gives the offset with a colon, +01:00
  • added some common formats à la go time pkg, like RFC822 or RFC3339
  • extended ISO calendar handling, e.g. parse from string with %t and convert to a Gregorian calendar date
  • ISO8601 parser now also handles Year - Day-of-Year format
5 Likes

Some updates on the datetime side of things;

  • 0.3 to 0.4: important update on how timezones/offsets are handled, motivated by #32. Clean separation of timezone and UTC offset, timezones passed as *const consistently.
  • 0.4.2 and 0.4.3 patches extend duration handling. ISO8601 duration strings take some time getting used to (à la ‘M’ can be months and minutes?! Who thought up this madness…) but can be quite handy. There’s a parser for those now, plus you can do wall-time arithmetic like the following
const delta = try Duration.RelativeDelta.fromISO8601("P1D");
const dt_dst_off = try Datetime.fromFields(.{ .year = 2024, .month = 3, .day = 30, .hour = 8, .tz_options = .{ .tz = &tz_berlin } });
const dt_dst_on = try dt_dst_off.addRelative(delta);
println("{s} --> {s}", .{ dt_dst_off, dt_dst_on });
println("wall diff: {s}, absolute diff: {s}", .{ try dt_dst_on.diffWall(dt_dst_off), dt_dst_on.diff(dt_dst_off) });
// 2024-03-30T08:00:00+01:00 --> 2024-03-31T08:00:00+02:00
// wall diff: P1D, absolute diff: PT23H

…so your calendar app can display a notification “1 day before” or “1 day after” at the correct time during DST transitions.

4 Likes