zig

How can I generate a timestamp string using zig?


using zig, I need to generate a timestamp that format exactly or almost matches 2025-04-18T14:03:52.000Z. How can I go about it? using the standard library only


Solution

  • It is possible to do this, but I would strongly suggest using a library like zig-time, zig-datetime or zig-tzif instead.

    Also please note that this is one of my first Zig programs, so it might not adhere to all conventions and best practices.

    That said, here is how I would do it using the standard library only:

    const std = @import("std");
    
    const DateTime = struct {
        year: u32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        millisecond: u16,
    };
    
    fn isLeapYear(year: u32) bool {
        return (@rem(year, 4) == 0 and @rem(year, 100) != 0) or (@rem(year, 400) == 0);
    }
    
    fn daysInMonth(month: u8, year: u32) u8 {
        return switch (month) {
            1 => 31,
            2 => if (isLeapYear(year)) 29 else 28,
            3 => 31,
            4 => 30,
            5 => 31,
            6 => 30,
            7 => 31,
            8 => 31,
            9 => 30,
            10 => 31,
            11 => 30,
            12 => 31,
            else => unreachable,
        };
    }
    
    fn unixTimestampToUTC(timestamp: u64) DateTime {
        const MILLIS_PER_SEC = 1000;
        const SECS_PER_MIN = 60;
        const SECS_PER_HOUR = SECS_PER_MIN * 60;
        const SECS_PER_DAY = SECS_PER_HOUR * 24;
    
        const millisecond: u16 = @intCast(@rem(timestamp, MILLIS_PER_SEC));
        const seconds = @divTrunc(timestamp, MILLIS_PER_SEC);
    
        // Compute the time of day.
        const hour: u8 = @intCast(@divTrunc(@rem(seconds, SECS_PER_DAY), SECS_PER_HOUR));
        const minute: u8 = @intCast(@divTrunc(@rem(seconds, SECS_PER_HOUR), SECS_PER_MIN));
        const second: u8 = @intCast(@rem(seconds, SECS_PER_MIN));
    
        // Compute the date.
        var days = @divTrunc(seconds, SECS_PER_DAY);
        var year: u32 = 1970;
    
        while (true) {
            const days_in_year: u16 = if (isLeapYear(year)) 366 else 365;
            if (days >= days_in_year) {
                days -= days_in_year;
                year += 1;
            } else break;
        }
    
        var month: u8 = 1;
        while (true) {
            const day_of_month = daysInMonth(month, year);
            if (days >= day_of_month) {
                days -= day_of_month;
                month += 1;
            } else break;
        }
    
        const day: u8 = @intCast(days + 1);
    
        return DateTime{
            .year = year,
            .month = month,
            .day = day,
            .hour = hour,
            .minute = minute,
            .second = second,
            .millisecond = millisecond,
        };
    }
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
        const stdout = std.io.getStdOut().writer();
    
        // Timestamps before 01-01-1970 are not supported by this program.
        const timestamp: u64 = @intCast(std.time.milliTimestamp());
    
        const dt = unixTimestampToUTC(timestamp);
    
        const iso8601 = try std.fmt.allocPrint(
            allocator,
            "{}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}.{:0>3}Z",
            .{ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond },
        );
        defer allocator.free(iso8601);
    
        try stdout.print("ISO 8601 UTC: {s}\n", .{iso8601});
    }
    

    Which gives you an output like:

    ISO 8601 UTC: 2025-04-23T17:07:17.668Z
    

    Currently this code comes with several caveats that all could be fixed, but I did not attempt this for the sake of brevity: