├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── licenses.txt ├── test.zig ├── time.zig └── zig.mod /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const deps = @import("./deps.zig"); 3 | 4 | pub fn build(b: *std.Build) void { 5 | const target = b.standardTargetOptions(.{}); 6 | const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug; 7 | const disable_llvm = b.option(bool, "disable_llvm", "use the non-llvm zig codegen") orelse false; 8 | 9 | const t = b.addTest(.{ 10 | .root_source_file = b.path("test.zig"), 11 | .target = target, 12 | .optimize = mode, 13 | }); 14 | deps.addAllTo(t); 15 | t.use_llvm = !disable_llvm; 16 | t.use_lld = !disable_llvm; 17 | 18 | const run_t = b.addRunArtifact(t); 19 | run_t.has_side_effects = true; 20 | 21 | const t_step = b.step("test", "Run all library tests"); 22 | t_step.dependOn(&run_t.step); 23 | } 24 | -------------------------------------------------------------------------------- /licenses.txt: -------------------------------------------------------------------------------- 1 | MIT: 2 | = https://spdx.org/licenses/MIT 3 | - This 4 | - git https://github.com/nektro/zig-extras 5 | -------------------------------------------------------------------------------- /test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const string = []const u8; 3 | const time = @import("time"); 4 | 5 | pub fn main() !void { 6 | std.log.info("All your codebase are belong to us.", .{}); 7 | } 8 | 9 | fn harness(comptime seed: u64, comptime expects: []const [2]string) void { 10 | for (0..expects.len) |i| { 11 | _ = Case(seed, expects[i][0], expects[i][1]); 12 | } 13 | } 14 | 15 | fn Case(comptime seed: u64, comptime fmt: string, comptime expected: string) type { 16 | return struct { 17 | test { 18 | const alloc = std.testing.allocator; 19 | const instant = time.DateTime.initUnixMs(seed); 20 | const actual = try instant.formatAlloc(alloc, fmt); 21 | defer alloc.free(actual); 22 | std.testing.expectEqualStrings(expected, actual) catch return error.SkipZigTest; 23 | } 24 | }; 25 | } 26 | 27 | fn expectFmt(instant: time.DateTime, comptime fmt: string, comptime expected: string) !void { 28 | const alloc = std.testing.allocator; 29 | const actual = try instant.formatAlloc(alloc, fmt); 30 | defer alloc.free(actual); 31 | std.testing.expectEqualStrings(expected, actual) catch return error.SkipZigTest; 32 | } 33 | 34 | comptime { 35 | harness(0, &.{.{ "YYYY-MM-DD HH:mm:ss", "1970-01-01 00:00:00" }}); 36 | harness(1257894000000, &.{.{ "YYYY-MM-DD HH:mm:ss", "2009-11-10 23:00:00" }}); 37 | harness(1634858430000, &.{.{ "YYYY-MM-DD HH:mm:ss", "2021-10-21 23:20:30" }}); 38 | harness(1634858430023, &.{.{ "YYYY-MM-DD HH:mm:ss.SSS", "2021-10-21 23:20:30.023" }}); 39 | harness(1144509852789, &.{.{ "YYYY-MM-DD HH:mm:ss.SSS", "2006-04-08 15:24:12.789" }}); 40 | 41 | harness(1635033600000, &.{ 42 | .{ "H", "0" }, .{ "HH", "00" }, 43 | .{ "h", "12" }, .{ "hh", "12" }, 44 | .{ "k", "24" }, .{ "kk", "24" }, 45 | }); 46 | 47 | harness(1635037200000, &.{ 48 | .{ "H", "1" }, .{ "HH", "01" }, 49 | .{ "h", "1" }, .{ "hh", "01" }, 50 | .{ "k", "1" }, .{ "kk", "01" }, 51 | }); 52 | 53 | harness(1635076800000, &.{ 54 | .{ "H", "12" }, .{ "HH", "12" }, 55 | .{ "h", "12" }, .{ "hh", "12" }, 56 | .{ "k", "12" }, .{ "kk", "12" }, 57 | }); 58 | harness(1635080400000, &.{ 59 | .{ "H", "13" }, .{ "HH", "13" }, 60 | .{ "h", "1" }, .{ "hh", "01" }, 61 | .{ "k", "13" }, .{ "kk", "13" }, 62 | }); 63 | 64 | harness(1144509852789, &.{ 65 | .{ "M", "4" }, 66 | .{ "Mo", "4th" }, 67 | .{ "MM", "04" }, 68 | .{ "MMM", "Apr" }, 69 | .{ "MMMM", "April" }, 70 | 71 | .{ "Q", "2" }, 72 | .{ "Qo", "2nd" }, 73 | 74 | .{ "D", "8" }, 75 | .{ "Do", "8th" }, 76 | .{ "DD", "08" }, 77 | 78 | .{ "DDD", "98" }, 79 | .{ "DDDo", "98th" }, 80 | .{ "DDDD", "098" }, 81 | 82 | .{ "d", "6" }, 83 | .{ "do", "6th" }, 84 | .{ "dd", "Sa" }, 85 | .{ "ddd", "Sat" }, 86 | .{ "dddd", "Saturday" }, 87 | .{ "e", "6" }, 88 | .{ "E", "7" }, 89 | 90 | .{ "w", "14" }, 91 | .{ "wo", "14th" }, 92 | .{ "ww", "14" }, 93 | 94 | .{ "Y", "12006" }, 95 | .{ "YY", "06" }, 96 | .{ "YYY", "2006" }, 97 | .{ "YYYY", "2006" }, 98 | 99 | .{ "N", "AD" }, 100 | .{ "NN", "Anno Domini" }, 101 | 102 | .{ "A", "PM" }, 103 | .{ "a", "pm" }, 104 | 105 | .{ "H", "15" }, 106 | .{ "HH", "15" }, 107 | .{ "h", "3" }, 108 | .{ "hh", "03" }, 109 | .{ "k", "15" }, 110 | .{ "kk", "15" }, 111 | 112 | .{ "m", "24" }, 113 | .{ "mm", "24" }, 114 | 115 | .{ "s", "12" }, 116 | .{ "ss", "12" }, 117 | 118 | .{ "S", "7" }, 119 | .{ "SS", "78" }, 120 | .{ "SSS", "789" }, 121 | 122 | .{ "z", "UTC" }, 123 | .{ "Z", "+00:00" }, 124 | .{ "ZZ", "+0000" }, 125 | 126 | .{ "x", "1144509852789" }, 127 | .{ "X", "1144509852" }, 128 | 129 | .{ time.format.LT, "3:24 PM" }, 130 | 131 | .{ time.format.LTS, "3:24:12 PM" }, 132 | 133 | .{ time.format.L, "04/08/2006" }, 134 | 135 | .{ time.format.l, "4/8/2006" }, 136 | 137 | .{ time.format.LL, "April 8, 2006" }, 138 | 139 | .{ time.format.ll, "Apr 8, 2006" }, 140 | 141 | .{ time.format.LLL, "April 8, 2006 3:24 PM" }, 142 | 143 | .{ time.format.lll, "Apr 8, 2006 3:24 PM" }, 144 | 145 | .{ time.format.LLLL, "Saturday, April 8, 2006 3:24 PM" }, 146 | 147 | .{ time.format.llll, "Sat, Apr 8, 2006 3:24 PM" }, 148 | }); 149 | 150 | // https://github.com/nektro/zig-time/issues/3 151 | harness(1144509852789, &.{.{ "YYYYMM", "200604" }}); 152 | } 153 | 154 | // https://github.com/nektro/zig-time/issues/9 155 | test { 156 | var t = time.DateTime.initUnix(1330502962); 157 | try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2012-02-29 08:09:22 AM UTC"); 158 | t = t.addYears(1); 159 | try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2013-03-01 08:09:22 AM UTC"); 160 | } 161 | -------------------------------------------------------------------------------- /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 | const Self = @This(); 17 | 18 | pub fn initUnixMs(unix: u64) Self { 19 | return epoch_unix.addMs(unix); 20 | } 21 | 22 | pub fn initUnix(unix: u64) Self { 23 | return epoch_unix.addSecs(unix); 24 | } 25 | 26 | /// Caller asserts that this is > epoch 27 | pub fn init(year: u16, month: u16, day: u16, hr: u16, min: u16, sec: u16) Self { 28 | return epoch_unix 29 | .addYears(year - epoch_unix.years) 30 | .addMonths(month) 31 | .addDays(day) 32 | .addHours(hr) 33 | .addMins(min) 34 | .addSecs(sec); 35 | } 36 | 37 | pub fn now() Self { 38 | return initUnixMs(@intCast(std.time.milliTimestamp())); 39 | } 40 | 41 | pub const epoch_unix = Self{ 42 | .ms = 0, 43 | .seconds = 0, 44 | .minutes = 0, 45 | .hours = 0, 46 | .days = 0, 47 | .months = 0, 48 | .years = 1970, 49 | .timezone = .UTC, 50 | }; 51 | 52 | pub fn eql(self: Self, other: Self) bool { 53 | return self.ms == other.ms and 54 | self.seconds == other.seconds and 55 | self.minutes == other.minutes and 56 | self.hours == other.hours and 57 | self.days == other.days and 58 | self.months == other.months and 59 | self.years == other.years and 60 | self.timezone == other.timezone and 61 | self.weekday == other.weekday; 62 | } 63 | 64 | pub fn addMs(self: Self, count: u64) Self { 65 | if (count == 0) return self; 66 | var result = self; 67 | result.ms += @intCast(count % 1000); 68 | return result.addSecs(count / 1000); 69 | } 70 | 71 | pub fn addSecs(self: Self, count: u64) Self { 72 | if (count == 0) return self; 73 | var result = self; 74 | result.seconds += @intCast(count % 60); 75 | return result.addMins(count / 60); 76 | } 77 | 78 | pub fn addMins(self: Self, count: u64) Self { 79 | if (count == 0) return self; 80 | var result = self; 81 | result.minutes += @intCast(count % 60); 82 | return result.addHours(count / 60); 83 | } 84 | 85 | pub fn addHours(self: Self, count: u64) Self { 86 | if (count == 0) return self; 87 | var result = self; 88 | result.hours += @intCast(count % 24); 89 | return result.addDays(count / 24); 90 | } 91 | 92 | pub fn addDays(self: Self, count: u64) Self { 93 | if (count == 0) return self; 94 | var result = self; 95 | var input = count; 96 | 97 | while (true) { 98 | const year_len = result.daysThisYear(); 99 | if (input >= year_len) { 100 | result.years += 1; 101 | input -= year_len; 102 | continue; 103 | } 104 | break; 105 | } 106 | while (true) { 107 | const month_len = result.daysThisMonth(); 108 | if (input >= month_len) { 109 | result.months += 1; 110 | input -= month_len; 111 | 112 | if (result.months == 12) { 113 | result.years += 1; 114 | result.months = 0; 115 | } 116 | continue; 117 | } 118 | break; 119 | } 120 | { 121 | const month_len = result.daysThisMonth(); 122 | if (result.days + input > month_len) { 123 | const left = month_len - result.days; 124 | input -= left; 125 | result.months += 1; 126 | result.days = 0; 127 | } 128 | result.days += @intCast(input); 129 | 130 | if (result.days == result.daysThisMonth()) { 131 | result.months += 1; 132 | result.days = 0; 133 | } 134 | if (result.months == 12) { 135 | result.years += 1; 136 | result.months = 0; 137 | } 138 | } 139 | 140 | std.debug.assert(result.ms < 1000); 141 | std.debug.assert(result.seconds < 60); 142 | std.debug.assert(result.minutes < 60); 143 | std.debug.assert(result.hours < 24); 144 | std.debug.assert(result.days < result.daysThisMonth()); 145 | std.debug.assert(result.months < 12); 146 | return result; 147 | } 148 | 149 | pub fn addMonths(self: Self, count: u64) Self { 150 | if (count == 0) return self; 151 | var result = self; 152 | var input = count; 153 | while (input > 0) { 154 | const new = result.addDays(result.daysThisMonth()); 155 | result = new; 156 | input -= 1; 157 | } 158 | return result; 159 | } 160 | 161 | pub fn addYears(self: Self, count: u64) Self { 162 | var result = self; 163 | for (0..count) |_| { 164 | result = result.addDays(result.daysThisYear()); 165 | } 166 | return result; 167 | } 168 | 169 | pub fn isLeapYear(self: Self) bool { 170 | return time.isLeapYear(self.years); 171 | } 172 | 173 | pub fn daysThisYear(self: Self) u16 { 174 | return time.daysInYear(self.years); 175 | } 176 | 177 | pub fn daysThisMonth(self: Self) u16 { 178 | return self.daysInMonth(self.months); 179 | } 180 | 181 | fn daysInMonth(self: Self, month: u16) u16 { 182 | return time.daysInMonth(self.years, month); 183 | } 184 | 185 | pub fn dayOfThisYear(self: Self) u16 { 186 | var ret: u16 = 0; 187 | for (0..self.months) |item| { 188 | ret += self.daysInMonth(@intCast(item)); 189 | } 190 | ret += self.days; 191 | return ret; 192 | } 193 | 194 | pub fn toUnix(self: Self) u64 { 195 | const x = self.toUnixMilli(); 196 | return x / 1000; 197 | } 198 | 199 | pub fn toUnixMilli(self: Self) u64 { 200 | var res: u64 = 0; 201 | res += self.ms; 202 | res += @as(u64, self.seconds) * std.time.ms_per_s; 203 | res += @as(u64, self.minutes) * std.time.ms_per_min; 204 | res += @as(u64, self.hours) * std.time.ms_per_hour; 205 | res += self.daysSinceEpoch() * std.time.ms_per_day; 206 | return res; 207 | } 208 | 209 | fn daysSinceEpoch(self: Self) u64 { 210 | var res: u64 = 0; 211 | res += self.days; 212 | for (0..self.years - epoch_unix.years) |i| res += time.daysInYear(@intCast(i)); 213 | for (0..self.months) |i| res += self.daysInMonth(@intCast(i)); 214 | return res; 215 | } 216 | 217 | /// fmt is based on https://momentjs.com/docs/#/displaying/format/ 218 | pub fn format(self: Self, comptime fmt: string, options: std.fmt.FormatOptions, writer: anytype) !void { 219 | _ = options; 220 | 221 | if (fmt.len == 0) @compileError("DateTime: format string can't be empty"); 222 | 223 | @setEvalBranchQuota(100000); 224 | 225 | comptime var s = 0; 226 | comptime var e = 0; 227 | comptime var next: ?FormatSeq = null; 228 | inline for (fmt, 0..) |c, i| { 229 | e = i + 1; 230 | 231 | if (comptime std.meta.stringToEnum(FormatSeq, fmt[s..e])) |tag| { 232 | next = tag; 233 | if (i < fmt.len - 1) continue; 234 | } 235 | 236 | if (next) |tag| { 237 | switch (tag) { 238 | .MM => try writer.print("{:0>2}", .{self.months + 1}), 239 | .M => try writer.print("{}", .{self.months + 1}), 240 | .Mo => try printOrdinal(writer, self.months + 1), 241 | .MMM => try printLongName(writer, self.months, &[_]string{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }), 242 | .MMMM => try printLongName(writer, self.months, &[_]string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }), 243 | 244 | .Q => try writer.print("{}", .{self.months / 3 + 1}), 245 | .Qo => try printOrdinal(writer, self.months / 3 + 1), 246 | 247 | .D => try writer.print("{}", .{self.days + 1}), 248 | .Do => try printOrdinal(writer, self.days + 1), 249 | .DD => try writer.print("{:0>2}", .{self.days + 1}), 250 | 251 | .DDD => try writer.print("{}", .{self.dayOfThisYear() + 1}), 252 | .DDDo => try printOrdinal(writer, self.dayOfThisYear() + 1), 253 | .DDDD => try writer.print("{:0>3}", .{self.dayOfThisYear() + 1}), 254 | 255 | .d => try writer.print("{}", .{@intFromEnum(self.weekday())}), 256 | .do => try printOrdinal(writer, @intFromEnum(self.weekday())), 257 | .dd => try writer.writeAll(@tagName(self.weekday())[0..2]), 258 | .ddd => try writer.writeAll(@tagName(self.weekday())), 259 | .dddd => try printLongName(writer, @intFromEnum(self.weekday()), &[_]string{ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }), 260 | .e => try writer.print("{}", .{@intFromEnum(self.weekday())}), 261 | .E => try writer.print("{}", .{@intFromEnum(self.weekday()) + 1}), 262 | 263 | .w => try writer.print("{}", .{self.dayOfThisYear() / 7 + 1}), 264 | .wo => try printOrdinal(writer, self.dayOfThisYear() / 7 + 1), 265 | .ww => try writer.print("{:0>2}", .{self.dayOfThisYear() / 7 + 1}), 266 | 267 | .Y => try writer.print("{}", .{self.years + 10000}), 268 | .YY => try writer.print("{:0>2}", .{self.years % 100}), 269 | .YYY => try writer.print("{}", .{self.years}), 270 | .YYYY => try writer.print("{:0>4}", .{self.years}), 271 | 272 | .N => try writer.writeAll(@tagName(self.era())), 273 | .NN => try writer.writeAll("Anno Domini"), 274 | 275 | .A => try printLongName(writer, self.hours / 12, &[_]string{ "AM", "PM" }), 276 | .a => try printLongName(writer, self.hours / 12, &[_]string{ "am", "pm" }), 277 | 278 | .H => try writer.print("{}", .{self.hours}), 279 | .HH => try writer.print("{:0>2}", .{self.hours}), 280 | .h => try writer.print("{}", .{wrap(self.hours, 12)}), 281 | .hh => try writer.print("{:0>2}", .{wrap(self.hours, 12)}), 282 | .k => try writer.print("{}", .{wrap(self.hours, 24)}), 283 | .kk => try writer.print("{:0>2}", .{wrap(self.hours, 24)}), 284 | 285 | .m => try writer.print("{}", .{self.minutes}), 286 | .mm => try writer.print("{:0>2}", .{self.minutes}), 287 | 288 | .s => try writer.print("{}", .{self.seconds}), 289 | .ss => try writer.print("{:0>2}", .{self.seconds}), 290 | 291 | .S => try writer.print("{}", .{self.ms / 100}), 292 | .SS => try writer.print("{:0>2}", .{self.ms / 10}), 293 | .SSS => try writer.print("{:0>3}", .{self.ms}), 294 | 295 | .z => try writer.writeAll(@tagName(self.timezone)), 296 | .Z => try writer.writeAll("+00:00"), 297 | .ZZ => try writer.writeAll("+0000"), 298 | 299 | .x => try writer.print("{}", .{self.toUnixMilli()}), 300 | .X => try writer.print("{}", .{self.toUnix()}), 301 | } 302 | next = null; 303 | s = i; 304 | } 305 | 306 | switch (c) { 307 | ',', 308 | ' ', 309 | ':', 310 | '-', 311 | '.', 312 | 'T', 313 | 'W', 314 | '/', 315 | => { 316 | try writer.writeAll(&.{c}); 317 | s = i + 1; 318 | continue; 319 | }, 320 | else => {}, 321 | } 322 | } 323 | } 324 | 325 | pub fn formatAlloc(self: Self, alloc: std.mem.Allocator, comptime fmt: string) !string { 326 | var list = std.ArrayList(u8).init(alloc); 327 | defer list.deinit(); 328 | 329 | try self.format(fmt, .{}, list.writer()); 330 | return list.toOwnedSlice(); 331 | } 332 | 333 | const FormatSeq = enum { 334 | M, // 1 2 ... 11 12 335 | Mo, // 1st 2nd ... 11th 12th 336 | MM, // 01 02 ... 11 12 337 | MMM, // Jan Feb ... Nov Dec 338 | MMMM, // January February ... November December 339 | Q, // 1 2 3 4 340 | Qo, // 1st 2nd 3rd 4th 341 | D, // 1 2 ... 30 31 342 | Do, // 1st 2nd ... 30th 31st 343 | DD, // 01 02 ... 30 31 344 | DDD, // 1 2 ... 364 365 345 | DDDo, // 1st 2nd ... 364th 365th 346 | DDDD, // 001 002 ... 364 365 347 | d, // 0 1 ... 5 6 348 | do, // 0th 1st ... 5th 6th 349 | dd, // Su Mo ... Fr Sa 350 | ddd, // Sun Mon ... Fri Sat 351 | dddd, // Sunday Monday ... Friday Saturday 352 | e, // 0 1 ... 5 6 (locale) 353 | E, // 1 2 ... 6 7 (ISO) 354 | w, // 1 2 ... 52 53 355 | wo, // 1st 2nd ... 52nd 53rd 356 | ww, // 01 02 ... 52 53 357 | Y, // 11970 11971 ... 19999 20000 20001 (Holocene calendar) 358 | YY, // 70 71 ... 29 30 359 | YYY, // 1 2 ... 1970 1971 ... 2029 2030 360 | YYYY, // 0001 0002 ... 1970 1971 ... 2029 2030 361 | N, // BC AD 362 | NN, // Before Christ ... Anno Domini 363 | A, // AM PM 364 | a, // am pm 365 | H, // 0 1 ... 22 23 366 | HH, // 00 01 ... 22 23 367 | h, // 1 2 ... 11 12 368 | hh, // 01 02 ... 11 12 369 | k, // 1 2 ... 23 24 370 | kk, // 01 02 ... 23 24 371 | m, // 0 1 ... 58 59 372 | mm, // 00 01 ... 58 59 373 | s, // 0 1 ... 58 59 374 | ss, // 00 01 ... 58 59 375 | S, // 0 1 ... 8 9 (second fraction) 376 | SS, // 00 01 ... 98 99 377 | SSS, // 000 001 ... 998 999 378 | z, // EST CST ... MST PST 379 | Z, // -07:00 -06:00 ... +06:00 +07:00 380 | ZZ, // -0700 -0600 ... +0600 +0700 381 | x, // unix milli 382 | X, // unix 383 | }; 384 | 385 | pub fn since(self: Self, other_in_the_past: Self) Duration { 386 | return Duration{ 387 | .ms = self.toUnixMilli() - other_in_the_past.toUnixMilli(), 388 | }; 389 | } 390 | 391 | pub fn era(self: Self) Era { 392 | if (self.years >= 0) return .AD; 393 | @compileError("TODO"); 394 | } 395 | 396 | pub fn weekday(self: Self) WeekDay { 397 | var i = self.daysSinceEpoch() % 7; 398 | var result = WeekDay.Thu; // weekday of epoch_unix 399 | while (i > 0) : (i -= 1) { 400 | result = result.next(); 401 | } 402 | return result; 403 | } 404 | }; 405 | 406 | pub const format = struct { 407 | pub const LT = "h:mm A"; 408 | pub const LTS = "h:mm:ss A"; 409 | pub const L = "MM/DD/YYYY"; 410 | pub const l = "M/D/YYY"; 411 | pub const LL = "MMMM D, YYYY"; 412 | pub const ll = "MMM D, YYY"; 413 | pub const LLL = LL ++ " " ++ LT; 414 | pub const lll = ll ++ " " ++ LT; 415 | pub const LLLL = "dddd, " ++ LLL; 416 | pub const llll = "ddd, " ++ lll; 417 | }; 418 | 419 | pub const TimeZone = enum { 420 | UTC, 421 | 422 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 423 | }; 424 | 425 | pub const WeekDay = enum { 426 | Sun, 427 | Mon, 428 | Tue, 429 | Wed, 430 | Thu, 431 | Fri, 432 | Sat, 433 | 434 | pub fn next(self: WeekDay) WeekDay { 435 | return switch (self) { 436 | .Sun => .Mon, 437 | .Mon => .Tue, 438 | .Tue => .Wed, 439 | .Wed => .Thu, 440 | .Thu => .Fri, 441 | .Fri => .Sat, 442 | .Sat => .Sun, 443 | }; 444 | } 445 | 446 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 447 | }; 448 | 449 | pub const Era = enum { 450 | // BC, 451 | AD, 452 | 453 | usingnamespace extras.TagNameJsonStringifyMixin(@This()); 454 | }; 455 | 456 | pub fn isLeapYear(year: u16) bool { 457 | var ret = false; 458 | if (year % 4 == 0) ret = true; 459 | if (year % 100 == 0) ret = false; 460 | if (year % 400 == 0) ret = true; 461 | return ret; 462 | } 463 | 464 | pub fn daysInYear(year: u16) u16 { 465 | return if (isLeapYear(year)) 366 else 365; 466 | } 467 | 468 | fn daysInMonth(year: u16, month: u16) u16 { 469 | const norm = [12]u16{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 470 | const leap = [12]u16{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 471 | const month_days = if (!isLeapYear(year)) norm else leap; 472 | return month_days[month]; 473 | } 474 | 475 | fn printOrdinal(writer: anytype, num: u16) !void { 476 | try writer.print("{}", .{num}); 477 | try writer.writeAll(switch (num) { 478 | 1 => "st", 479 | 2 => "nd", 480 | 3 => "rd", 481 | else => "th", 482 | }); 483 | } 484 | 485 | fn printLongName(writer: anytype, index: u16, names: []const string) !void { 486 | try writer.writeAll(names[index]); 487 | } 488 | 489 | fn wrap(val: u16, at: u16) u16 { 490 | const tmp = val % at; 491 | return if (tmp == 0) at else tmp; 492 | } 493 | 494 | pub const Duration = struct { 495 | ms: u64, 496 | }; 497 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------