aboutsummaryrefslogtreecommitdiff
path: root/src/time.zig
blob: 290c9de8fb9d8c5d04c276006e06ef39809ed837 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
const std = @import("std");

pub const Time = struct {
    year: u16,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,

    pub fn from_timestamp(timestamp: u64) Time {
        // Source: https://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
        // Copyright: https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT

        const leapoch = 946684800 + 86400 * (31 + 29); // 2000-03-01 (mod 400 year, immediately after feb29)
        const days_per_400y = 365 * 400 + 97;
        const days_per_100y = 365 * 100 + 24;
        const days_per_4y = 365 * 4 + 1;
        const days_in_month = [_]i64{ 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29 };

        var days: i64 = undefined;
        var remsecs: i64 = undefined;
        if (timestamp < leapoch) {
            const secs: i64 = @as(i64, @intCast(timestamp)) - leapoch;
            days = @divTrunc(secs, 86400);
            remsecs = @rem(secs, 86400);
        } else {
            const secs: u64 = timestamp - leapoch;
            days = @intCast(secs / 86400);
            remsecs = @intCast(@rem(secs, 86400));
        }
        if (remsecs < 0) {
            remsecs += 86400;
            days -= 1;
        }

        var qc_cycles = @divTrunc(days, days_per_400y);
        var remdays = @rem(days, days_per_400y);
        if (remdays < 0) {
            remdays += days_per_400y;
            qc_cycles = -1;
        }

        var c_cycles = @divTrunc(remdays, days_per_100y);
        if (c_cycles == 4) {
            c_cycles -= 1;
        }
        remdays -= c_cycles * days_per_100y;

        var q_cycles = @divTrunc(remdays, days_per_4y);
        if (q_cycles == 25) {
            q_cycles -= 1;
        }
        remdays -= q_cycles * days_per_4y;

        var remyears = @divTrunc(remdays, 365);
        if (remyears == 4) {
            remyears -= 1;
        }
        remdays -= remyears * 365;

        var years = remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
        var months: u64 = 0;
        while (remdays >= days_in_month[months]) {
            remdays -= days_in_month[months];
            months += 1;
        }

        if (months < 10) {
            months += 3;
        } else {
            months -= 9;
            years += 1;
        }

        return .{
            .year = @intCast(years + 2000),
            .month = @intCast(months),
            .day = @intCast(remdays + 1),
            .hour = @intCast(@divTrunc(remsecs, 3600)),
            .minute = @intCast(@rem(@divTrunc(remsecs, 60), 60)),
            .second = @intCast(@rem(remsecs, 60)),
        };
    }

    pub fn format(self: Time, w: *std.io.Writer) std.io.Writer.Error!void {
        try w.print(
            "{d:0>4}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2} UTC",
            .{ self.year, self.month, self.day, self.hour, self.minute, self.second },
        );
    }
};

pub fn now() u64 {
    const ts = std.posix.clock_gettime(.REALTIME) catch return 0;
    return if (ts.sec < 0) 0 else @intCast(ts.sec);
}

test "time from timestamp" {
    var t: Time = undefined;
    const leapoch = 946684800 + 86400 * (31 + 29);

    t = .from_timestamp(0);
    try std.testing.expectEqual(1970, t.year);
    try std.testing.expectEqual(1, t.month);
    try std.testing.expectEqual(1, t.day);
    try std.testing.expectEqual(0, t.hour);
    try std.testing.expectEqual(0, t.minute);
    try std.testing.expectEqual(0, t.second);

    t = .from_timestamp(leapoch - 1);
    try std.testing.expectEqual(2000, t.year);
    try std.testing.expectEqual(2, t.month);
    try std.testing.expectEqual(29, t.day);
    try std.testing.expectEqual(23, t.hour);
    try std.testing.expectEqual(59, t.minute);
    try std.testing.expectEqual(59, t.second);

    t = .from_timestamp(leapoch);
    try std.testing.expectEqual(2000, t.year);
    try std.testing.expectEqual(3, t.month);
    try std.testing.expectEqual(1, t.day);
    try std.testing.expectEqual(0, t.hour);
    try std.testing.expectEqual(0, t.minute);
    try std.testing.expectEqual(0, t.second);

    t = .from_timestamp(leapoch + 1);
    try std.testing.expectEqual(2000, t.year);
    try std.testing.expectEqual(3, t.month);
    try std.testing.expectEqual(1, t.day);
    try std.testing.expectEqual(0, t.hour);
    try std.testing.expectEqual(0, t.minute);
    try std.testing.expectEqual(1, t.second);
}