Skip to content

DateTime

Description

A broken-down calendar instant. Plain value type – copy and discard freely; there is no Init/Deinit.

year : Proleptic Gregorian year (e.g. 2026). month : 1..12. day : 1..31. hour : 0..23. minute : 0..59. second : 0..60 (60 only across a leap second). weekday : 0=Sunday .. 6=Saturday. nanosecond : 0..999999999. utc_offset_seconds : Offset of this rendering from UTC, east-positive (0 for a UTC instant; +19800 for IST).

Usage example (Cross-references)

Usage examples (Cross-references)
        u32 nanosecond;
        i32 utc_offset_seconds;
    } DateTime;
    
    ///
    /// TAGS: Std, DateTime, Convert
    ///
    DateTime DateTimeFromUnixNs(u64 unix_ns, i32 utc_offset_seconds);
    
    ///
    /// TAGS: Std, DateTime, Convert
    ///
    u64 DateTimeToUnixNs(DateTime dt);
    
    ///
    /// TAGS: Std, DateTime, Compare
    ///
    i32 DateTimeCompare(DateTime a, DateTime b);
    
    ///
    /// TAGS: Std, DateTime, Compare
    ///
    i64 DateTimeDiffNs(DateTime a, DateTime b);
    
    ///
    /// TAGS: Std, DateTime, Arithmetic
    ///
    DateTime DateTimeAddNs(DateTime dt, i64 delta_ns);
    
    ///
    /// TAGS: Std, DateTime, Calendar
    ///
    u16 DateTimeYearDay(DateTime dt);
    
    #endif // MISRA_STD_DATETIME_H
    #include <Misra/Std/Container.h>
    #include <Misra/Std/Container/Buf.h>
    #include <Misra/Std/DateTime.h>
    #include <Misra/Std/Zstr.h>
    #include <Misra/Std/File.h>
                Str: TO_TYPE_SPECIFIC_IO(Str, &(x)),                                                                       \
                Buf: TO_TYPE_SPECIFIC_IO(Buf, &(x)),                                                                       \
                DateTime: TO_TYPE_SPECIFIC_IO(DateTime, &(x)),                                                             \
                IOFMT_FLOAT_CASE_(x, &(x)) IOFMT_INT_CASE_(x, &(x)) IOFMT_BITVEC_CASE_(x, &(x)) IOFMT_USER_CASE_(x, &(x))  \
                    Zstr: TO_TYPE_SPECIFIC_IO(Zstr, &(x)),                                                                 \
                Str: TO_TYPE_SPECIFIC_IO(Str, (void *)&(x)),                                                               \
                Buf: TO_TYPE_SPECIFIC_IO(Buf, (void *)&(x)),                                                               \
                DateTime: TO_TYPE_SPECIFIC_IO(DateTime, (void *)&(x)),                                                     \
                IOFMT_FLOAT_CASE_(x, (void *)&(x)) IOFMT_INT_CASE_(x, (void *)&(x)) IOFMT_BITVEC_CASE_(x, (void *)&(x))    \
                    IOFMT_USER_CASE_(x, (void *)&(x)) Zstr: TO_TYPE_SPECIFIC_IO(Zstr, (void *)&(x)),                       \
    bool _write_Str(Str *o, FmtInfo *fmt_info, Str *s);
    bool _write_Buf(Str *o, FmtInfo *fmt_info, Buf *b);
    bool _write_DateTime(Str *o, FmtInfo *fmt_info, DateTime *dt);
    bool _write_u8(Str *o, FmtInfo *fmt_info, u8 *v);
    bool _write_u16(Str *o, FmtInfo *fmt_info, u16 *v);
    Zstr _read_Str(Zstr i, FmtInfo *fmt_info, Str *s);
    Zstr _read_Buf(Zstr i, FmtInfo *fmt_info, Buf *b);
    Zstr _read_DateTime(Zstr i, FmtInfo *fmt_info, DateTime *dt);
    Zstr _read_u8(Zstr i, FmtInfo *fmt_info, u8 *v);
    Zstr _read_u16(Zstr i, FmtInfo *fmt_info, u16 *v);
    #define MISRA_SYS_CLOCK_H
    
    #include <Misra/Std/DateTime.h>
    #include <Misra/Types.h>
    /// TAGS: Sys, Clock, Time, Calendar, Utc
    ///
    DateTime ClockUtc(void);
    
    ///
    /// TAGS: Sys, Clock, Time, Calendar, Local, Timezone
    ///
    DateTime ClockLocal(void);
    
    #endif // MISRA_SYS_CLOCK_H
    // fraction is exactly the nanosecond count, so it parses back as a plain
    // integer. Writer and reader share the layout, so a value round-trips.
    bool _write_DateTime(Str *o, FmtInfo *fmt_info, DateTime *dt) {
        (void)fmt_info;
        if (!o || !dt) {
    }
    
    Zstr _read_DateTime(Zstr i, FmtInfo *fmt_info, DateTime *dt) {
        (void)fmt_info;
        if (!i || !dt) {
        }
    
        DateTime tmp;
        tmp.year               = year;
        tmp.month              = (u8)mon;
    /// tables and no branches.
    
    #include <Misra/Std/DateTime.h>
    
    #define NS_PER_SEC   1000000000ll
    }
    
    DateTime DateTimeFromUnixNs(u64 unix_ns, i32 utc_offset_seconds) {
        i64 total_sec = (i64)(unix_ns / (u64)NS_PER_SEC);
        u32 nanos     = (u32)(unix_ns % (u64)NS_PER_SEC);
            wd += 7;
    
        DateTime dt;
        dt.year               = year;
        dt.month              = (u8)month;
    }
    
    u64 DateTimeToUnixNs(DateTime dt) {
        i64 days      = days_from_civil(dt.year, dt.month, dt.day);
        i64 local_sec = days * SECS_PER_DAY + (i64)dt.hour * 3600 + (i64)dt.minute * 60 + (i64)dt.second;
    }
    
    i32 DateTimeCompare(DateTime a, DateTime b) {
        u64 ua = DateTimeToUnixNs(a);
        u64 ub = DateTimeToUnixNs(b);
    }
    
    i64 DateTimeDiffNs(DateTime a, DateTime b) {
        return (i64)DateTimeToUnixNs(a) - (i64)DateTimeToUnixNs(b);
    }
    }
    
    DateTime DateTimeAddNs(DateTime dt, i64 delta_ns) {
        u64 base = DateTimeToUnixNs(dt);
        return DateTimeFromUnixNs((u64)((i64)base + delta_ns), dt.utc_offset_seconds);
    }
    
    u16 DateTimeYearDay(DateTime dt) {
        i64 today = days_from_civil(dt.year, dt.month, dt.day);
        i64 jan1  = days_from_civil(dt.year, 1, 1);
    }
    
    DateTime ClockUtc(void) {
        return DateTimeFromUnixNs(ClockRealNs(), 0);
    }
    }
    
    DateTime ClockLocal(void) {
        u64 ns = ClockRealNs();
    #if FEATURE_FILE
        u64              base  = 1609459200ull * 1000000000ull; // 2021-01-01T00:00:00Z
    
        DateTime utc = DateTimeFromUnixNs(base, 0);
        StrClear(&s);
        StrAppendFmt(&s, "{}", utc);
        ok = ok && ZstrCompare(StrBegin(&s), "2021-01-01T00:00:00Z") == 0;
    
        DateTime ist = DateTimeFromUnixNs(base, 19800); // +05:30
        StrClear(&s);
        StrAppendFmt(&s, "{}", ist);
        ok = ok && ZstrCompare(StrBegin(&s), "2021-01-01T05:30:00+05:30") == 0;
    
        DateTime neg = DateTimeFromUnixNs(base, -34200); // -09:30
        StrClear(&s);
        StrAppendFmt(&s, "{}", neg);
        ok = ok && ZstrCompare(StrBegin(&s), "2020-12-31T14:30:00-09:30") == 0;
    
        DateTime frac = DateTimeFromUnixNs(base + 123456789ull, 0);
        StrClear(&s);
        StrAppendFmt(&s, "{}", frac);
        {
            Zstr     in = "2021-01-01T00:00:00Z";
            DateTime d  = {0};
            StrReadFmt(in, "{}", d);
            ok = ok && d.year == 2021 && d.month == 1 && d.day == 1 && d.hour == 0 && d.minute == 0 && d.second == 0 &&
        {
            Zstr     in = "2021-01-01T05:30:00+05:30";
            DateTime d  = {0};
            StrReadFmt(in, "{}", d);
            ok = ok && d.year == 2021 && d.hour == 5 && d.minute == 30 && d.utc_offset_seconds == 19800;
        {
            Zstr     in = "2020-12-31T14:30:00-09:30";
            DateTime d  = {0};
            StrReadFmt(in, "{}", d);
            ok = ok && d.year == 2020 && d.month == 12 && d.day == 31 && d.hour == 14 && d.minute == 30 &&
        {
            Zstr     in = "2021-01-01T00:00:00.123456789Z";
            DateTime d  = {0};
            StrReadFmt(in, "{}", d);
            ok = ok && d.nanosecond == 123456789 && d.utc_offset_seconds == 0;
    #include <Misra.h>
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/DateTime.h>
    #include <Misra/Std/Io.h>
        for (u32 s = 0; s < sizeof(samples) / sizeof(samples[0]); ++s) {
            for (u32 o = 0; o < sizeof(offsets) / sizeof(offsets[0]); ++o) {
                DateTime d = DateTimeFromUnixNs(samples[s], offsets[o]);
                if (DateTimeToUnixNs(d) != samples[s]) {
                    return false;
    
    static bool test_to_unix_known(void) {
        DateTime epoch = {.year = 1970, .month = 1, .day = 1};
        DateTime day1  = {.year = 1970, .month = 1, .day = 2};
        return DateTimeToUnixNs(epoch) == 0 && DateTimeToUnixNs(day1) == 86400ull * NS_PER_SEC;
    static bool test_to_unix_known(void) {
        DateTime epoch = {.year = 1970, .month = 1, .day = 1};
        DateTime day1  = {.year = 1970, .month = 1, .day = 2};
        return DateTimeToUnixNs(epoch) == 0 && DateTimeToUnixNs(day1) == 86400ull * NS_PER_SEC;
    }
    
    static bool test_compare(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        DateTime b = DateTimeFromUnixNs((T_2021 + 1) * NS_PER_SEC, 0);
        // Same instant rendered in two zones compares equal.
    static bool test_compare(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        DateTime b = DateTimeFromUnixNs((T_2021 + 1) * NS_PER_SEC, 0);
        // Same instant rendered in two zones compares equal.
        DateTime c = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST);
        DateTime b = DateTimeFromUnixNs((T_2021 + 1) * NS_PER_SEC, 0);
        // Same instant rendered in two zones compares equal.
        DateTime c = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST);
        return DateTimeCompare(a, b) == -1 && DateTimeCompare(b, a) == 1 && DateTimeCompare(a, c) == 0;
    }
    
    static bool test_diff(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        DateTime b = DateTimeFromUnixNs((T_2021 + 90) * NS_PER_SEC, 0);
        return DateTimeDiffNs(b, a) == 90ll * (i64)NS_PER_SEC && DateTimeDiffNs(a, b) == -90ll * (i64)NS_PER_SEC;
    static bool test_diff(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        DateTime b = DateTimeFromUnixNs((T_2021 + 90) * NS_PER_SEC, 0);
        return DateTimeDiffNs(b, a) == 90ll * (i64)NS_PER_SEC && DateTimeDiffNs(a, b) == -90ll * (i64)NS_PER_SEC;
    }
    
    static bool test_add(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST);
        DateTime b = DateTimeAddNs(a, 86400ll * (i64)NS_PER_SEC); // +1 day
        return b.day == 2 && b.month == 1 && b.year == 2021 && b.utc_offset_seconds == IST &&
    static bool test_add(void) {
        DateTime a = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST);
        DateTime b = DateTimeAddNs(a, 86400ll * (i64)NS_PER_SEC); // +1 day
        return b.day == 2 && b.month == 1 && b.year == 2021 && b.utc_offset_seconds == IST &&
               DateTimeDiffNs(b, a) == 86400ll * (i64)NS_PER_SEC;
    
    static bool test_year_day(void) {
        DateTime jan1   = {.year = 2024, .month = 1, .day = 1};
        DateTime mar1   = {.year = 2024, .month = 3, .day = 1}; // leap: 31+29+1
        DateTime dec31l = {.year = 2024, .month = 12, .day = 31};
    static bool test_year_day(void) {
        DateTime jan1   = {.year = 2024, .month = 1, .day = 1};
        DateTime mar1   = {.year = 2024, .month = 3, .day = 1}; // leap: 31+29+1
        DateTime dec31l = {.year = 2024, .month = 12, .day = 31};
        DateTime dec31  = {.year = 2023, .month = 12, .day = 31};
        DateTime jan1   = {.year = 2024, .month = 1, .day = 1};
        DateTime mar1   = {.year = 2024, .month = 3, .day = 1}; // leap: 31+29+1
        DateTime dec31l = {.year = 2024, .month = 12, .day = 31};
        DateTime dec31  = {.year = 2023, .month = 12, .day = 31};
        return DateTimeYearDay(jan1) == 1 && DateTimeYearDay(mar1) == 61 && DateTimeYearDay(dec31l) == 366 &&
        DateTime mar1   = {.year = 2024, .month = 3, .day = 1}; // leap: 31+29+1
        DateTime dec31l = {.year = 2024, .month = 12, .day = 31};
        DateTime dec31  = {.year = 2023, .month = 12, .day = 31};
        return DateTimeYearDay(jan1) == 1 && DateTimeYearDay(mar1) == 61 && DateTimeYearDay(dec31l) == 366 &&
               DateTimeYearDay(dec31) == 365;
        for (u32 yi = 0; yi < sizeof(years) / sizeof(years[0]); ++yi) {
            for (u32 m = 1; m <= 12; ++m) {
                DateTime d = {.year = years[yi], .month = (u8)m, .day = 15, .hour = 13, .minute = 7, .second = 5};
                DateTime r = DateTimeFromUnixNs(DateTimeToUnixNs(d), 0);
                if (r.year != years[yi] || r.month != (u8)m || r.day != 15 || r.hour != 13 || r.minute != 7 ||
            for (u32 m = 1; m <= 12; ++m) {
                DateTime d = {.year = years[yi], .month = (u8)m, .day = 15, .hour = 13, .minute = 7, .second = 5};
                DateTime r = DateTimeFromUnixNs(DateTimeToUnixNs(d), 0);
                if (r.year != years[yi] || r.month != (u8)m || r.day != 15 || r.hour != 13 || r.minute != 7 ||
                    r.second != 5) {
    
    static bool test_leap_day_roundtrip(void) {
        DateTime feb29 = {.year = 2024, .month = 2, .day = 29, .hour = 23, .minute = 59, .second = 59};
        DateTime r     = DateTimeFromUnixNs(DateTimeToUnixNs(feb29), 0);
        return r.year == 2024 && r.month == 2 && r.day == 29 && r.hour == 23 && r.minute == 59 && r.second == 59;
    static bool test_leap_day_roundtrip(void) {
        DateTime feb29 = {.year = 2024, .month = 2, .day = 29, .hour = 23, .minute = 59, .second = 59};
        DateTime r     = DateTimeFromUnixNs(DateTimeToUnixNs(feb29), 0);
        return r.year == 2024 && r.month == 2 && r.day == 29 && r.hour == 23 && r.minute == 59 && r.second == 59;
    }
    static bool test_year_boundaries_sweep(void) {
        for (i32 y = 1970; y <= 2550; ++y) {
            DateTime jan1  = {.year = y, .month = 1, .day = 1};
            DateTime dec31 = {.year = y, .month = 12, .day = 31};
            DateTime rj    = DateTimeFromUnixNs(DateTimeToUnixNs(jan1), 0);
        for (i32 y = 1970; y <= 2550; ++y) {
            DateTime jan1  = {.year = y, .month = 1, .day = 1};
            DateTime dec31 = {.year = y, .month = 12, .day = 31};
            DateTime rj    = DateTimeFromUnixNs(DateTimeToUnixNs(jan1), 0);
            DateTime rd    = DateTimeFromUnixNs(DateTimeToUnixNs(dec31), 0);
            DateTime jan1  = {.year = y, .month = 1, .day = 1};
            DateTime dec31 = {.year = y, .month = 12, .day = 31};
            DateTime rj    = DateTimeFromUnixNs(DateTimeToUnixNs(jan1), 0);
            DateTime rd    = DateTimeFromUnixNs(DateTimeToUnixNs(dec31), 0);
            if (rj.year != y || rj.month != 1 || rj.day != 1) {
            DateTime dec31 = {.year = y, .month = 12, .day = 31};
            DateTime rj    = DateTimeFromUnixNs(DateTimeToUnixNs(jan1), 0);
            DateTime rd    = DateTimeFromUnixNs(DateTimeToUnixNs(dec31), 0);
            if (rj.year != y || rj.month != 1 || rj.day != 1) {
                return false;
        // Exact century / 400-cycle leap boundaries (the era's last day,
        // doe==146096, and the leap Feb 29s) pin the remaining correction term.
        DateTime exact[] = {
            {.year = 2000,  .month = 2, .day = 29},
            {.year = 2100,  .month = 2, .day = 28},
        };
        for (u32 i = 0; i < sizeof(exact) / sizeof(exact[0]); ++i) {
            DateTime r = DateTimeFromUnixNs(DateTimeToUnixNs(exact[i]), 0);
            if (r.year != exact[i].year || r.month != exact[i].month || r.day != exact[i].day) {
                return false;
        DefaultAllocator alloc = DefaultAllocatorInit();
        Str              s     = StrInit(&alloc);
        DateTime         d     = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        StrAppendFmt(&s, "{}", d);
        bool ok = ZstrCompare(StrBegin(&s), "2021-01-01T00:00:00Z") == 0;
        DefaultAllocator alloc = DefaultAllocatorInit();
        Str              s     = StrInit(&alloc);
        DateTime         d     = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST);
        StrAppendFmt(&s, "{}", d);
        bool ok = ZstrCompare(StrBegin(&s), "2021-01-01T05:30:00+05:30") == 0;
        DefaultAllocator alloc = DefaultAllocatorInit();
        Str              s     = StrInit(&alloc);
        DateTime         d     = DateTimeFromUnixNs(T_2021 * NS_PER_SEC + 123456789ull, 0);
        StrAppendFmt(&s, "{}", d);
        bool ok = ZstrCompare(StrBegin(&s), "2021-01-01T00:00:00.123456789Z") == 0;
        DefaultAllocator alloc = DefaultAllocatorInit();
        Str              s     = StrInit(&alloc);
        DateTime         a     = DateTimeFromUnixNs(T_2021 * NS_PER_SEC + 123456789ull, MST);
        StrAppendFmt(&s, "{}", a);
        StrAppendFmt(&s, "{}", a);
    
        DateTime b = {0};
        Zstr     p = StrBegin(&s);
        StrReadFmt(p, "{}", b);
    static bool test_iso_read_direct(void) {
        Zstr     in = "2026-06-26T17:34:11-07:00";
        DateTime d  = {0};
        StrReadFmt(in, "{}", d);
        return d.year == 2026 && d.month == 6 && d.day == 26 && d.hour == 17 && d.minute == 34 && d.second == 11 &&
            test_iso_read_direct,
        };
        return run_test_suite(tests, sizeof(tests) / sizeof(tests[0]), NULL, 0, "DateTime");
    }
    
    static bool test_epoch_zero(void) {
        DateTime d = DateTimeFromUnixNs(0, 0);
        return d.year == 1970 && d.month == 1 && d.day == 1 && d.hour == 0 && d.minute == 0 && d.second == 0 &&
               d.weekday == 4 /* Thursday */ && d.nanosecond == 0 && d.utc_offset_seconds == 0;
    
    static bool test_known_utc_instant(void) {
        DateTime d = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, 0);
        return d.year == 2021 && d.month == 1 && d.day == 1 && d.hour == 0 && d.minute == 0 && d.second == 0 &&
               d.weekday == 5 /* Friday */;
    
    static bool test_offset_shifts_wall_fields(void) {
        DateTime d = DateTimeFromUnixNs(T_2021 * NS_PER_SEC, IST_OFFSET);
        return d.year == 2021 && d.month == 1 && d.day == 1 && d.hour == 5 && d.minute == 30 && d.second == 0 &&
               d.weekday == 5 && d.utc_offset_seconds == IST_OFFSET;
    
    static bool test_subsecond_preserved(void) {
        DateTime d = DateTimeFromUnixNs(T_2021 * NS_PER_SEC + 123456789ull, 0);
        return d.second == 0 && d.nanosecond == 123456789;
    }
    
    static bool test_clock_utc_is_utc(void) {
        DateTime d = ClockUtc();
        return d.utc_offset_seconds == 0 && d.year >= 2024 && d.month >= 1 && d.month <= 12;
    }
    // timezone resolves (it falls back to UTC, offset 0).
    static bool test_clock_local_sane(void) {
        DateTime d = ClockLocal();
        return d.year >= 2024 && d.month >= 1 && d.month <= 12 && d.day >= 1 && d.day <= 31 && d.hour <= 23 &&
               d.minute <= 59;
Last updated on