This commit is contained in:
Jeffrey C. Ollie 2024-03-13 15:47:56 -05:00
parent 15fb429788
commit 40fc6a8d41
Signed by: jeff
GPG key ID: 6F86035A6D97044E
3 changed files with 668 additions and 113 deletions

View file

@ -1,18 +1,19 @@
{ {
"nodes": { "nodes": {
"bash": { "flake-compat": {
"flake": false,
"locked": { "locked": {
"lastModified": 1697126158, "lastModified": 1673956053,
"narHash": "sha256-XoRmgs8U78oVMVzk4riJpkmXaX1Pk2Ya/wYMmTYt2mA=", "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"ref": "refs/heads/main", "owner": "edolstra",
"rev": "443dc212854202ddf2bb3bf29ad6d6c1f8829ff6", "repo": "flake-compat",
"revCount": 11, "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "git", "type": "github"
"url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git"
}, },
"original": { "original": {
"type": "git", "owner": "edolstra",
"url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git" "repo": "flake-compat",
"type": "github"
} }
}, },
"flake-utils": { "flake-utils": {
@ -20,11 +21,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1694529238, "lastModified": 1701680307,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -33,6 +34,54 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"zls",
"nixpkgs"
]
},
"locked": {
"lastModified": 1694102001,
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"langref": {
"flake": false,
"locked": {
"narHash": "sha256-mYdDCBdNEIeMbavdhSo8qXqW+3fqPC8BAich7W3umrI=",
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
},
"original": {
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
}
},
"make-shell": { "make-shell": {
"locked": { "locked": {
"lastModified": 1634940815, "lastModified": 1634940815,
@ -50,11 +99,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1697059129, "lastModified": 1702312524,
"narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=", "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593", "rev": "a9bf124c46ef298113270b1f84a164865987a91c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -65,10 +114,11 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"bash": "bash",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"make-shell": "make-shell", "make-shell": "make-shell",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs",
"zig": "zig",
"zls": "zls"
} }
}, },
"systems": { "systems": {
@ -85,6 +135,56 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"zig": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1702772729,
"narHash": "sha256-y29/F6sZHDXefE+e84DOVfU5rCoTPyH0q5QCrNxRG64=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "1a7d61cc7d3ec69ea418e373a4582bfc765469e3",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
},
"zls": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"gitignore": "gitignore",
"langref": "langref",
"nixpkgs": [
"nixpkgs"
],
"zig-overlay": [
"zig"
]
},
"locked": {
"lastModified": 1702566669,
"narHash": "sha256-z7Uq+hXr0CWA4uBZjg+t8FEQkCV0CtGnP0RCgzrjnxo=",
"owner": "zigtools",
"repo": "zls",
"rev": "9476a1d47034954367be026b6609062aaefa16ba",
"type": "github"
},
"original": {
"owner": "zigtools",
"repo": "zls",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -1,5 +1,5 @@
{ {
description = "zig-ha"; description = "zig-datetime";
inputs = { inputs = {
nixpkgs = { nixpkgs = {
@ -8,32 +8,37 @@
flake-utils = { flake-utils = {
url = "github:numtide/flake-utils"; url = "github:numtide/flake-utils";
}; };
bash = {
url = "git+https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git";
};
make-shell = { make-shell = {
url = "github:ursi/nix-make-shell"; url = "github:ursi/nix-make-shell";
}; };
zig = {
url = "github:mitchellh/zig-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
zls = {
url = "github:zigtools/zls";
inputs.nixpkgs.follows = "nixpkgs";
inputs.zig-overlay.follows = "zig";
inputs.flake-utils.follows = "flake-utils";
};
}; };
outputs = { self, nixpkgs, flake-utils, bash, ... }@inputs: outputs = {
self,
nixpkgs,
flake-utils,
zig,
zls,
...
} @ inputs:
flake-utils.lib.eachDefaultSystem ( flake-utils.lib.eachDefaultSystem (
system: system: let
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
}; };
in in {
{ devShells.default = let
devShells.default =
let
project = "zig-datetime"; project = "zig-datetime";
prompt = (
bash.build_ps1_prompt
bash.ansi_normal_blue
"${project} - ${bash.username}@${bash.hostname_short}: ${bash.current_working_directory}"
"${project}:${bash.current_working_directory}"
);
make-shell = import inputs.make-shell { make-shell = import inputs.make-shell {
inherit system; inherit system;
pkgs = pkgs; pkgs = pkgs;
@ -42,11 +47,11 @@
make-shell { make-shell {
packages = [ packages = [
pkgs.zon2nix pkgs.zon2nix
pkgs.zig_0_11 zig.packages.${pkgs.system}.master
pkgs.zls zls.packages.${pkgs.system}.zls
]; ];
env = { env = {
PS1 = prompt; name = project;
}; };
}; };
} }

View file

@ -1,5 +1,93 @@
const std = @import("std"); const std = @import("std");
// https://www.nist.gov/pml/owm/metric-si-prefixes
pub const SIPrefix = enum(i8) {
quetta = 30,
ronna = 27,
yotta = 24,
zetta = 21,
exa = 18,
peta = 15,
tera = 12,
giga = 9,
mega = 6,
kilo = 3,
hecto = 2,
deka = 1,
deci = -1,
centi = -2,
milli = -3,
micro = -6,
nano = -9,
pico = -12,
femto = -15,
atto = -18,
zepto = -21,
yocto = -24,
ronto = -27,
quecto = -30,
const Self = @This();
pub fn exponent(self: Self) i8 {
return @intFromEnum(self);
}
pub fn symbol(self: Self) []const u8 {
return switch (self) {
.quetta => "Q",
.ronna => "R",
.yotta => "Y",
.zetta => "Z",
.exa => "E",
.peta => "P",
.tera => "T",
.giga => "G",
.mega => "M",
.kilo => "k",
.hecto => "h",
.deka => "da",
.deci => "d",
.centi => "c",
.milli => "m",
.micro => "µ",
.nano => "n",
.pico => "p",
.femto => "f",
.atto => "a",
.zepto => "z",
.yocto => "y",
.ronto => "r",
.quecto => "q",
};
}
};
pub fn convert(from: i8, to: i8, value: i128) struct { result: i128, remainder: i128 } {
const exponent = from - to;
if (exponent == 0) return value;
if (exponent < 0) {
const factor = std.math.pow(i128, 10, @abs(exponent));
const remainder = @rem(value, factor);
const result = @divTrunc(value, factor);
return .{ result, remainder };
}
if (exponent > 0) {
const factor = std.math.pow(i128, 10, exponent);
return .{ factor * value, 0 };
}
}
// test "convert-1" {
// const result = convert(.milli, .micro, 1);
// try std.testing.expectEqual(i128, result.result, 1000);
// try std.testing.expectEqual(i128, result.remainder, 0);
// }
// pub fn conversionFactor(from: i8, to: i8) !i30 {
// if (from < to) return error.InvalidConversion;
// if (from == to) return 1;
// }
pub const Day = u6; pub const Day = u6;
pub const Month = enum(u4) { pub const Month = enum(u4) {
@ -179,6 +267,24 @@ pub fn writeTwelveHour(hour: Hour, case: enum { lower, upper }, writer: anytype)
pub const Nanosecond = u30; pub const Nanosecond = u30;
fn readInt(text: []const u8, maxlen: usize) []const u8 {
if (text.len == 0) return text[0..0];
for (0..@min(text.len, maxlen)) |i| {
if (!std.ascii.isDigit(text[i])) return text[0 .. i - 1];
}
return text[0..maxlen];
}
fn readFrac(text: []const u8, length: usize) !Nanosecond {
if (length == 0) return error.TooShort;
if (length > 9) return error.TooLong;
const v = readInt(text, length);
if (v.len != length) return error.TooShort;
return try std.fmt.parseInt(Nanosecond, v, 10) * try std.math.powi(Nanosecond, 10, 9 - @as(Nanosecond, @intCast(length)));
}
// https://github.com/nektro/zig-time // https://github.com/nektro/zig-time
pub const DateTime = struct { pub const DateTime = struct {
@ -193,7 +299,7 @@ pub const DateTime = struct {
const Self = @This(); const Self = @This();
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { pub fn formatX(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = options; _ = options;
if (fmt.len == 0) @compileError("DateTime: format string can't be empty"); if (fmt.len == 0) @compileError("DateTime: format string can't be empty");
@ -258,7 +364,7 @@ pub const DateTime = struct {
if (self.year < 0) { if (self.year < 0) {
try writer.writeAll("-"); try writer.writeAll("-");
} }
try writer.print("{d:0>4}", .{std.math.absCast(self.year)}); try writer.print("{d:0>4}", .{@abs(self.year)});
}, },
.A => try writeTwelveHour(self.hour, .upper), .A => try writeTwelveHour(self.hour, .upper),
@ -277,15 +383,24 @@ pub const DateTime = struct {
.s => try writer.print("{d}", .{self.second}), .s => try writer.print("{d}", .{self.second}),
.ss => try writer.print("{d:0>2}", .{self.second}), .ss => try writer.print("{d:0>2}", .{self.second}),
.S => try writer.print("{d:>1}", .{self.nanosecond / tenthsPerNanoSecond}), // .S => try writer.print("{d:>1}", .{self.nanosecond / tenthsPerNanoSecond}),
.SS => try writer.print("{d:0>2}", .{self.nanosecond / hundredthsPerNanoSecond}), // .SS => try writer.print("{d:0>2}", .{self.nanosecond / hundredthsPerNanoSecond}),
.SSS => try writer.print("{d:0>3}", .{self.nanosecond / milliSecondsPerNanoSecond}), // .SSS => try writer.print("{d:0>3}", .{self.nanosecond / milliSecondsPerNanoSecond}),
.SSSS => try writer.print("{d:0>4}", .{self.nanosecond / (milliSecondsPerNanoSecond / 10)}), // .SSSS => try writer.print("{d:0>4}", .{self.nanosecond / (milliSecondsPerNanoSecond / 10)}),
.SSSSS => try writer.print("{d:0>5}", .{self.nanosecond / (milliSecondsPerNanoSecond / 100)}), // .SSSSS => try writer.print("{d:0>5}", .{self.nanosecond / (milliSecondsPerNanoSecond / 100)}),
.SSSSSS => try writer.print("{d:0>6}", .{self.nanosecond / microSecondsPerNanoSecond}), // .SSSSSS => try writer.print("{d:0>6}", .{self.nanosecond / microSecondsPerNanoSecond}),
.SSSSSSS => try writer.print("{d:0>7}", .{self.nanosecond / (microSecondsPerNanoSecond / 10)}), // .SSSSSSS => try writer.print("{d:0>7}", .{self.nanosecond / (microSecondsPerNanoSecond / 10)}),
.SSSSSSSS => try writer.print("{d:0>8}", .{self.nanosecond / (microSecondsPerNanoSecond / 100)}), // .SSSSSSSS => try writer.print("{d:0>8}", .{self.nanosecond / (microSecondsPerNanoSecond / 100)}),
.SSSSSSSSS => try writer.print("{d:0>9}", .{self.nanosecond}), // .SSSSSSSSS => try writer.print("{d:0>9}", .{self.nanosecond}),
.S => try writer.print("{d:>1}", .{tag.cnv(self.nanosecond)}),
.SS => try writer.print("{d:>2}", .{tag.cnv(self.nanosecond)}),
.SSS => try writer.print("{d:>3}", .{tag.cnv(self.nanosecond)}),
.SSSS => try writer.print("{d:>4}", .{tag.cnv(self.nanosecond)}),
.SSSSS => try writer.print("{d:>5}", .{tag.cnv(self.nanosecond)}),
.SSSSSS => try writer.print("{d:>6}", .{tag.cnv(self.nanosecond)}),
.SSSSSSS => try writer.print("{d:>7}", .{tag.cnv(self.nanosecond)}),
.SSSSSSSS => try writer.print("{d:>8}", .{tag.cnv(self.nanosecond)}),
.SSSSSSSSS => try writer.print("{d:>9}", .{tag.cnv(self.nanosecond)}),
// .z => try writer.writeAll(@tagName(self.timezone)), // .z => try writer.writeAll(@tagName(self.timezone)),
// .Z => try writer.writeAll("+00:00"), // .Z => try writer.writeAll("+00:00"),
@ -313,10 +428,104 @@ pub const DateTime = struct {
var list = std.ArrayList(u8).init(alloc); var list = std.ArrayList(u8).init(alloc);
defer list.deinit(); defer list.deinit();
try self.format(fmt, .{}, list.writer()); try self.formatX(fmt, .{}, list.writer());
return list.toOwnedSlice(); return list.toOwnedSlice();
} }
pub fn parse(comptime fmt: []const u8, value: []const u8) !Self {
if (fmt.len == 0) @compileError("DateTime: format string can't be empty");
@setEvalBranchQuota(100000);
var date: Self = .{
.nanosecond = 0,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
};
var left = value;
comptime var start = 0;
comptime var end = 0;
comptime var next: ?FormatSeq = null;
inline for (fmt, 0..) |char, index| {
end = index + 1;
if (comptime std.meta.stringToEnum(FormatSeq, fmt[start..end])) |tag| {
next = tag;
if (index < fmt.len - 1) continue;
}
if (next) |tag| {
switch (tag) {
.YY => {
const year = readInt(left, 2);
date.year = try std.fmt.parseInt(Year, year, 10) + 2000;
left = left[year.len..];
date.weekday = DayOfWeek.dayOfWeek(date.year, date.month, date.day);
},
.YYYY, .YYY => {
const year = readInt(left, 4);
date.year = try std.fmt.parseInt(Year, year, 10);
left = left[year.len..];
date.weekday = DayOfWeek.dayOfWeek(date.year, date.month, date.day);
},
.SSSSSSSSS => {
date.nanosecond = try readFrac(left, 9);
left = left[9..];
},
.SSSSSSSS => {
date.nanosecond = try readFrac(left, 8);
left = left[8..];
},
.SSSSSSS => {
date.nanosecond = try readFrac(left, 7);
left = left[7..];
},
.SSSSSS => {
date.nanosecond = try readFrac(left, 6);
left = left[6..];
},
.SSSSS => {
date.nanosecond = try readFrac(left, 5);
left = left[5..];
},
.SSSS => {
date.nanosecond = try readFrac(left, 4);
left = left[4..];
},
.SSS => {
date.nanosecond = try readFrac(left, 3);
left = left[3..];
},
.SS => {
date.nanosecond = try readFrac(left, 2);
left = left[2..];
},
.S => {
date.nanosecond = try readFrac(left, 1);
left = left[1..];
},
else => {},
}
next = null;
start = index;
}
switch (char) {
',', ' ', ':', '-', '.', 'T', 'W' => {
start = index + 1;
continue;
},
else => {},
}
}
return date;
}
const FormatSeq = enum { const FormatSeq = enum {
M, // 1 2 ... 11 12 (month, numeric) M, // 1 2 ... 11 12 (month, numeric)
Mo, // 1st 2nd ... 11th 12th (month, numeric ordinal) Mo, // 1st 2nd ... 11th 12th (month, numeric ordinal)
@ -379,6 +588,37 @@ pub const DateTime = struct {
// ZZ, // -0700 -0600 ... +0600 +0700 // ZZ, // -0700 -0600 ... +0600 +0700
// x, // unix milli // x, // unix milli
// X, // unix // X, // unix
pub fn cnv(comptime tag: FormatSeq, value: u30) u30 {
const name = comptime @tagName(tag);
comptime var count: i8 = undefined;
inline for (name, 1..) |c, i| {
if (c != 'S') @compileError(name ++ " is not a fractional second format sequence");
count = i;
}
if (count > 9) @compileError("fractional seconds smaller than nanoseconds are not supported");
if (count == 9) return value;
const exponent = 9 - count;
const factor = std.math.pow(u30, 10, exponent);
return @as(u30, @divTrunc(value, factor));
}
test "cnv" {
const cases = [_]struct { tag: FormatSeq, expected: u30, value: u30 }{
.{ .tag = .S, .expected = 1, .value = 123456789 },
.{ .tag = .SS, .expected = 12, .value = 123456789 },
.{ .tag = .SSS, .expected = 123, .value = 123456789 },
.{ .tag = .SSSS, .expected = 1234, .value = 123456789 },
.{ .tag = .SSSSS, .expected = 12345, .value = 123456789 },
.{ .tag = .SSSSSS, .expected = 123456, .value = 123456789 },
.{ .tag = .SSSSSSS, .expected = 1234567, .value = 123456789 },
.{ .tag = .SSSSSSSS, .expected = 12345678, .value = 123456789 },
.{ .tag = .SSSSSSSSS, .expected = 123456789, .value = 123456789 },
};
inline for (cases) |case| {
try std.testing.expectEqual(case.expected, cnv(case.tag, case.value));
}
}
}; };
pub fn dayOfThisYear(self: Self) u9 { pub fn dayOfThisYear(self: Self) u9 {
@ -386,12 +626,193 @@ pub const DateTime = struct {
} }
}; };
test "parseTest" {
const cases = [_]struct { value: []const u8, fmt: []const u8, expected: DateTime }{
.{
.value = "1970",
.fmt = "YYYY",
.expected = .{
.nanosecond = 0,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "2001",
.fmt = "YYYY",
.expected = .{
.nanosecond = 0,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 2001,
.weekday = .Mon,
},
},
.{
.value = "70",
.fmt = "YY",
.expected = .{
.nanosecond = 0,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 2070,
.weekday = .Wed,
},
},
.{
.value = "123456789",
.fmt = "SSSSSSSSS",
.expected = .{
.nanosecond = 123456789,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "12345678",
.fmt = "SSSSSSSS",
.expected = .{
.nanosecond = 123456780,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "1234567",
.fmt = "SSSSSSS",
.expected = .{
.nanosecond = 123456700,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "123456",
.fmt = "SSSSSS",
.expected = .{
.nanosecond = 123456000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "12345",
.fmt = "SSSSS",
.expected = .{
.nanosecond = 123450000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "1234",
.fmt = "SSSS",
.expected = .{
.nanosecond = 123400000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "123",
.fmt = "SSS",
.expected = .{
.nanosecond = 123000000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "12",
.fmt = "SS",
.expected = .{
.nanosecond = 120000000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
.{
.value = "1",
.fmt = "S",
.expected = .{
.nanosecond = 100000000,
.second = 0,
.minute = 0,
.hour = 0,
.day = 1,
.month = .Jan,
.year = 1970,
.weekday = .Thu,
},
},
};
inline for (cases) |case| {
const result = try DateTime.parse(case.fmt, case.value);
std.testing.expectEqual(case.expected, result) catch |err| {
std.debug.print("\n{s} {s} {} {}\n", .{ case.value, case.fmt, case.expected, result });
return err;
};
}
}
test "formatTest" { test "formatTest" {
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer); var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator(); const allocator = fba.allocator();
const epoch = DateTime{ const test_date = DateTime{
.nanosecond = 123456789, .nanosecond = 123456789,
.second = 0, .second = 0,
.minute = 0, .minute = 0,
@ -401,49 +822,50 @@ test "formatTest" {
.year = 1970, .year = 1970,
.weekday = .Fri, .weekday = .Fri,
}; };
const cases = [_]struct { datetime: DateTime, fmt: []const u8, result: []const u8 }{ const cases = [_]struct { datetime: DateTime, fmt: []const u8, result: []const u8 }{
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSSS",
.result = "1970-01-01T00:00:00.123456789", .result = "1970-01-01T00:00:00.123456789",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSS",
.result = "1970-01-01T00:00:00.12345678", .result = "1970-01-01T00:00:00.12345678",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSS",
.result = "1970-01-01T00:00:00.1234567", .result = "1970-01-01T00:00:00.1234567",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSS",
.result = "1970-01-01T00:00:00.123456", .result = "1970-01-01T00:00:00.123456",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSSS",
.result = "1970-01-01T00:00:00.12345", .result = "1970-01-01T00:00:00.12345",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSSS",
.result = "1970-01-01T00:00:00.1234", .result = "1970-01-01T00:00:00.1234",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SSS", .fmt = "YYYY-MM-DDTHH:mm:ss.SSS",
.result = "1970-01-01T00:00:00.123", .result = "1970-01-01T00:00:00.123",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.SS", .fmt = "YYYY-MM-DDTHH:mm:ss.SS",
.result = "1970-01-01T00:00:00.12", .result = "1970-01-01T00:00:00.12",
}, },
.{ .{
.datetime = epoch, .datetime = test_date,
.fmt = "YYYY-MM-DDTHH:mm:ss.S", .fmt = "YYYY-MM-DDTHH:mm:ss.S",
.result = "1970-01-01T00:00:00.1", .result = "1970-01-01T00:00:00.1",
}, },
@ -453,7 +875,7 @@ test "formatTest" {
var array = std.ArrayList(u8).init(allocator); var array = std.ArrayList(u8).init(allocator);
defer array.deinit(); defer array.deinit();
try case.datetime.format(case.fmt, .{}, array.writer()); try case.datetime.formatX(case.fmt, .{}, array.writer());
std.testing.expect(std.mem.eql(u8, array.items, case.result)) catch |err| { std.testing.expect(std.mem.eql(u8, array.items, case.result)) catch |err| {
std.debug.print("{s} {s} {s}\n", .{ std.debug.print("{s} {s} {s}\n", .{
@ -554,7 +976,7 @@ pub const Instant = struct {
.minute = minute, .minute = minute,
.second = second, .second = second,
.nanosecond = nanosecond, .nanosecond = nanosecond,
.weekday = weekdayFromDays(days), .weekday = DayOfWeek.weekdayFromDays(days),
}; };
} }
// pub fn format(self: @This()) []const u8 { // pub fn format(self: @This()) []const u8 {
@ -807,6 +1229,17 @@ const DayOfWeek = enum(u3) {
pub fn isoWeekdayNumber(self: Self) u3 { pub fn isoWeekdayNumber(self: Self) u3 {
return if (self == .Sun) 7 else @intFromEnum(self); return if (self == .Sun) 7 else @intFromEnum(self);
} }
pub fn weekdayFromDays(days: i32) DayOfWeek {
return @as(
DayOfWeek,
@enumFromInt(if (days >= -4) @rem(days + 4, 7) else @rem(days + 5, 7) + 6),
);
}
pub fn dayOfWeek(year: Year, month: Month, day: Day) Self {
const days = daysFromCivil(year, month, day);
return Self.weekdayFromDays(days);
}
}; };
pub fn weekdayDifference(start: DayOfWeek, end: DayOfWeek) u3 { pub fn weekdayDifference(start: DayOfWeek, end: DayOfWeek) u3 {
@ -836,13 +1269,6 @@ test "weekdayDifference" {
} }
} }
pub fn weekdayFromDays(z: i32) DayOfWeek {
return @as(
DayOfWeek,
@enumFromInt(if (z >= -4) @rem(z + 4, 7) else @rem(z + 5, 7) + 6),
);
}
test "weekdayFromDays" { test "weekdayFromDays" {
const tests = [_]struct { days: i32, result: DayOfWeek }{ const tests = [_]struct { days: i32, result: DayOfWeek }{
.{ .{
@ -864,7 +1290,7 @@ test "weekdayFromDays" {
}; };
for (tests) |case| { for (tests) |case| {
const result = weekdayFromDays(case.days); const result = DayOfWeek.weekdayFromDays(case.days);
try std.testing.expect(std.meta.eql(case.result, result)); try std.testing.expect(std.meta.eql(case.result, result));
} }
} }
@ -1000,7 +1426,7 @@ test "testPrintOrdinalSuperscript" {
inline for (cases) |case| { inline for (cases) |case| {
var buffer: [16]u8 = undefined; var buffer: [16]u8 = undefined;
var stream = std.io.fixedBufferStream(&buffer); var stream = std.io.fixedBufferStream(&buffer);
var writer = stream.writer(); const writer = stream.writer();
try printOrdinalSuperscript(writer, case.number); try printOrdinalSuperscript(writer, case.number);
std.testing.expect(std.mem.eql(u8, stream.getWritten(), case.result)) catch |err| { std.testing.expect(std.mem.eql(u8, stream.getWritten(), case.result)) catch |err| {
@ -1015,7 +1441,7 @@ fn printLongName(writer: anytype, index: u16, names: []const []const u8) !void {
} }
fn wrap(val: u16, at: u16) u16 { fn wrap(val: u16, at: u16) u16 {
var tmp = val % at; const tmp = val % at;
return if (tmp == 0) at else tmp; return if (tmp == 0) at else tmp;
} }
@ -1023,8 +1449,32 @@ test "asDateTime" {
_ = Instant.utc().asDateTime(); _ = Instant.utc().asDateTime();
} }
fn isLeap(year: i32) bool { pub fn isLeap(year: Year) bool {
return @rem(year, 4) == 0 and (@rem(year, 100) != 0 or @rem(year, 400) == 0); // taken from https://github.com/ziglang/zig/pull/18451
// In the western Gregorian Calendar leap a year is
// a multiple of 4, excluding multiples of 100, and
// adding multiples of 400. In code:
//
// if (@mod(year, 4) != 0)
// return false;
// if (@mod(year, 100) != 0)
// return true;
// return (0 == @mod(year, 400));
// The following is equivalent to the above
// but uses bitwise operations when testing
// for divisibility, masking with 3 as test
// for multiples of 4 and with 15 as a test
// for multiples of 16. Multiples of 16 and
// 100 are, conveniently, multiples of 400.
const mask: Year = switch (year % 100) {
0 => 0b1111,
else => 0b11,
};
return 0 == year & mask;
// return @rem(year, 4) == 0 and (@rem(year, 100) != 0 or @rem(year, 400) == 0);
} }
test "isLeap" { test "isLeap" {
@ -1034,36 +1484,36 @@ test "isLeap" {
try std.testing.expectEqual(true, isLeap(2400)); try std.testing.expectEqual(true, isLeap(2400));
} }
test "bigTest" { // test "bigTest" {
const ystart = -1000000; // const ystart = -1000000;
var prev_z: i32 = daysFromCivil(ystart, .Jan, 1) - 1; // var prev_z: i32 = daysFromCivil(ystart, .Jan, 1) - 1;
try std.testing.expect(prev_z < 0); // try std.testing.expect(prev_z < 0);
var prev_wd = weekdayFromDays(prev_z); // var prev_wd = DayOfWeek.weekdayFromDays(prev_z);
try std.testing.expect(0 <= @intFromEnum(prev_wd) and @intFromEnum(prev_wd) <= 6); // try std.testing.expect(0 <= @intFromEnum(prev_wd) and @intFromEnum(prev_wd) <= 6);
var y: Year = ystart; // var y: Year = ystart;
while (y <= -ystart) { // while (y <= -ystart) {
for ([_]Month{ .Jan, .Feb, .Mar, .Apr, .May, .Jun, .Jul, .Aug, .Sep, .Oct, .Nov, .Dec }) |m| { // for ([_]Month{ .Jan, .Feb, .Mar, .Apr, .May, .Jun, .Jul, .Aug, .Sep, .Oct, .Nov, .Dec }) |m| {
var d: Day = 1; // var d: Day = 1;
const e = m.lastDay(y); // const e = m.lastDay(y);
while (d <= e) { // while (d <= e) {
// std.debug.print("{d} {d} {d}\n", .{ y, @intFromEnum(m), d }); // // std.debug.print("{d} {d} {d}\n", .{ y, @intFromEnum(m), d });
const z = daysFromCivil(y, m, d); // const z = daysFromCivil(y, m, d);
// std.debug.print("{d} {d}\n", .{ prev_z, z }); // // std.debug.print("{d} {d}\n", .{ prev_z, z });
try std.testing.expect(prev_z < z); // try std.testing.expect(prev_z < z);
try std.testing.expect(z == prev_z + 1); // try std.testing.expect(z == prev_z + 1);
const date = civilFromDays(z); // const date = civilFromDays(z);
try std.testing.expect(y == date.year); // try std.testing.expect(y == date.year);
try std.testing.expect(m == date.month); // try std.testing.expect(m == date.month);
try std.testing.expect(d == date.day); // try std.testing.expect(d == date.day);
const wd = weekdayFromDays(z); // const wd = DayOfWeek.weekdayFromDays(z);
try std.testing.expect(0 <= @intFromEnum(wd) and @intFromEnum(wd) <= 6); // try std.testing.expect(0 <= @intFromEnum(wd) and @intFromEnum(wd) <= 6);
try std.testing.expect(wd == prev_wd.next()); // try std.testing.expect(wd == prev_wd.next());
try std.testing.expect(prev_wd == wd.prev()); // try std.testing.expect(prev_wd == wd.prev());
prev_z = z; // prev_z = z;
prev_wd = wd; // prev_wd = wd;
d += 1; // d += 1;
} // }
} // }
y += 1; // y += 1;
} // }
} // }