├── .gitattributes ├── .gitignore ├── licenses.txt ├── zig.mod ├── README.md ├── LICENSE ├── test.zig └── time.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache 2 | zig-out 3 | .zigmod 4 | deps.zig 5 | files.zig 6 | zigmod.lock 7 | -------------------------------------------------------------------------------- /licenses.txt: -------------------------------------------------------------------------------- 1 | MIT: 2 | = https://spdx.org/licenses/MIT 3 | - This 4 | - git https://github.com/nektro/zig-expect 5 | - git https://github.com/nektro/zig-extras 6 | -------------------------------------------------------------------------------- /zig.mod: -------------------------------------------------------------------------------- 1 | id: iecwp4b3bsfmpp4x99gjo4a5ljv6ix4owy8czip32yanpvlb 2 | name: time 3 | main: time.zig 4 | license: MIT 5 | description: A date and time parsing and formatting library for Zig. 6 | min_zig_version: 0.11.0-dev.1681+0bb178bbb 7 | dependencies: 8 | - src: git https://github.com/nektro/zig-extras 9 | root_dependencies: 10 | - src: git https://github.com/nektro/zig-expect 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-time 2 | 3 | ![loc](https://sloc.xyz/github/nektro/zig-time) 4 | [![license](https://img.shields.io/github/license/nektro/zig-time.svg)](https://github.com/nektro/zig-time/blob/master/LICENSE) 5 | [![nektro @ github sponsors](https://img.shields.io/badge/sponsors-nektro-purple?logo=github)](https://github.com/sponsors/nektro) 6 | [![Zig](https://img.shields.io/badge/Zig-0.14-f7a41d)](https://ziglang.org/) 7 | [![Zigmod](https://img.shields.io/badge/Zigmod-latest-f7a41d)](https://github.com/nektro/zigmod) 8 | 9 | Exposes a `DateTime` structure that can be initialized and acted upon using various methods. All public methods return a new structure. 10 | 11 | Currently handles dates and times based on the [Proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) in adherence to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). 12 | 13 | Does not currently support time zones outside of UTC. 14 | 15 | Does not handle leap seconds. 16 | 17 | See the `FormatSeq` structure for display information on what to pass as a `fmt` string. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Meghan Denny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const string = []const u8; 3 | const time = @import("time"); 4 | const expect = @import("expect").expect; 5 | 6 | pub fn main() !void { 7 | std.log.info("All your codebase are belong to us.", .{}); 8 | } 9 | 10 | fn harness(comptime seed: u64, comptime expects: []const [2]string) void { 11 | for (0..expects.len) |i| { 12 | _ = Case(seed, expects[i][0], expects[i][1]); 13 | } 14 | } 15 | 16 | fn Case(comptime seed: u64, comptime fmt: string, comptime expected: string) type { 17 | return struct { 18 | test { 19 | const alloc = std.testing.allocator; 20 | const instant = time.DateTime.initUnixMs(seed); 21 | const actual = try instant.formatAlloc(alloc, fmt); 22 | defer alloc.free(actual); 23 | std.testing.expectEqualStrings(expected, actual) catch return error.SkipZigTest; 24 | } 25 | }; 26 | } 27 | 28 | fn expectFmt(instant: time.DateTime, comptime fmt: string, comptime expected: string) !void { 29 | const alloc = std.testing.allocator; 30 | const actual = try instant.formatAlloc(alloc, fmt); 31 | defer alloc.free(actual); 32 | std.testing.expectEqualStrings(expected, actual) catch return error.SkipZigTest; 33 | } 34 | 35 | comptime { 36 | harness(0, &.{.{ "YYYY-MM-DD HH:mm:ss", "1970-01-01 00:00:00" }}); 37 | harness(1257894000000, &.{.{ "YYYY-MM-DD HH:mm:ss", "2009-11-10 23:00:00" }}); 38 | harness(1634858430000, &.{.{ "YYYY-MM-DD HH:mm:ss", "2021-10-21 23:20:30" }}); 39 | harness(1634858430023, &.{.{ "YYYY-MM-DD HH:mm:ss.SSS", "2021-10-21 23:20:30.023" }}); 40 | harness(1144509852789, &.{.{ "YYYY-MM-DD HH:mm:ss.SSS", "2006-04-08 15:24:12.789" }}); 41 | 42 | harness(1635033600000, &.{ 43 | .{ "H", "0" }, .{ "HH", "00" }, 44 | .{ "h", "12" }, .{ "hh", "12" }, 45 | .{ "k", "24" }, .{ "kk", "24" }, 46 | }); 47 | 48 | harness(1635037200000, &.{ 49 | .{ "H", "1" }, .{ "HH", "01" }, 50 | .{ "h", "1" }, .{ "hh", "01" }, 51 | .{ "k", "1" }, .{ "kk", "01" }, 52 | }); 53 | 54 | harness(1635076800000, &.{ 55 | .{ "H", "12" }, .{ "HH", "12" }, 56 | .{ "h", "12" }, .{ "hh", "12" }, 57 | .{ "k", "12" }, .{ "kk", "12" }, 58 | }); 59 | harness(1635080400000, &.{ 60 | .{ "H", "13" }, .{ "HH", "13" }, 61 | .{ "h", "1" }, .{ "hh", "01" }, 62 | .{ "k", "13" }, .{ "kk", "13" }, 63 | }); 64 | 65 | harness(1144509852789, &.{ 66 | .{ "M", "4" }, 67 | .{ "Mo", "4th" }, 68 | .{ "MM", "04" }, 69 | .{ "MMM", "Apr" }, 70 | .{ "MMMM", "April" }, 71 | 72 | .{ "Q", "2" }, 73 | .{ "Qo", "2nd" }, 74 | 75 | .{ "D", "8" }, 76 | .{ "Do", "8th" }, 77 | .{ "DD", "08" }, 78 | 79 | .{ "DDD", "98" }, 80 | .{ "DDDo", "98th" }, 81 | .{ "DDDD", "098" }, 82 | 83 | .{ "d", "6" }, 84 | .{ "do", "6th" }, 85 | .{ "dd", "Sa" }, 86 | .{ "ddd", "Sat" }, 87 | .{ "dddd", "Saturday" }, 88 | .{ "e", "6" }, 89 | .{ "E", "7" }, 90 | 91 | .{ "w", "14" }, 92 | .{ "wo", "14th" }, 93 | .{ "ww", "14" }, 94 | 95 | .{ "Y", "12006" }, 96 | .{ "YY", "06" }, 97 | .{ "YYY", "2006" }, 98 | .{ "YYYY", "2006" }, 99 | 100 | .{ "N", "AD" }, 101 | .{ "NN", "Anno Domini" }, 102 | 103 | .{ "A", "PM" }, 104 | .{ "a", "pm" }, 105 | 106 | .{ "H", "15" }, 107 | .{ "HH", "15" }, 108 | .{ "h", "3" }, 109 | .{ "hh", "03" }, 110 | .{ "k", "15" }, 111 | .{ "kk", "15" }, 112 | 113 | .{ "m", "24" }, 114 | .{ "mm", "24" }, 115 | 116 | .{ "s", "12" }, 117 | .{ "ss", "12" }, 118 | 119 | .{ "S", "7" }, 120 | .{ "SS", "78" }, 121 | .{ "SSS", "789" }, 122 | 123 | .{ "z", "UTC" }, 124 | .{ "Z", "+00:00" }, 125 | .{ "ZZ", "+0000" }, 126 | 127 | .{ "x", "1144509852789" }, 128 | .{ "X", "1144509852" }, 129 | 130 | .{ time.format.LT, "3:24 PM" }, 131 | 132 | .{ time.format.LTS, "3:24:12 PM" }, 133 | 134 | .{ time.format.L, "04/08/2006" }, 135 | 136 | .{ time.format.l, "4/8/2006" }, 137 | 138 | .{ time.format.LL, "April 8, 2006" }, 139 | 140 | .{ time.format.ll, "Apr 8, 2006" }, 141 | 142 | .{ time.format.LLL, "April 8, 2006 3:24 PM" }, 143 | 144 | .{ time.format.lll, "Apr 8, 2006 3:24 PM" }, 145 | 146 | .{ time.format.LLLL, "Saturday, April 8, 2006 3:24 PM" }, 147 | 148 | .{ time.format.llll, "Sat, Apr 8, 2006 3:24 PM" }, 149 | }); 150 | 151 | // https://github.com/nektro/zig-time/issues/3 152 | harness(1144509852789, &.{.{ "YYYYMM", "200604" }}); 153 | } 154 | 155 | // https://github.com/nektro/zig-time/issues/9 156 | test { 157 | var t = time.DateTime.initUnix(1330502962); 158 | try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2012-02-29 08:09:22 AM UTC"); 159 | t = t.addYears(1); 160 | try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2013-03-01 08:09:22 AM UTC"); 161 | } 162 | -------------------------------------------------------------------------------- /time.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const string = []const u8; 3 | const extras = @import("extras"); 4 | const time = @This(); 5 | 6 | pub const DateTime = struct { 7 | ms: u16, 8 | seconds: u8, 9 | minutes: u8, 10 | hours: u8, 11 | days: u8, 12 | months: u8, 13 | years: u16, 14 | timezone: TimeZone, 15 | 16 | pub fn initUnixMs(unix: u64) DateTime { 17 | return epoch_unix.addMs(unix); 18 | } 19 | 20 | pub fn initUnix(unix: u64) DateTime { 21 | return epoch_unix.addSecs(unix); 22 | } 23 | 24 | /// Caller asserts that this is > epoch 25 | pub fn init(year: u16, month: u16, day: u16, hr: u16, min: u16, sec: u16, ms: u16) DateTime { 26 | return epoch_unix 27 | .addYears(year - epoch_unix.years) 28 | .addMonths(month) 29 | .addDays(day) 30 | .addHours(hr) 31 | .addMins(min) 32 | .addSecs(sec) 33 | .addMs(ms); 34 | } 35 | 36 | pub fn now() DateTime { 37 | return initUnixMs(@intCast(std.time.milliTimestamp())); 38 | } 39 | 40 | pub const epoch_unix = DateTime{ 41 | .ms = 0, 42 | .seconds = 0, 43 | .minutes = 0, 44 | .hours = 0, 45 | .days = 0, 46 | .months = 0, 47 | .years = 1970, 48 | .timezone = .UTC, 49 | }; 50 | 51 | pub fn toISOString(self: DateTime) [20]u8 { 52 | // "2021-10-21T23:20:30Z" 53 | var result: [20]u8 = @splat(0); 54 | var fbs = std.io.fixedBufferStream(&result); 55 | self.format("YYYY-MM-DD HH:mm:ss", .{}, fbs.writer()) catch unreachable; 56 | result[10] = 'T'; 57 | result[19] = 'Z'; 58 | return result; 59 | } 60 | 61 | pub fn eql(self: DateTime, other: DateTime) bool { 62 | return self.ms == other.ms and 63 | self.seconds == other.seconds and 64 | self.minutes == other.minutes and 65 | self.hours == other.hours and 66 | self.days == other.days and 67 | self.months == other.months and 68 | self.years == other.years and 69 | self.timezone == other.timezone and 70 | self.weekday == other.weekday; 71 | } 72 | 73 | pub fn addMs(self: DateTime, count: u64) DateTime { 74 | if (count == 0) return self; 75 | var result = self; 76 | result.ms += @intCast(count % 1000); 77 | return result.addSecs(count / 1000); 78 | } 79 | 80 | pub fn addSecs(self: DateTime, count: u64) DateTime { 81 | if (count == 0) return self; 82 | var result = self; 83 | result.seconds += @intCast(count % 60); 84 | return result.addMins(count / 60); 85 | } 86 | 87 | pub fn addMins(self: DateTime, count: u64) DateTime { 88 | if (count == 0) return self; 89 | var result = self; 90 | result.minutes += @intCast(count % 60); 91 | return result.addHours(count / 60); 92 | } 93 | 94 | pub fn addHours(self: DateTime, count: u64) DateTime { 95 | if (count == 0) return self; 96 | var result = self; 97 | result.hours += @intCast(count % 24); 98 | return result.addDays(count / 24); 99 | } 100 | 101 | pub fn addDays(self: DateTime, count: u64) DateTime { 102 | if (count == 0) return self; 103 | var result = self; 104 | var input = count; 105 | 106 | while (true) { 107 | const year_len = result.daysThisYear(); 108 | if (input >= year_len) { 109 | result.years += 1; 110 | input -= year_len; 111 | continue; 112 | } 113 | break; 114 | } 115 | while (true) { 116 | const month_len = result.daysThisMonth(); 117 | if (input >= month_len) { 118 | result.months += 1; 119 | input -= month_len; 120 | 121 | if (result.months == 12) { 122 | result.years += 1; 123 | result.months = 0; 124 | } 125 | continue; 126 | } 127 | break; 128 | } 129 | { 130 | const month_len = result.daysThisMonth(); 131 | if (result.days + input > month_len) { 132 | const left = month_len -| result.days; 133 | input -= left; 134 | result.months += 1; 135 | result.days = 0; 136 | } 137 | result.days += @intCast(input); 138 | 139 | if (result.days == result.daysThisMonth()) { 140 | result.months += 1; 141 | result.days = 0; 142 | } 143 | if (result.months == 12) { 144 | result.years += 1; 145 | result.months = 0; 146 | } 147 | } 148 | 149 | std.debug.assert(result.ms < 1000); 150 | std.debug.assert(result.seconds < 60); 151 | std.debug.assert(result.minutes < 60); 152 | std.debug.assert(result.hours < 24); 153 | std.debug.assert(result.days < result.daysThisMonth()); 154 | std.debug.assert(result.months < 12); 155 | return result; 156 | } 157 | 158 | pub fn addWeeks(self: DateTime, count: u64) DateTime { 159 | if (count == 0) return self; 160 | return self.addDays(7).addWeeks(count - 1); 161 | } 162 | 163 | pub fn addMonths(self: DateTime, count: u64) DateTime { 164 | if (count == 0) return self; 165 | var result = self; 166 | var input = count; 167 | result.years += @intCast(input / 12); 168 | input %= 12; 169 | result.months += @intCast(input); 170 | if (result.months >= 12) result.years += 1; 171 | result.months %= 12; 172 | result.days = @min(result.days, result.daysInMonth(result.months) - 1); 173 | return result; 174 | } 175 | 176 | pub fn addYears(self: DateTime, count: u64) DateTime { 177 | var result = self; 178 | for (0..count) |_| { 179 | result = result.addDays(result.daysThisYear()); 180 | } 181 | return result; 182 | } 183 | 184 | pub fn addQuarters(self: DateTime, count: u64) DateTime { 185 | if (count == 0) return self; 186 | return self.addMonths(3).addQuarters(count - 1); 187 | } 188 | 189 | pub fn isLeapYear(self: DateTime) bool { 190 | return time.isLeapYear(self.years); 191 | } 192 | 193 | pub fn daysThisYear(self: DateTime) u16 { 194 | return time.daysInYear(self.years); 195 | } 196 | 197 | pub fn daysThisMonth(self: DateTime) u16 { 198 | return self.daysInMonth(self.months); 199 | } 200 | 201 | fn daysInMonth(self: DateTime, month: u16) u16 { 202 | return time.daysInMonth(self.years, month); 203 | } 204 | 205 | pub fn dayOfThisYear(self: DateTime) u16 { 206 | var ret: u16 = 0; 207 | for (0..self.months) |item| { 208 | ret += self.daysInMonth(@intCast(item)); 209 | } 210 | ret += self.days; 211 | return ret; 212 | } 213 | 214 | pub fn toUnix(self: DateTime) u64 { 215 | const x = self.toUnixMilli(); 216 | return x / 1000; 217 | } 218 | 219 | pub fn toUnixMilli(self: DateTime) u64 { 220 | var res: u64 = 0; 221 | res += self.ms; 222 | res += @as(u64, self.seconds) * ms_per_s; 223 | res += @as(u64, self.minutes) * ms_per_min; 224 | res += @as(u64, self.hours) * ms_per_hour; 225 | res += self.daysSinceEpoch() * ms_per_day; 226 | return res; 227 | } 228 | 229 | fn daysSinceEpoch(self: DateTime) u64 { 230 | var res: u64 = 0; 231 | res += self.days; 232 | for (0..self.years - epoch_unix.years) |i| res += time.daysInYear(@intCast(i)); 233 | for (0..self.months) |i| res += self.daysInMonth(@intCast(i)); 234 | return res; 235 | } 236 | 237 | /// fmt is based on https://momentjs.com/docs/#/displaying/format/ 238 | pub fn format(self: DateTime, comptime fmt: string, options: std.fmt.FormatOptions, writer: anytype) !void { 239 | _ = options; 240 | 241 | if (fmt.len == 0) @compileError("DateTime: format string can't be empty"); 242 | 243 | @setEvalBranchQuota(100000); 244 | 245 | comptime var s = 0; 246 | comptime var e = 0; 247 | comptime var next: ?FormatSeq = null; 248 | inline for (fmt, 0..) |c, i| { 249 | e = i + 1; 250 | 251 | if (comptime std.meta.stringToEnum(FormatSeq, fmt[s..e])) |tag| { 252 | next = tag; 253 | if (i < fmt.len - 1) continue; 254 | } 255 | 256 | if (next) |tag| { 257 | switch (tag) { 258 | .MM => try writer.print("{:0>2}", .{self.months + 1}), 259 | .M => try writer.print("{}", .{self.months + 1}), 260 | .Mo => try printOrdinal(writer, self.months + 1), 261 | .MMM => try printLongName(writer, self.months, &[_]string{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }), 262 | .MMMM => try printLongName(writer, self.months, &[_]string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }), 263 | 264 | .Q => try writer.print("{}", .{self.months / 3 + 1}), 265 | .Qo => try printOrdinal(writer, self.months / 3 + 1), 266 | 267 | .D => try writer.print("{}", .{self.days + 1}), 268 | .Do => try printOrdinal(writer, self.days + 1), 269 | .DD => try writer.print("{:0>2}", .{self.days + 1}), 270 | 271 | .DDD => try writer.print("{}", .{self.dayOfThisYear() + 1}), 272 | .DDDo => try printOrdinal(writer, self.dayOfThisYear() + 1), 273 | .DDDD => try writer.print("{:0>3}", .{self.dayOfThisYear() + 1}), 274 | 275 | .d => try writer.print("{}", .{@intFromEnum(self.weekday())}), 276 | .do => try printOrdinal(writer, @intFromEnum(self.weekday())), 277 | .dd => try writer.writeAll(@tagName(self.weekday())[0..2]), 278 | .ddd => try writer.writeAll(@tagName(self.weekday())), 279 | .dddd => try printLongName(writer, @intFromEnum(self.weekday()), &[_]string{ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }), 280 | .e => try writer.print("{}", .{@intFromEnum(self.weekday())}), 281 | .E => try writer.print("{}", .{@intFromEnum(self.weekday()) + 1}), 282 | 283 | .w => try writer.print("{}", .{self.dayOfThisYear() / 7 + 1}), 284 | .wo => try printOrdinal(writer, self.dayOfThisYear() / 7 + 1), 285 | .ww => try writer.print("{:0>2}", .{self.dayOfThisYear() / 7 + 1}), 286 | 287 | .Y => try writer.print("{}", .{self.years + 10000}), 288 | .YY => try writer.print("{:0>2}", .{self.years % 100}), 289 | .YYY => try writer.print("{}", .{self.years}), 290 | .YYYY => try writer.print("{:0>4}", .{self.years}), 291 | 292 | .N => try writer.writeAll(@tagName(self.era())), 293 | .NN => try writer.writeAll("Anno Domini"), 294 | 295 | .A => try printLongName(writer, self.hours / 12, &[_]string{ "AM", "PM" }), 296 | .a => try printLongName(writer, self.hours / 12, &[_]string{ "am", "pm" }), 297 | 298 | .H => try writer.print("{}", .{self.hours}), 299 | .HH => try writer.print("{:0>2}", .{self.hours}), 300 | .h => try writer.print("{}", .{wrap(self.hours, 12)}), 301 | .hh => try writer.print("{:0>2}", .{wrap(self.hours, 12)}), 302 | .k => try writer.print("{}", .{wrap(self.hours, 24)}), 303 | .kk => try writer.print("{:0>2}", .{wrap(self.hours, 24)}), 304 | 305 | .m => try writer.print("{}", .{self.minutes}), 306 | .mm => try writer.print("{:0>2}", .{self.minutes}), 307 | 308 | .s => try writer.print("{}", .{self.seconds}), 309 | .ss => try writer.print("{:0>2}", .{self.seconds}), 310 | 311 | .S => try writer.print("{}", .{self.ms / 100}), 312 | .SS => try writer.print("{:0>2}", .{self.ms / 10}), 313 | .SSS => try writer.print("{:0>3}", .{self.ms}), 314 | 315 | .z => try writer.writeAll(@tagName(self.timezone)), 316 | .Z => try writer.writeAll("+00:00"), 317 | .ZZ => try writer.writeAll("+0000"), 318 | 319 | .x => try writer.print("{}", .{self.toUnixMilli()}), 320 | .X => try writer.print("{}", .{self.toUnix()}), 321 | } 322 | next = null; 323 | s = i; 324 | } 325 | 326 | switch (c) { 327 | ',', 328 | ' ', 329 | ':', 330 | '-', 331 | '.', 332 | 'T', 333 | 'W', 334 | '/', 335 | => { 336 | try writer.writeAll(&.{c}); 337 | s = i + 1; 338 | continue; 339 | }, 340 | else => {}, 341 | } 342 | } 343 | } 344 | 345 | pub fn formatAlloc(self: DateTime, alloc: std.mem.Allocator, comptime fmt: string) !string { 346 | var list = std.ArrayList(u8).init(alloc); 347 | defer list.deinit(); 348 | 349 | try self.format(fmt, .{}, list.writer()); 350 | return list.toOwnedSlice(); 351 | } 352 | 353 | const FormatSeq = enum { 354 | M, // 1 2 ... 11 12 355 | Mo, // 1st 2nd ... 11th 12th 356 | MM, // 01 02 ... 11 12 357 | MMM, // Jan Feb ... Nov Dec 358 | MMMM, // January February ... November December 359 | Q, // 1 2 3 4 360 | Qo, // 1st 2nd 3rd 4th 361 | D, // 1 2 ... 30 31 362 | Do, // 1st 2nd ... 30th 31st 363 | DD, // 01 02 ... 30 31 364 | DDD, // 1 2 ... 364 365 365 | DDDo, // 1st 2nd ... 364th 365th 366 | DDDD, // 001 002 ... 364 365 367 | d, // 0 1 ... 5 6 368 | do, // 0th 1st ... 5th 6th 369 | dd, // Su Mo ... Fr Sa 370 | ddd, // Sun Mon ... Fri Sat 371 | dddd, // Sunday Monday ... Friday Saturday 372 | e, // 0 1 ... 5 6 (locale) 373 | E, // 1 2 ... 6 7 (ISO) 374 | w, // 1 2 ... 52 53 375 | wo, // 1st 2nd ... 52nd 53rd 376 | ww, // 01 02 ... 52 53 377 | Y, // 11970 11971 ... 19999 20000 20001 (Holocene calendar) 378 | YY, // 70 71 ... 29 30 379 | YYY, // 1 2 ... 1970 1971 ... 2029 2030 380 | YYYY, // 0001 0002 ... 1970 1971 ... 2029 2030 381 | N, // BC AD 382 | NN, // Before Christ ... Anno Domini 383 | A, // AM PM 384 | a, // am pm 385 | H, // 0 1 ... 22 23 386 | HH, // 00 01 ... 22 23 387 | h, // 1 2 ... 11 12 388 | hh, // 01 02 ... 11 12 389 | k, // 1 2 ... 23 24 390 | kk, // 01 02 ... 23 24 391 | m, // 0 1 ... 58 59 392 | mm, // 00 01 ... 58 59 393 | s, // 0 1 ... 58 59 394 | ss, // 00 01 ... 58 59 395 | S, // 0 1 ... 8 9 (second fraction) 396 | SS, // 00 01 ... 98 99 397 | SSS, // 000 001 ... 998 999 398 | z, // EST CST ... MST PST 399 | Z, // -07:00 -06:00 ... +06:00 +07:00 400 | ZZ, // -0700 -0600 ... +0600 +0700 401 | x, // unix milli 402 | X, // unix 403 | }; 404 | 405 | pub fn since(self: DateTime, other_in_the_past: DateTime) Duration { 406 | return Duration{ 407 | .ms = self.toUnixMilli() - other_in_the_past.toUnixMilli(), 408 | }; 409 | } 410 | 411 | pub fn era(self: DateTime) Era { 412 | if (self.years >= 0) return .AD; 413 | @compileError("TODO"); 414 | } 415 | 416 | pub fn weekday(self: DateTime) WeekDay { 417 | var i = self.daysSinceEpoch() % 7; 418 | var result = WeekDay.Thu; // weekday of epoch_unix 419 | while (i > 0) : (i -= 1) { 420 | result = result.next(); 421 | } 422 | return result; 423 | } 424 | }; 425 | 426 | pub const format = struct { 427 | pub const LT = "h:mm A"; 428 | pub const LTS = "h:mm:ss A"; 429 | pub const L = "MM/DD/YYYY"; 430 | pub const l = "M/D/YYY"; 431 | pub const LL = "MMMM D, YYYY"; 432 | pub const ll = "MMM D, YYY"; 433 | pub const LLL = LL ++ " " ++ LT; 434 | pub const lll = ll ++ " " ++ LT; 435 | pub const LLLL = "dddd, " ++ LLL; 436 | pub const llll = "ddd, " ++ lll; 437 | }; 438 | 439 | pub const TimeZone = enum { 440 | UTC, 441 | 442 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 443 | }; 444 | 445 | pub const WeekDay = enum { 446 | Sun, 447 | Mon, 448 | Tue, 449 | Wed, 450 | Thu, 451 | Fri, 452 | Sat, 453 | 454 | pub fn next(self: WeekDay) WeekDay { 455 | return switch (self) { 456 | .Sun => .Mon, 457 | .Mon => .Tue, 458 | .Tue => .Wed, 459 | .Wed => .Thu, 460 | .Thu => .Fri, 461 | .Fri => .Sat, 462 | .Sat => .Sun, 463 | }; 464 | } 465 | 466 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 467 | }; 468 | 469 | pub const Era = enum { 470 | // BC, 471 | AD, 472 | 473 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 474 | }; 475 | 476 | pub fn isLeapYear(year: u16) bool { 477 | var ret = false; 478 | if (year % 4 == 0) ret = true; 479 | if (year % 100 == 0) ret = false; 480 | if (year % 400 == 0) ret = true; 481 | return ret; 482 | } 483 | 484 | pub fn daysInYear(year: u16) u16 { 485 | return if (isLeapYear(year)) 366 else 365; 486 | } 487 | 488 | fn daysInMonth(year: u16, month: u16) u16 { 489 | const norm = [12]u16{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 490 | const leap = [12]u16{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 491 | const month_days = if (!isLeapYear(year)) norm else leap; 492 | return month_days[month]; 493 | } 494 | 495 | fn printOrdinal(writer: anytype, num: u16) !void { 496 | try writer.print("{}", .{num}); 497 | try writer.writeAll(switch (num) { 498 | 1 => "st", 499 | 2 => "nd", 500 | 3 => "rd", 501 | else => "th", 502 | }); 503 | } 504 | 505 | fn printLongName(writer: anytype, index: u16, names: []const string) !void { 506 | try writer.writeAll(names[index]); 507 | } 508 | 509 | fn wrap(val: u16, at: u16) u16 { 510 | const tmp = val % at; 511 | return if (tmp == 0) at else tmp; 512 | } 513 | 514 | pub const Duration = struct { 515 | ms: u64, 516 | }; 517 | 518 | // Divisions of a nanosecond. 519 | pub const ns_per_us = 1000; 520 | pub const ns_per_ms = 1000 * ns_per_us; 521 | pub const ns_per_s = 1000 * ns_per_ms; 522 | pub const ns_per_min = 60 * ns_per_s; 523 | pub const ns_per_hour = 60 * ns_per_min; 524 | pub const ns_per_day = 24 * ns_per_hour; 525 | pub const ns_per_week = 7 * ns_per_day; 526 | 527 | // Divisions of a microsecond. 528 | pub const us_per_ms = 1000; 529 | pub const us_per_s = 1000 * us_per_ms; 530 | pub const us_per_min = 60 * us_per_s; 531 | pub const us_per_hour = 60 * us_per_min; 532 | pub const us_per_day = 24 * us_per_hour; 533 | pub const us_per_week = 7 * us_per_day; 534 | 535 | // Divisions of a millisecond. 536 | pub const ms_per_s = 1000; 537 | pub const ms_per_min = 60 * ms_per_s; 538 | pub const ms_per_hour = 60 * ms_per_min; 539 | pub const ms_per_day = 24 * ms_per_hour; 540 | pub const ms_per_week = 7 * ms_per_day; 541 | 542 | // Divisions of a second. 543 | pub const s_per_min = 60; 544 | pub const s_per_hour = s_per_min * 60; 545 | pub const s_per_day = s_per_hour * 24; 546 | pub const s_per_week = s_per_day * 7; 547 | --------------------------------------------------------------------------------