diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..71969716e3e9ca87fdafa1839c25c6daf7da7560 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +zig-* +.zigmod +deps.zig diff --git a/build.zig b/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..6394c9fe15b47b072952770b58d8001d88ebc473 --- /dev/null +++ b/build.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zig-time", "main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/main.zig b/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..d29869ff88feb94cd5a5fd28d760824920f269ac --- /dev/null +++ b/main.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + std.log.info("All your codebase are belong to us.", .{}); +} diff --git a/time.zig b/time.zig new file mode 100644 index 0000000000000000000000000000000000000000..ee5483292292fc0302dbb35cdf4899438d7c3e9b --- /dev/null +++ b/time.zig @@ -0,0 +1,335 @@ +const std = @import("std"); +const string = []const u8; +const range = @import("range").range; + +pub const DateTime = struct { + ms: u16, + seconds: u16, + minutes: u16, + hours: u16, + days: u16, + months: u16, + years: u16, + timezone: TimeZone, + weekday: WeekDay, + + const Self = @This(); + + pub fn initUnixMs(unix: u64) Self { + return unix_epoch.addMs(unix); + } + + /// Caller asserts that this is > epoch + pub fn init(year: u16, month: u16, day: u16, hr: u16, min: u16, sec: u16) Self { + return unix_epoch + .addYears(year - unix_epoch.years) + .addMonths(month) + .addDays(day) + .addHours(hr) + .addMins(min) + .addSecs(sec); + } + + pub fn now() Self { + return initUnixMs(@intCast(u64, std.time.milliTimestamp())); + } + + pub const unix_epoch = Self{ + .ms = 0, + .seconds = 0, + .minutes = 0, + .hours = 0, + .days = 0, + .months = 0, + .years = 1970, + .timezone = .UTC, + .weekday = .Thu, + }; + + pub fn addMs(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + result.ms += @intCast(u16, count % 1000); + return result.addSecs(count / 1000); + } + + pub fn addSecs(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + result.seconds += @intCast(u16, count % 60); + return result.addMins(count / 60); + } + + pub fn addMins(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + result.minutes += @intCast(u16, count % 60); + return result.addHours(count / 60); + } + + pub fn addHours(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + result.hours += @intCast(u16, count % 24); + return result.addDays(count / 24); + } + + pub fn addDays(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + var input = count; + + while (true) { + const year_len = self.daysThisYear(); + if (input >= year_len) { + result.years += 1; + input -= year_len; + continue; + } + break; + } + while (true) { + const month_len = self.daysThisMonth(); + if (input >= month_len) { + result.months += 1; + input -= month_len; + + if (result.months == 12) { + result.years += 1; + result.months = 0; + } + continue; + } + break; + } + { + const month_len = self.daysThisMonth(); + if (result.days + input > month_len) { + const left = month_len - result.days; + input -= left; + result.months += 1; + result.days = 0; + result.incrementWeekday(left); + } + result.days += @intCast(u16, input); + result.incrementWeekday(input); + + if (result.months == 12) { + result.years += 1; + result.months = 0; + } + } + + return result; + } + + pub fn addMonths(self: Self, count: u64) Self { + if (count == 0) return self; + var result = self; + var input = count; + while (input > 0) { + const new = result.addDays(result.daysThisMonth()); + result = new; + input -= 1; + } + return result; + } + + pub fn addYears(self: Self, count: u64) Self { + if (count == 0) return self; + return self.addMonths(count * 12); + } + + pub fn isLeapYear(self: Self) bool { + const y = self.years; + var ret = false; + if (y % 4 == 0) ret = true; + if (y % 100 == 0) ret = false; + if (y % 400 == 0) ret = true; + return ret; + } + + pub fn daysThisYear(self: Self) u64 { + return if (self.isLeapYear()) 366 else 365; + } + + pub fn daysThisMonth(self: Self) u64 { + const norm = [12]u64{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + const leap = [12]u64{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + const month_days = if (!self.isLeapYear()) norm else leap; + return month_days[self.months]; + } + + fn incrementWeekday(self: *Self, count: u64) void { + for (range(count % 7)) |_| { + self.weekday = self.weekday.next(); + } + } + + /// fmt is defined by https://momentjs.com/docs/#/displaying/format/ + pub fn format(self: Self, comptime fmt: string, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = options; + + if (fmt.len == 0) @compileError("DateTime: format string can't be empty"); + + _ = writer; + _ = self; + + @setEvalBranchQuota(100000); + + comptime var s = 0; + comptime var e = 0; + comptime var next: ?FormatSeq = null; + inline for (fmt) |c, i| { + e = i + 1; + + if (comptime std.meta.stringToEnum(FormatSeq, fmt[s..e])) |tag| { + next = tag; + if (i < fmt.len - 1) continue; + } + + if (next) |tag| { + switch (tag) { + .ddd => try writer.writeAll(@tagName(self.weekday)), + .DD => try writer.print("{:0>2}", .{self.days + 1}), + .YYYY => try writer.print("{:0>4}", .{self.years}), + .HH => try writer.print("{:0>2}", .{self.hours}), + .mm => try writer.print("{:0>2}", .{self.minutes}), + .ss => try writer.print("{:0>2}", .{self.seconds}), + .MM => try writer.print("{:0>2}", .{self.months}), + .z => try writer.writeAll(@tagName(self.timezone)), + + .MMM => { + const names = [_]string{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + try writer.writeAll(names[self.months]); + }, + + else => @compileError("'" ++ @tagName(tag) ++ "' not currently supported"), + + // stubs to make the parser work + .YYY => @compileError(comptime std.fmt.comptimePrint("'{s}' is not a valid format sequence", .{@tagName(tag)})), + } + next = null; + s = i; + } + + switch (c) { + ',', + ' ', + ':', + => { + try writer.writeAll(&.{c}); + s = i + 1; + continue; + }, + else => {}, + } + + if (i < fmt.len - 1) @compileError(comptime std.fmt.comptimePrint("'{s}' is not a valid format sequence", .{fmt[s..e]})); + } + } + + pub fn formatAlloc(self: Self, alloc: *std.mem.Allocator, comptime fmt: string) !string { + var list = std.ArrayList(u8).init(alloc); + defer list.deinit(); + + try self.format(fmt, .{}, list.writer()); + return list.toOwnedSlice(); + } + + const FormatSeq = enum { + M, // 1 2 ... 11 12 + Mo, // 1st 2nd ... 11th 12th + MM, // 01 02 ... 11 12 + MMM, // Jan Feb ... Nov Dec + MMMM, // January February ... November December + Q, // 1 2 3 4 + Qo, // 1st 2nd 3rd 4th + D, // 1 2 ... 30 31 + Do, // 1st 2nd ... 30th 31st + DD, // 01 02 ... 30 31 + DDD, // 1 2 ... 364 365 + DDDo, // 1st 2nd ... 364th 365th + DDDD, // 001 002 ... 364 365 + d, // 0 1 ... 5 6 + do, // 0th 1st ... 5th 6th + dd, // Su Mo ... Fr Sa + ddd, // Sun Mon ... Fri Sat + dddd, // Sunday Monday ... Friday Saturday + e, // 0 1 ... 5 6 (locale) + E, // 1 2 ... 6 7 (ISO) + w, // 1 2 ... 52 53 + wo, // 1st 2nd ... 52nd 53rd + ww, // 01 02 ... 52 53 + W, // 1 2 ... 52 53 (ISO) + Wo, // 1st 2nd ... 52nd 53rd + WW, // 01 02 ... 52 53 + YY, // 70 71 ... 29 30 + YYY, // stub + YYYY, // 1970 1971 ... 2029 2030 + YYYYYY, // -001970 -001971 ... +001907 +001971 + Y, // 1970 1971 ... 9999 +10000 +10001 + y, // 1 2 ... 2020 ... (era) + N, // BC AD + NN, // Before Christ ... Anno Domini + A, // AM PM + a, // am pm + H, // 0 1 ... 22 23 + HH, // 00 01 ... 22 23 + h, // 1 2 ... 11 12 + hh, // 01 02 ... 11 12 + k, // 1 2 ... 23 24 + kk, // 01 02 ... 23 24 + m, // 0 1 ... 58 59 + mm, // 00 01 ... 58 59 + s, // 0 1 ... 58 59 + ss, // 00 01 ... 58 59 + S, // 0 1 ... 8 9 (second fraction) + SS, // 00 01 ... 98 99 + SSS, // 000 001 ... 998 999 + z, // EST CST ... MST PST + Z, // -07:00 -06:00 ... +06:00 +07:00 + ZZ, // -0700 -0600 ... +0600 +0700 + X, // unix + x, // unix milli + }; +}; + +pub const format = struct { + pub const LT = ""; + pub const LTS = ""; + pub const L = ""; + pub const l = ""; + pub const LL = ""; + pub const ll = ""; + pub const LLL = ""; + pub const lll = ""; + pub const LLLL = ""; + pub const llll = ""; +}; + +pub const TimeZone = enum { + UTC, +}; + +pub const WeekDay = enum { + Sun, + Mon, + Tue, + Wed, + Thu, + Fri, + Sat, + + pub fn next(self: WeekDay) WeekDay { + return switch (self) { + .Sun => .Mon, + .Mon => .Tue, + .Tue => .Wed, + .Wed => .Thu, + .Thu => .Fri, + .Fri => .Sat, + .Sat => .Sun, + }; + } +}; diff --git a/zig.mod b/zig.mod new file mode 100644 index 0000000000000000000000000000000000000000..64e92c8f4fa56d8a3d9c25f5a5d5fc7e8a9d11f1 --- /dev/null +++ b/zig.mod @@ -0,0 +1,7 @@ +id: iecwp4b3bsfmpp4x99gjo4a5ljv6ix4owy8czip32yanpvlb +name: time +main: time.zig +license: MIT +description: A date and time parsing and formatting library for Zig. +dependencies: + - src: git https://github.com/nektro/zig-range diff --git a/zigmod.lock b/zigmod.lock new file mode 100644 index 0000000000000000000000000000000000000000..4abcde2af0172615d652f2b48a13934c6821abf0 --- /dev/null +++ b/zigmod.lock @@ -0,0 +1,2 @@ +2 +git https://github.com/nektro/zig-range commit-890ca308fe09b3d5c866d5cfb3b3d7a95dbf939f