From 1acfbc6637f49ee16fa41a14e2f29897a99822af Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Mon, 1 Apr 2024 17:36:58 -0400 Subject: [PATCH 01/22] Replace epoch types with Date, Time, and DateTime. Necessary for DER parser upgrade. New types convert from Date/Time <-> epoch seconds. Old types only converted from epoch seconds -> Date. --- lib/std/time/epoch.zig | 443 ++++++++++++++++++++++++----------------- 1 file changed, 255 insertions(+), 188 deletions(-) diff --git a/lib/std/time/epoch.zig b/lib/std/time/epoch.zig index 631fa178e4bc..6cefafc9604a 100644 --- a/lib/std/time/epoch.zig +++ b/lib/std/time/epoch.zig @@ -1,56 +1,60 @@ -//! Epoch reference times in terms of their difference from -//! UTC 1970-01-01 in seconds. -const std = @import("../std.zig"); -const testing = std.testing; -const math = std.math; +//! Gregorian calendar date and time relative to unix time. +//! +//! Unix time is time since midnight 1970-01-01, ignoring leap seconds. +//! +//! Uses algorithms from https://howardhinnant.github.io/date_algorithms.html +pub const epoch_year = 1970; -/// Jan 01, 1970 AD -pub const posix = 0; -/// Jan 01, 1980 AD -pub const dos = 315532800; -/// Jan 01, 2001 AD -pub const ios = 978307200; -/// Nov 17, 1858 AD -pub const openvms = -3506716800; -/// Jan 01, 1900 AD -pub const zos = -2208988800; -/// Jan 01, 1601 AD -pub const windows = -11644473600; -/// Jan 01, 1978 AD -pub const amiga = 252460800; -/// Dec 31, 1967 AD -pub const pickos = -63244800; -/// Jan 06, 1980 AD -pub const gps = 315964800; -/// Jan 01, 0001 AD -pub const clr = -62135769600; +/// Using 32 bit arithmetic, overflow occurs approximately at +/- 5.8 million years. +/// Using 64 bit arithmetic overflow occurs far beyond +/- the age of the universe. +/// The intent is to make range checking superfluous. +pub const Int = i64; +// pub const UInt = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @typeInfo(Int).Int.bits } }); -pub const unix = posix; -pub const android = posix; -pub const os2 = dos; -pub const bios = dos; -pub const vfat = dos; -pub const ntfs = windows; -pub const ntp = zos; -pub const jbase = pickos; -pub const aros = amiga; -pub const morphos = amiga; -pub const brew = gps; -pub const atsc = gps; -pub const go = clr; +pub const Year = Int; +pub const MonthInt = IntFittingRange(1, 12); +pub const Month = enum(MonthInt) { + jan = 1, + feb = 2, + mar = 3, + apr = 4, + may = 5, + jun = 6, + jul = 7, + aug = 8, + sep = 9, + oct = 10, + nov = 11, + dec = 12, -/// The type that holds the current year, i.e. 2016 -pub const Year = u16; + /// Convenient conversion to `MonthInt`. jan = 1, dec = 12 + pub fn numeric(self: Month) MonthInt { + return @intFromEnum(self); + } -pub const epoch_year = 1970; -pub const secs_per_day: u17 = 24 * 60 * 60; - -pub fn isLeapYear(year: Year) bool { - if (@mod(year, 4) != 0) - return false; - if (@mod(year, 100) != 0) - return true; - return (0 == @mod(year, 400)); + pub fn days(self: Month, is_leap_year: bool) IntFittingRange(1, 31) { + return switch (self) { + .jan => 31, + .feb => @as(u5, switch (is_leap_year) { + .leap => 29, + .not_leap => 28, + }), + .mar => 31, + .apr => 30, + .may => 31, + .jun => 30, + .jul => 31, + .aug => 31, + .sep => 30, + .oct => 31, + .nov => 30, + .dec => 31, + }; + } +}; + +pub fn isLeapYear(y: Year) bool { + return @mod(y, 4) == 0 and (@mod(y, 100) != 0 or @mod(y, 400) == 0); } test isLeapYear { @@ -60,168 +64,231 @@ test isLeapYear { try testing.expectEqual(true, isLeapYear(2400)); } -pub fn getDaysInYear(year: Year) u9 { - return if (isLeapYear(year)) 366 else 365; -} +const secs_per_day = 24 * 60 * 60; -pub const YearLeapKind = enum(u1) { not_leap, leap }; +pub const Date = struct { + year: Year = epoch_year, + month: Month = .jan, + day: Day = 1, -pub const Month = enum(u4) { - jan = 1, - feb, - mar, - apr, - may, - jun, - jul, - aug, - sep, - oct, - nov, - dec, - - /// return the numeric calendar value for the given month - /// i.e. jan=1, feb=2, etc - pub fn numeric(self: Month) u4 { - return @intFromEnum(self); + pub const Day = IntFittingRange(1, 31); + + pub fn fromSeconds(epoch_seconds: Int) Date { + const days: Year = @divFloor(epoch_seconds, secs_per_day); + return fromEpochDays(days); } -}; -/// Get the number of days in the given month -pub fn getDaysInMonth(leap_year: YearLeapKind, month: Month) u5 { - return switch (month) { - .jan => 31, - .feb => @as(u5, switch (leap_year) { - .leap => 29, - .not_leap => 28, - }), - .mar => 31, - .apr => 30, - .may => 31, - .jun => 30, - .jul => 31, - .aug => 31, - .sep => 30, - .oct => 31, - .nov => 30, - .dec => 31, - }; -} + pub fn toSeconds(date: Date) Int { + return date.daysFromEpoch() * secs_per_day; + } + + pub fn daysFromEpoch(date: Date) Int { + const y = if (date.month.numeric() <= 2) date.year - 1 else date.year; + const m: Int = @intCast(date.month.numeric()); + const d = date.day; -pub const YearAndDay = struct { - year: Year, - /// The number of days into the year (0 to 365) - day: u9, - - pub fn calculateMonthDay(self: YearAndDay) MonthAndDay { - var month: Month = .jan; - var days_left = self.day; - const leap_kind: YearLeapKind = if (isLeapYear(self.year)) .leap else .not_leap; - while (true) { - const days_in_month = getDaysInMonth(leap_kind, month); - if (days_left < days_in_month) - break; - days_left -= days_in_month; - month = @as(Month, @enumFromInt(@intFromEnum(month) + 1)); - } - return .{ .month = month, .day_index = @as(u5, @intCast(days_left)) }; + const era = @divFloor(y, era_years); + const yoe = y - era * era_years; + const mp = @mod(m + 9, 12); + const doy = @divTrunc(153 * mp + 2, 5) + d - 1; + const doe = yoe * 365 + @divFloor(yoe, 4) - @divFloor(yoe, 100) + doy; + return era * era_days + doe - first_era; } -}; -pub const MonthAndDay = struct { - month: Month, - day_index: u5, // days into the month (0 to 30) + pub fn fromEpochDays(days: Int) Date { + const z = days + first_era; + const era: Int = @divFloor(z, era_days); + const doe = z - era * era_days; + const yoe = @divTrunc(doe - + @divTrunc(doe, 1460) + + @divTrunc(doe, 365 * 100 + 100 / 4 - 1) - + @divTrunc(doe, era_days - 1), 365); + const doy = doe - (365 * yoe + @divTrunc(yoe, 4) - @divTrunc(yoe, 100)); + const mp = @divTrunc(5 * doy + 2, 153); + const d: Day = @intCast(doy - @divTrunc(153 * mp + 2, 5) + 1); + const m = if (mp < 10) mp + 3 else mp - 9; + var y = yoe + era * era_years; + if (m <= 2) y += 1; + return .{ .year = y, .month = @enumFromInt(m), .day = d }; + } + + /// days between 0000-03-01 and 1970-01-01 + const first_era = 719468; + // Every 400 years the Gregorian calendar repeats. + const era_years = 400; + const era_days = 146097; }; -// days since epoch Oct 1, 1970 -pub const EpochDay = struct { - day: u47, // u47 = u64 - u17 (because day = sec(u64) / secs_per_day(u17) - pub fn calculateYearDay(self: EpochDay) YearAndDay { - var year_day = self.day; - var year: Year = epoch_year; - while (true) { - const year_size = getDaysInYear(year); - if (year_day < year_size) - break; - year_day -= year_size; - year += 1; - } - return .{ .year = year, .day = @as(u9, @intCast(year_day)) }; +pub const Time = struct { + hour: Hour = 0, + minute: Minute = 0, + second: Second = 0, + + pub const Hour = IntFittingRange(0, 23); + pub const Minute = IntFittingRange(0, 59); + pub const Second = IntFittingRange(0, 59); + + pub fn fromSeconds(seconds: Int) Time { + var day_seconds = std.math.comptimeMod(seconds, secs_per_day); + const DaySeconds = @TypeOf(day_seconds); + + const hour: Hour = @intCast(day_seconds / (60 * 60)); + day_seconds -= @as(DaySeconds, @intCast(hour)) * 60 * 60; + + const minute: Minute = @intCast(@divFloor(day_seconds, 60)); + day_seconds -= @as(DaySeconds, @intCast(minute)) * 60; + + return .{ .hour = hour, .minute = minute, .second = @intCast(day_seconds) }; } -}; -/// seconds since start of day -pub const DaySeconds = struct { - secs: u17, // max is 24*60*60 = 86400 + pub fn toSeconds(time: Time) Int { + var sec: Int = 0; + sec += @as(Int, time.hour) * 60 * 60; + sec += @as(Int, time.minute) * 60; + sec += @as(Int, time.second); - /// the number of hours past the start of the day (0 to 23) - pub fn getHoursIntoDay(self: DaySeconds) u5 { - return @as(u5, @intCast(@divTrunc(self.secs, 3600))); + return sec; } - /// the number of minutes past the hour (0 to 59) - pub fn getMinutesIntoHour(self: DaySeconds) u6 { - return @as(u6, @intCast(@divTrunc(@mod(self.secs, 3600), 60))); +}; + +pub const DateTime = struct { + year: Year = epoch_year, + month: Month = .jan, + day: Date.Day = 1, + hour: Time.Hour = 0, + minute: Time.Minute = 0, + second: Time.Second = 0, + + pub fn fromSeconds(epoch_seconds: Int) DateTime { + const date = Date.fromSeconds(epoch_seconds); + const time = Time.fromSeconds(epoch_seconds); + + return .{ + .year = date.year, + .month = date.month, + .day = date.day, + .hour = time.hour, + .minute = time.minute, + .second = time.second, + }; } - /// the number of seconds past the start of the minute (0 to 59) - pub fn getSecondsIntoMinute(self: DaySeconds) u6 { - return math.comptimeMod(self.secs, 60); + + pub fn toSeconds(dt: DateTime) Int { + const date = Date{ .year = dt.year, .month = dt.month, .day = dt.day }; + const time = Time{ .hour = dt.hour, .minute = dt.minute, .second = dt.second }; + return date.toSeconds() + time.toSeconds(); } }; -/// seconds since epoch Oct 1, 1970 at 12:00 AM -pub const EpochSeconds = struct { - secs: u64, +pub const Rfc3339 = struct { + pub fn parseDate(str: []const u8) !Date { + if (str.len != 10) return error.Parsing; + const Rfc3339Year = IntFittingRange(0, 9999); + const year = try std.fmt.parseInt(Rfc3339Year, str[0..4], 10); + if (str[4] != '-') return error.Parsing; + const month = try std.fmt.parseInt(MonthInt, str[5..7], 10); + if (str[7] != '-') return error.Parsing; + const day = try std.fmt.parseInt(Date.Day, str[8..10], 10); + return .{ .year = year, .month = @enumFromInt(month), .day = day }; + } - /// Returns the number of days since the epoch as an EpochDay. - /// Use EpochDay to get information about the day of this time. - pub fn getEpochDay(self: EpochSeconds) EpochDay { - return EpochDay{ .day = @as(u47, @intCast(@divTrunc(self.secs, secs_per_day))) }; + pub fn parseTime(str: []const u8) !Time { + if (str.len < 8) return error.Parsing; + + const hour = try std.fmt.parseInt(Time.Hour, str[0..2], 10); + if (str[2] != ':') return error.Parsing; + const minute = try std.fmt.parseInt(Time.Minute, str[3..5], 10); + if (str[5] != ':') return error.Parsing; + const second = try std.fmt.parseInt(Time.Second, str[6..8], 10); + // ignore optional subseconds + // ignore timezone + + return .{ .hour = hour, .minute = minute, .second = second }; } - /// Returns the number of seconds into the day as DaySeconds. - /// Use DaySeconds to get information about the time. - pub fn getDaySeconds(self: EpochSeconds) DaySeconds { - return DaySeconds{ .secs = math.comptimeMod(self.secs, secs_per_day) }; + pub fn parseDateTime(str: []const u8) !DateTime { + if (str.len < 10 + 1 + 8) return error.Parsing; + const date = try parseDate(str[0..10]); + if (str[10] != 'T') return error.Parsing; + const time = try parseTime(str[11..]); + return .{ + .year = date.year, + .month = date.month, + .day = date.day, + .hour = time.hour, + .minute = time.minute, + .second = time.second, + }; } }; -fn testEpoch(secs: u64, expected_year_day: YearAndDay, expected_month_day: MonthAndDay, expected_day_seconds: struct { - /// 0 to 23 - hours_into_day: u5, - /// 0 to 59 - minutes_into_hour: u6, - /// 0 to 59 - seconds_into_minute: u6, -}) !void { - const epoch_seconds = EpochSeconds{ .secs = secs }; - const epoch_day = epoch_seconds.getEpochDay(); - const day_seconds = epoch_seconds.getDaySeconds(); - const year_day = epoch_day.calculateYearDay(); - try testing.expectEqual(expected_year_day, year_day); - try testing.expectEqual(expected_month_day, year_day.calculateMonthDay()); - try testing.expectEqual(expected_day_seconds.hours_into_day, day_seconds.getHoursIntoDay()); - try testing.expectEqual(expected_day_seconds.minutes_into_hour, day_seconds.getMinutesIntoHour()); - try testing.expectEqual(expected_day_seconds.seconds_into_minute, day_seconds.getSecondsIntoMinute()); +fn comptimeParse(comptime time: []const u8) DateTime { + return Rfc3339.parseDateTime(time) catch unreachable; } -test "epoch decoding" { - try testEpoch(0, .{ .year = 1970, .day = 0 }, .{ - .month = .jan, - .day_index = 0, - }, .{ .hours_into_day = 0, .minutes_into_hour = 0, .seconds_into_minute = 0 }); - - try testEpoch(31535999, .{ .year = 1970, .day = 364 }, .{ - .month = .dec, - .day_index = 30, - }, .{ .hours_into_day = 23, .minutes_into_hour = 59, .seconds_into_minute = 59 }); - - try testEpoch(1622924906, .{ .year = 2021, .day = 31 + 28 + 31 + 30 + 31 + 4 }, .{ - .month = .jun, - .day_index = 4, - }, .{ .hours_into_day = 20, .minutes_into_hour = 28, .seconds_into_minute = 26 }); - - try testEpoch(1625159473, .{ .year = 2021, .day = 31 + 28 + 31 + 30 + 31 + 30 }, .{ - .month = .jul, - .day_index = 0, - }, .{ .hours_into_day = 17, .minutes_into_hour = 11, .seconds_into_minute = 13 }); +/// Tests EpochSeconds -> DateTime and DateTime -> EpochSeconds +fn testEpoch(secs: Int, dt: DateTime) !void { + const actual_dt = DateTime.fromSeconds(secs); + try std.testing.expectEqualDeep(dt, actual_dt); + + const actual_secs = actual_dt.toSeconds(); + try std.testing.expectEqual(secs, actual_secs); } + +test DateTime { + // $ date -d @31535999 --iso-8601=seconds + try testEpoch(0, .{}); + try testEpoch(31535999, comptimeParse("1970-12-31T23:59:59")); + try testEpoch(1622924906, comptimeParse("2021-06-05T20:28:26")); + try testEpoch(1625159473, comptimeParse("2021-07-01T17:11:13")); + // Washington bday, N.S. + try testEpoch(-7506041400, comptimeParse("1732-02-22T12:30:00")); + // outside Rfc3339 range + try testEpoch(-97506041400, .{ + .year = -1120, + .month = .feb, + .day = 26, + .hour = 20, + .minute = 30, + .second = 0, + }); +} + +const std = @import("../std.zig"); +const testing = std.testing; +const IntFittingRange = std.math.IntFittingRange; + +/// Jan 01, 1970 AD +pub const posix = 0; +/// Jan 01, 1980 AD +pub const dos = 315532800; +/// Jan 01, 2001 AD +pub const ios = 978307200; +/// Nov 17, 1858 AD +pub const openvms = -3506716800; +/// Jan 01, 1900 AD +pub const zos = -2208988800; +/// Jan 01, 1601 AD +pub const windows = -11644473600; +/// Jan 01, 1978 AD +pub const amiga = 252460800; +/// Dec 31, 1967 AD +pub const pickos = -63244800; +/// Jan 06, 1980 AD +pub const gps = 315964800; +/// Jan 01, 0001 AD +pub const clr = -62135769600; + +pub const unix = posix; +pub const android = posix; +pub const os2 = dos; +pub const bios = dos; +pub const vfat = dos; +pub const ntfs = windows; +pub const ntp = zos; +pub const jbase = pickos; +pub const aros = amiga; +pub const morphos = amiga; +pub const brew = gps; +pub const atsc = gps; +pub const go = clr; From da0a0276ecb74d0d66613c1d5a0082078be7e160 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Wed, 3 Apr 2024 20:41:01 -0400 Subject: [PATCH 02/22] make date and time generic. remove rfc 3339 --- lib/std/date.zig | 15 ++ lib/std/date/epoch.zig | 41 +++++ lib/std/date/gregorian.zig | 303 +++++++++++++++++++++++++++++++++++++ lib/std/date_time.zig | 122 +++++++++++++++ lib/std/math.zig | 15 +- lib/std/std.zig | 2 + lib/std/time.zig | 65 +++++++- lib/std/time/epoch.zig | 294 ----------------------------------- 8 files changed, 560 insertions(+), 297 deletions(-) create mode 100644 lib/std/date.zig create mode 100644 lib/std/date/epoch.zig create mode 100644 lib/std/date/gregorian.zig create mode 100644 lib/std/date_time.zig delete mode 100644 lib/std/time/epoch.zig diff --git a/lib/std/date.zig b/lib/std/date.zig new file mode 100644 index 000000000000..6568a174c891 --- /dev/null +++ b/lib/std/date.zig @@ -0,0 +1,15 @@ +pub const gregorian = @import("./date/gregorian.zig"); + +pub fn Date(comptime Year: type) type { + return gregorian.Date(Year, epoch.posix); +} + +pub const Date16 = Date(i16); +pub const Date32 = Date(i32); +pub const Date64 = Date(i64); + +test { + _ = gregorian; +} + +const epoch = @import("./date/epoch.zig"); diff --git a/lib/std/date/epoch.zig b/lib/std/date/epoch.zig new file mode 100644 index 000000000000..f253e1f81c05 --- /dev/null +++ b/lib/std/date/epoch.zig @@ -0,0 +1,41 @@ +//! Days since 1970-01-01 for various calendar systems. +//! +//! 1970-01-01 is Zig's chosen epoch for timestamp functions. + +// To build this list yourself using `date`: +// $ echo $(( $(date -d "YYYY-mm-dd UTC" +%s) / (60*60*24) )) + +/// 1970-01-01 +pub const posix = 0; +/// 1980-01-01 +pub const dos = 3_652; +/// 2001-01-01 +pub const ios = 11_323; +/// 1858-11-17 +pub const openvms = -40_587; +/// 1900-01-01 +pub const zos = -25_567; +/// 1601-01-01 +pub const windows = -134_774; +/// 1978-01-01 +pub const amiga = 2_922; +/// 1967-12-31 +pub const pickos = -732; +/// 1980-01-06 +pub const gps = 3_657; +/// 0001-01-01 +pub const clr = -719_164; + +pub const unix = posix; +pub const android = posix; +pub const os2 = dos; +pub const bios = dos; +pub const vfat = dos; +pub const ntfs = windows; +pub const ntp = zos; +pub const jbase = pickos; +pub const aros = amiga; +pub const morphos = amiga; +pub const brew = gps; +pub const atsc = gps; +pub const go = clr; diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig new file mode 100644 index 000000000000..c2f984356b73 --- /dev/null +++ b/lib/std/date/gregorian.zig @@ -0,0 +1,303 @@ +//! Proleptic (projected backwards) Gregorian calendar date and time. +//! +//! Introduced in 1582 as a revision of the Julian calendar. + +/// A Gregorian calendar date with: +/// * A `year: YearT,` which may be unsigned and is relative to 0000-00-00. +/// * Conversion from and to an `EpochDays` type which is the number of days since `epoch`. +/// The conversion algorithm used is Euclidean Affine Functions by Neri and Schneider. [1] +/// It has been chosen for its speed. +/// * A carefully selected epoch `shift` which allows for fast unsigned arithmetic at the cost +/// of moving the supported range of `YearT`. +/// +/// This implementation requires the `EpochDay` range cover all possible values of `YearT`. +/// Providing an invalid combination of `epoch` and `shift` will trigger a comptime assertion. +/// +/// To solve for `shift`, see `solve_shift`. +/// +/// [1] https://onlinelibrary.wiley.com/doi/epdf/10.1002/spe.3172 +pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_int) type { + // Zig's timestamp epoch is 1970-01-01. Ours is Mar 01, 0000 AD + const epoch = epoch_ + 719_468; + return struct { + year: Year, + month: MonthT, + day: Day, + + pub const Year = YearT; + pub const Month = MonthT; + pub const Day = IntFittingRange(1, 31); + + pub const EpochDays = MakeEpochDays(Year); + const UEpochDays = std.meta.Int(.unsigned, @typeInfo(EpochDays).Int.bits); + + const K = epoch + era_days * shift; + const L = era * shift; + /// Minimum epoch day representable by `Year` + pub const min_day = -K; + /// Maximum epoch day representable by `Year` + pub const max_day = (std.math.maxInt(EpochDays) - 3) / 4 - K; + + // Ensure `toEpochDays` won't cause overflow. + // + // If you trigger these assertions, try choosing a different value of `shift`. + comptime { + std.debug.assert(-L < std.math.minInt(Year)); + std.debug.assert(max_day / days_in_year.numerator - L + 1 > std.math.maxInt(Year)); + } + + const Computational = struct { + year: UEpochDays, + month: UIntFitting(14), + day: UIntFitting(30), + + fn toGregorian(self: Computational, N_Y: UIntFitting(365)) Self { + const last_day_of_jan = 306; + const J: UEpochDays = if (N_Y >= last_day_of_jan) 1 else 0; + + const month: MonthInt = if (J != 0) self.month - 12 else self.month; + + return .{ + .year = @intCast(@as(EpochDays, @bitCast(self.year +% J -% L))), + .month = @enumFromInt(month), + .day = @as(Day, self.day) + 1, + }; + } + + fn fromGregorian(date: Self) Computational { + const month: UIntFitting(14) = date.month.numeric(); + const Y_G: UEpochDays = @bitCast(@as(EpochDays, @intCast(date.year))); + const J: UEpochDays = if (month <= 2) 1 else 0; + + return .{ + .year = Y_G +% L -% J, + .month = if (J != 0) month + 12 else month, + .day = date.day - 1, + }; + } + }; + + const Self = @This(); + + pub fn order(self: Self, other: Self) std.math.Order { + return difference(self.year, other.year) orelse difference(self.month.numeric(), other.month.numeric()) orelse difference(self.day, other.day) orelse .eq; + } + + pub fn fromEpoch(days: EpochDays) Self { + // This function is Figure 12 of the paper. + // Besides being ported from C++, the following has changed: + // - Seperate Year and UEpochDays types + // - Rewrite EAFs in terms of `a` and `b` + // - Add EAF bounds assertions + // - Use bounded int types provided in Section 10 instead of u32 and u64 + // - Add computational calendar struct type + // - Add comments referencing some proofs + // + // While these changes should allow the reader to understand _how_ these functions + // work, I recommend reading the paper to understand *why*. + assert(days > min_day); + assert(days < max_day); + const mod = std.math.comptimeMod; + const div = std.math.comptimeDivFloor; + + const N = @as(UEpochDays, @bitCast(days)) +% K; + + const a1 = 4; + const b1 = 3; + const N_1 = a1 * N + b1; + const C = N_1 / era_days; + const N_C: UIntFitting(36_564) = div(mod(N_1, era_days), a1); + + const N_2 = a1 * @as(UIntFitting(146_099), N_C) + b1; + // n % 1461 == 2939745 * n % 2^32 / 2939745, + // for all n in [0, 28825529) + assert(N_2 < 28_825_529); + const a2 = 2_939_745; + const b2 = 0; + const P_2_max = 429493804755; + const P_2 = a2 * @as(UIntFitting(P_2_max), N_2) + b2; + const Z: UIntFitting(99) = div(P_2, (1 << 32)); + const N_Y: UIntFitting(365) = div(mod(P_2, (1 << 32)), a2 * a1); + + // (5 * n + 461) / 153 == (2141 * n + 197913) /2^16, + // for all n in [0, 734) + assert(N_Y < 734); + const a3 = 2_141; + const b3 = 197_913; + const N_3 = a3 * @as(UIntFitting(979_378), N_Y) + b3; + + return (Computational{ + .year = 100 * C + Z, + .month = div(N_3, 1 << 16), + .day = div(mod(N_3, (1 << 16)), a3), + }).toGregorian(N_Y); + } + + pub fn toEpoch(self: Self) EpochDays { + // This function is Figure 13 of the paper. + const c = Computational.fromGregorian(self); + const C = c.year / 100; + + const y_star = days_in_year.numerator * c.year / 4 - C + C / 4; + const days_in_5mo = 31 + 30 + 31 + 30 + 31; + const m_star = (days_in_5mo * @as(UEpochDays, c.month) - 457) / 5; + const N = y_star + m_star + c.day; + + return @as(EpochDays, @intCast(N)) - K; + } + }; +} + +/// Epoch is days since 1970 +pub fn Date(comptime Year: type, epoch: comptime_int) type { + const shift = solve_shift(Year, epoch) catch unreachable; + return DateAdvanced(Year, epoch, shift); +} + +test Date { + const T = Date(i16, 0); + const d1 = T{ .year = 1970, .month = .jan, .day = 1 }; + const d2 = T{ .year = 1971, .month = .jan, .day = 1 }; + + try expectEqual(d1.order(d2), .lt); + try expectEqual(365, d2.toEpoch() - d1.toEpoch()); +} + +const MonthInt = IntFittingRange(1, 12); +const MonthT = enum(MonthInt) { + jan = 1, + feb = 2, + mar = 3, + apr = 4, + may = 5, + jun = 6, + jul = 7, + aug = 8, + sep = 9, + oct = 10, + nov = 11, + dec = 12, + + pub const Int = MonthInt; + pub const Days = IntFittingRange(28, 31); + + /// Convenient conversion to `MonthInt`. jan = 1, dec = 12 + pub fn numeric(self: MonthT) MonthInt { + return @intFromEnum(self); + } + + pub fn days(self: MonthT, is_leap_year: bool) Days { + const m: Days = @intCast(self.numeric()); + return if (m != 2) + 30 | (m ^ (m >> 3)) + else if (is_leap_year) + 29 + else + 28; + } +}; + +test MonthT { + try expectEqual(31, MonthT.jan.days(false)); + try expectEqual(29, MonthT.feb.days(true)); + try expectEqual(28, MonthT.feb.days(false)); + try expectEqual(31, MonthT.mar.days(false)); + try expectEqual(30, MonthT.apr.days(false)); + try expectEqual(31, MonthT.may.days(false)); + try expectEqual(30, MonthT.jun.days(false)); + try expectEqual(31, MonthT.jul.days(false)); + try expectEqual(31, MonthT.aug.days(false)); + try expectEqual(30, MonthT.sep.days(false)); + try expectEqual(31, MonthT.oct.days(false)); + try expectEqual(30, MonthT.nov.days(false)); + try expectEqual(31, MonthT.dec.days(false)); +} + +pub fn is_leap(year: anytype) bool { + return if (@mod(year, 25) != 0) + year & (4 - 1) == 0 + else + year & (16 - 1) == 0; +} + +test is_leap { + try expectEqual(false, is_leap(2095)); + try expectEqual(true, is_leap(2096)); + try expectEqual(false, is_leap(2100)); + try expectEqual(true, is_leap(2400)); +} + +fn days_between_years(from: usize, to: usize) usize { + var res: usize = 0; + var i: usize = from; + while (i < to) : (i += 1) { + res += if (is_leap(i)) 366 else 365; + } + return res; +} + +test days_between_years { + try expectEqual(366, days_between_years(2000, 2001)); + try expectEqual(146_097, days_between_years(0, 400)); +} + +/// Returns .gt, .lt, or null +fn difference(a: anytype, b: anytype) ?std.math.Order { + const res = std.math.order(a, b); + if (res != .eq) return res; + return null; +} + +/// The Gregorian calendar repeats every 400 years. +const era = 400; +const era_days: comptime_int = days_between_years(0, 400); // 146_097 + +/// Number of days between two consecutive March equinoxes +const days_in_year = struct { + const actual = 365.2424; + // .0001 days per year of error. + const numerator = 1_461; + const denominator = 4; +}; + +/// Int type to represent all possible days between minimum `Year` and maximum `Year`. +/// Rounded up to nearest power of 2 to meet unsigned division assertions. +fn MakeEpochDays(comptime Year: type) type { + const year_range = (std.math.maxInt(Year) - std.math.minInt(Year)) * days_in_year.actual; + const required_int_bits = @ceil(std.math.log2(year_range)); + const int_bits = std.math.ceilPowerOfTwoAssert(u16, @intFromFloat(required_int_bits)); + return std.meta.Int(.signed, int_bits); +} + +fn UIntFitting(to: comptime_int) type { + return IntFittingRange(0, to); +} + +/// Finds minimum epoch shift that covers the range: +/// [std.math.minInt(YearT), std.math.maxInt(YearT)] +fn solve_shift(comptime Year: type, epoch: comptime_int) !comptime_int { + const shift = std.math.maxInt(Year) / era + 1; + + const L = era * shift; + const K = epoch + era_days * shift; + const EpochDays = MakeEpochDays(Year); + const max_day = (std.math.maxInt(EpochDays) - 3) / 4 - K; + + if (@divFloor(max_day, days_in_year.numerator) - L + 1 <= std.math.maxInt(Year)) + return error.Constraint; // if you hit this write a system of equations solver here to prove + // there are no possible values of `shift` + + return shift; +} + +test solve_shift { + try expectEqual(82, try solve_shift(i16, 719_468)); + try expectEqual(5_368_710, try solve_shift(i32, 719_468)); + try expectEqual(23_058_430_092_136_940, try solve_shift(i64, 719_468)); +} + +const std = @import("std"); +const IntFittingRange = std.math.IntFittingRange; +const secs_per_day = std.time.s_per_day; +const expectEqual = std.testing.expectEqual; +const assert = std.debug.assert; diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig new file mode 100644 index 000000000000..1e0d98b51990 --- /dev/null +++ b/lib/std/date_time.zig @@ -0,0 +1,122 @@ +pub fn DateTime(comptime Year: type, time_precision: comptime_int) type { + return struct { + date: Date, + time: Time = .{}, + + pub const Date = date_mod.Date(Year); + pub const Time = time_mod.Time(time_precision); + pub const EpochSeconds = i64; + + const Self = @This(); + + pub fn fromEpoch(seconds: EpochSeconds) Self { + const days = @divFloor(seconds, s_per_day * Time.fs_per_s); + const new_date = Date.fromEpoch(@intCast(days)); + const day_seconds = std.math.comptimeMod(seconds, s_per_day * Time.fs_per_s); + const new_time = Time.fromDayFractionalSeconds(day_seconds); + return .{ .date = new_date, .time = new_time }; + } + + pub fn toEpoch(self: Self) EpochSeconds { + var res: EpochSeconds = 0; + res += @as(EpochSeconds, self.date.toEpoch()) * s_per_day * Time.fs_per_s; + res += self.time.toDayFractionalSeconds(); + return res; + } + }; +} + +pub const Date16Time = DateTime(i16, 0); +comptime { + assert(@sizeOf(Date16Time) == 8); +} + +/// Tests EpochSeconds -> DateTime and DateTime -> EpochSeconds +fn testEpoch(secs: i64, dt: Date16Time) !void { + const actual_dt = Date16Time.fromEpoch(secs); + try std.testing.expectEqualDeep(dt, actual_dt); + try std.testing.expectEqual(secs, dt.toEpoch()); +} + +test Date16Time { + // $ date -d @31535999 --iso-8601=seconds + try testEpoch(0, .{ .date = .{ .year = 1970, .month = .jan, .day = 1 } }); + try testEpoch(31535999, .{ + .date = .{ .year = 1970, .month = .dec, .day = 31 }, + .time = .{ .hour = 23, .minute = 59, .second = 59 }, + }); + try testEpoch(1622924906, .{ + .date = .{ .year = 2021, .month = .jun, .day = 5 }, + .time = .{ .hour = 20, .minute = 28, .second = 26 }, + }); + try testEpoch(1625159473, .{ + .date = .{ .year = 2021, .month = .jul, .day = 1 }, + .time = .{ .hour = 17, .minute = 11, .second = 13 }, + }); + // Washington bday, proleptic + try testEpoch(-7506041400, .{ + .date = .{ .year = 1732, .month = .feb, .day = 22 }, + .time = .{ .hour = 12, .minute = 30 }, + }); + // negative year + try testEpoch(-97506041400, .{ + .date = .{ .year = -1120, .month = .feb, .day = 26 }, + .time = .{ .hour = 20, .minute = 30, .second = 0 }, + }); + // minimum date + try testEpoch(-1096225401600, .{ + .date = .{ .year = std.math.minInt(i16), .month = .jan, .day = 1 }, + }); +} + +// pub const Rfc3339 = struct { +// pub fn parseDate(str: []const u8) !Date { +// if (str.len != 10) return error.Parsing; +// const Rfc3339Year = IntFittingRange(0, 9999); +// const year = try std.fmt.parseInt(Rfc3339Year, str[0..4], 10); +// if (str[4] != '-') return error.Parsing; +// const month = try std.fmt.parseInt(MonthInt, str[5..7], 10); +// if (str[7] != '-') return error.Parsing; +// const day = try std.fmt.parseInt(Date.Day, str[8..10], 10); +// return .{ .year = year, .month = @enumFromInt(month), .day = day }; +// } +// +// pub fn parseTime(str: []const u8) !Time { +// if (str.len < 8) return error.Parsing; +// +// const hour = try std.fmt.parseInt(Time.Hour, str[0..2], 10); +// if (str[2] != ':') return error.Parsing; +// const minute = try std.fmt.parseInt(Time.Minute, str[3..5], 10); +// if (str[5] != ':') return error.Parsing; +// const second = try std.fmt.parseInt(Time.Second, str[6..8], 10); +// // ignore optional subseconds +// // ignore timezone +// +// return .{ .hour = hour, .minute = minute, .second = second }; +// } +// +// pub fn parseDateTime(str: []const u8) !DateTime { +// if (str.len < 10 + 1 + 8) return error.Parsing; +// const date = try parseDate(str[0..10]); +// if (str[10] != 'T') return error.Parsing; +// const time = try parseTime(str[11..]); +// return .{ +// .year = date.year, +// .month = date.month, +// .day = date.day, +// .hour = time.hour, +// .minute = time.minute, +// .second = time.second, +// }; +// } +// }; +// +// fn comptimeParse(comptime time: []const u8) DateTime { +// return Rfc3339.parseDateTime(time) catch unreachable; +// } + +const std = @import("std.zig"); +const date_mod = @import("./date.zig"); +const time_mod = @import("./time.zig"); +const s_per_day = time_mod.s_per_day; +const assert = std.debug.assert; diff --git a/lib/std/math.zig b/lib/std/math.zig index 19dd82ec6798..6187b575389a 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -1770,7 +1770,20 @@ test boolMask { /// Return the mod of `num` with the smallest integer type pub fn comptimeMod(num: anytype, comptime denom: comptime_int) IntFittingRange(0, denom - 1) { - return @as(IntFittingRange(0, denom - 1), @intCast(@mod(num, denom))); + return @intCast(@mod(num, denom)); +} + +fn ComptimeDiv(comptime Num: type, comptime divisor: comptime_int) type { + const info = @typeInfo(Num).Int; + var n_bits = info.bits; + n_bits -= log2_int(usize, divisor); + + return std.meta.Int(info.signedness, n_bits); +} + +/// Return the quotient of `num` with the smallest integer type +pub fn comptimeDivFloor(num: anytype, comptime divisor: comptime_int) ComptimeDiv(@TypeOf(num), divisor) { + return @intCast(@divFloor(num, divisor)); } pub const F80 = struct { diff --git a/lib/std/std.zig b/lib/std/std.zig index 8aa12ff31a47..1e2a6e084974 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -64,6 +64,8 @@ pub const coff = @import("coff.zig"); pub const compress = @import("compress.zig"); pub const comptime_string_map = @import("comptime_string_map.zig"); pub const crypto = @import("crypto.zig"); +pub const date = @import("date.zig"); +pub const date_time = @import("date_time.zig"); pub const debug = @import("debug.zig"); pub const dwarf = @import("dwarf.zig"); pub const elf = @import("elf.zig"); diff --git a/lib/std/time.zig b/lib/std/time.zig index e8b37d3010ee..a3dbd1a4cb2a 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -1,12 +1,12 @@ const std = @import("std.zig"); const builtin = @import("builtin"); +const epoch = @import("./date/epoch.zig"); const assert = std.debug.assert; const testing = std.testing; const math = std.math; const windows = std.os.windows; const posix = std.posix; - -pub const epoch = @import("time/epoch.zig"); +const IntFittingRange = std.math.IntFittingRange; /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn sleep(nanoseconds: u64) void { @@ -344,6 +344,67 @@ test Timer { try testing.expect(timer.read() < time_1); } +pub fn Time(precision_: comptime_int) type { + const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); + return struct { + hour: Hour = 0, + minute: Minute = 0, + second: Second = 0, + /// Milliseconds, microseconds, or nanoseconds. + fractional_second: FractionalSecond = 0, + + pub const Hour = IntFittingRange(0, 23); + pub const Minute = IntFittingRange(0, 59); + pub const Second = IntFittingRange(0, 59); + pub const FractionalSecond = IntFittingRange(0, if (precision_ == 0) 0 else multiplier); + pub const DayFractionalSeconds = IntFittingRange(0, s_per_day * multiplier); + + const Self = @This(); + + pub const precision = precision_; + pub const fs_per_hour = 60 * 60 * multiplier; + pub const fs_per_minute = 60 * multiplier; + pub const fs_per_s = multiplier; + + pub fn fromDayFractionalSeconds(fractional_seconds: DayFractionalSeconds) Self { + var fs = fractional_seconds; + + const hour: Hour = @intCast(@divFloor(fs, fs_per_hour)); + fs -= @as(DayFractionalSeconds, @intCast(hour)) * fs_per_hour; + + const minute: Minute = @intCast(@divFloor(fs, fs_per_minute)); + fs -= @as(DayFractionalSeconds, @intCast(minute)) * fs_per_minute; + + const second: Second = @intCast(@divFloor(fs, fs_per_s)); + fs -= @as(DayFractionalSeconds, @intCast(second)) * fs_per_s; + + return .{ + .hour = hour, + .minute = minute, + .second = second, + .fractional_second = @intCast(fs), + }; + } + + pub fn toDayFractionalSeconds(self: Self) DayFractionalSeconds { + var sec: DayFractionalSeconds = 0; + sec += @as(DayFractionalSeconds, self.hour) * fs_per_hour; + sec += @as(DayFractionalSeconds, self.minute) * fs_per_minute; + sec += @as(DayFractionalSeconds, self.second) * fs_per_s; + sec += @as(DayFractionalSeconds, self.fractional_second); + + return sec; + } + }; +} + +comptime { + assert(@sizeOf(Time(0)) == 3); + assert(@sizeOf(Time(3)) == 6); + assert(@sizeOf(Time(6)) == 8); + assert(@sizeOf(Time(9)) == 8); +} + test { _ = epoch; } diff --git a/lib/std/time/epoch.zig b/lib/std/time/epoch.zig deleted file mode 100644 index 6cefafc9604a..000000000000 --- a/lib/std/time/epoch.zig +++ /dev/null @@ -1,294 +0,0 @@ -//! Gregorian calendar date and time relative to unix time. -//! -//! Unix time is time since midnight 1970-01-01, ignoring leap seconds. -//! -//! Uses algorithms from https://howardhinnant.github.io/date_algorithms.html -pub const epoch_year = 1970; - -/// Using 32 bit arithmetic, overflow occurs approximately at +/- 5.8 million years. -/// Using 64 bit arithmetic overflow occurs far beyond +/- the age of the universe. -/// The intent is to make range checking superfluous. -pub const Int = i64; -// pub const UInt = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @typeInfo(Int).Int.bits } }); - -pub const Year = Int; -pub const MonthInt = IntFittingRange(1, 12); -pub const Month = enum(MonthInt) { - jan = 1, - feb = 2, - mar = 3, - apr = 4, - may = 5, - jun = 6, - jul = 7, - aug = 8, - sep = 9, - oct = 10, - nov = 11, - dec = 12, - - /// Convenient conversion to `MonthInt`. jan = 1, dec = 12 - pub fn numeric(self: Month) MonthInt { - return @intFromEnum(self); - } - - pub fn days(self: Month, is_leap_year: bool) IntFittingRange(1, 31) { - return switch (self) { - .jan => 31, - .feb => @as(u5, switch (is_leap_year) { - .leap => 29, - .not_leap => 28, - }), - .mar => 31, - .apr => 30, - .may => 31, - .jun => 30, - .jul => 31, - .aug => 31, - .sep => 30, - .oct => 31, - .nov => 30, - .dec => 31, - }; - } -}; - -pub fn isLeapYear(y: Year) bool { - return @mod(y, 4) == 0 and (@mod(y, 100) != 0 or @mod(y, 400) == 0); -} - -test isLeapYear { - try testing.expectEqual(false, isLeapYear(2095)); - try testing.expectEqual(true, isLeapYear(2096)); - try testing.expectEqual(false, isLeapYear(2100)); - try testing.expectEqual(true, isLeapYear(2400)); -} - -const secs_per_day = 24 * 60 * 60; - -pub const Date = struct { - year: Year = epoch_year, - month: Month = .jan, - day: Day = 1, - - pub const Day = IntFittingRange(1, 31); - - pub fn fromSeconds(epoch_seconds: Int) Date { - const days: Year = @divFloor(epoch_seconds, secs_per_day); - return fromEpochDays(days); - } - - pub fn toSeconds(date: Date) Int { - return date.daysFromEpoch() * secs_per_day; - } - - pub fn daysFromEpoch(date: Date) Int { - const y = if (date.month.numeric() <= 2) date.year - 1 else date.year; - const m: Int = @intCast(date.month.numeric()); - const d = date.day; - - const era = @divFloor(y, era_years); - const yoe = y - era * era_years; - const mp = @mod(m + 9, 12); - const doy = @divTrunc(153 * mp + 2, 5) + d - 1; - const doe = yoe * 365 + @divFloor(yoe, 4) - @divFloor(yoe, 100) + doy; - return era * era_days + doe - first_era; - } - - pub fn fromEpochDays(days: Int) Date { - const z = days + first_era; - const era: Int = @divFloor(z, era_days); - const doe = z - era * era_days; - const yoe = @divTrunc(doe - - @divTrunc(doe, 1460) + - @divTrunc(doe, 365 * 100 + 100 / 4 - 1) - - @divTrunc(doe, era_days - 1), 365); - const doy = doe - (365 * yoe + @divTrunc(yoe, 4) - @divTrunc(yoe, 100)); - const mp = @divTrunc(5 * doy + 2, 153); - const d: Day = @intCast(doy - @divTrunc(153 * mp + 2, 5) + 1); - const m = if (mp < 10) mp + 3 else mp - 9; - var y = yoe + era * era_years; - if (m <= 2) y += 1; - return .{ .year = y, .month = @enumFromInt(m), .day = d }; - } - - /// days between 0000-03-01 and 1970-01-01 - const first_era = 719468; - // Every 400 years the Gregorian calendar repeats. - const era_years = 400; - const era_days = 146097; -}; - -pub const Time = struct { - hour: Hour = 0, - minute: Minute = 0, - second: Second = 0, - - pub const Hour = IntFittingRange(0, 23); - pub const Minute = IntFittingRange(0, 59); - pub const Second = IntFittingRange(0, 59); - - pub fn fromSeconds(seconds: Int) Time { - var day_seconds = std.math.comptimeMod(seconds, secs_per_day); - const DaySeconds = @TypeOf(day_seconds); - - const hour: Hour = @intCast(day_seconds / (60 * 60)); - day_seconds -= @as(DaySeconds, @intCast(hour)) * 60 * 60; - - const minute: Minute = @intCast(@divFloor(day_seconds, 60)); - day_seconds -= @as(DaySeconds, @intCast(minute)) * 60; - - return .{ .hour = hour, .minute = minute, .second = @intCast(day_seconds) }; - } - - pub fn toSeconds(time: Time) Int { - var sec: Int = 0; - sec += @as(Int, time.hour) * 60 * 60; - sec += @as(Int, time.minute) * 60; - sec += @as(Int, time.second); - - return sec; - } -}; - -pub const DateTime = struct { - year: Year = epoch_year, - month: Month = .jan, - day: Date.Day = 1, - hour: Time.Hour = 0, - minute: Time.Minute = 0, - second: Time.Second = 0, - - pub fn fromSeconds(epoch_seconds: Int) DateTime { - const date = Date.fromSeconds(epoch_seconds); - const time = Time.fromSeconds(epoch_seconds); - - return .{ - .year = date.year, - .month = date.month, - .day = date.day, - .hour = time.hour, - .minute = time.minute, - .second = time.second, - }; - } - - pub fn toSeconds(dt: DateTime) Int { - const date = Date{ .year = dt.year, .month = dt.month, .day = dt.day }; - const time = Time{ .hour = dt.hour, .minute = dt.minute, .second = dt.second }; - return date.toSeconds() + time.toSeconds(); - } -}; - -pub const Rfc3339 = struct { - pub fn parseDate(str: []const u8) !Date { - if (str.len != 10) return error.Parsing; - const Rfc3339Year = IntFittingRange(0, 9999); - const year = try std.fmt.parseInt(Rfc3339Year, str[0..4], 10); - if (str[4] != '-') return error.Parsing; - const month = try std.fmt.parseInt(MonthInt, str[5..7], 10); - if (str[7] != '-') return error.Parsing; - const day = try std.fmt.parseInt(Date.Day, str[8..10], 10); - return .{ .year = year, .month = @enumFromInt(month), .day = day }; - } - - pub fn parseTime(str: []const u8) !Time { - if (str.len < 8) return error.Parsing; - - const hour = try std.fmt.parseInt(Time.Hour, str[0..2], 10); - if (str[2] != ':') return error.Parsing; - const minute = try std.fmt.parseInt(Time.Minute, str[3..5], 10); - if (str[5] != ':') return error.Parsing; - const second = try std.fmt.parseInt(Time.Second, str[6..8], 10); - // ignore optional subseconds - // ignore timezone - - return .{ .hour = hour, .minute = minute, .second = second }; - } - - pub fn parseDateTime(str: []const u8) !DateTime { - if (str.len < 10 + 1 + 8) return error.Parsing; - const date = try parseDate(str[0..10]); - if (str[10] != 'T') return error.Parsing; - const time = try parseTime(str[11..]); - return .{ - .year = date.year, - .month = date.month, - .day = date.day, - .hour = time.hour, - .minute = time.minute, - .second = time.second, - }; - } -}; - -fn comptimeParse(comptime time: []const u8) DateTime { - return Rfc3339.parseDateTime(time) catch unreachable; -} - -/// Tests EpochSeconds -> DateTime and DateTime -> EpochSeconds -fn testEpoch(secs: Int, dt: DateTime) !void { - const actual_dt = DateTime.fromSeconds(secs); - try std.testing.expectEqualDeep(dt, actual_dt); - - const actual_secs = actual_dt.toSeconds(); - try std.testing.expectEqual(secs, actual_secs); -} - -test DateTime { - // $ date -d @31535999 --iso-8601=seconds - try testEpoch(0, .{}); - try testEpoch(31535999, comptimeParse("1970-12-31T23:59:59")); - try testEpoch(1622924906, comptimeParse("2021-06-05T20:28:26")); - try testEpoch(1625159473, comptimeParse("2021-07-01T17:11:13")); - // Washington bday, N.S. - try testEpoch(-7506041400, comptimeParse("1732-02-22T12:30:00")); - // outside Rfc3339 range - try testEpoch(-97506041400, .{ - .year = -1120, - .month = .feb, - .day = 26, - .hour = 20, - .minute = 30, - .second = 0, - }); -} - -const std = @import("../std.zig"); -const testing = std.testing; -const IntFittingRange = std.math.IntFittingRange; - -/// Jan 01, 1970 AD -pub const posix = 0; -/// Jan 01, 1980 AD -pub const dos = 315532800; -/// Jan 01, 2001 AD -pub const ios = 978307200; -/// Nov 17, 1858 AD -pub const openvms = -3506716800; -/// Jan 01, 1900 AD -pub const zos = -2208988800; -/// Jan 01, 1601 AD -pub const windows = -11644473600; -/// Jan 01, 1978 AD -pub const amiga = 252460800; -/// Dec 31, 1967 AD -pub const pickos = -63244800; -/// Jan 06, 1980 AD -pub const gps = 315964800; -/// Jan 01, 0001 AD -pub const clr = -62135769600; - -pub const unix = posix; -pub const android = posix; -pub const os2 = dos; -pub const bios = dos; -pub const vfat = dos; -pub const ntfs = windows; -pub const ntp = zos; -pub const jbase = pickos; -pub const aros = amiga; -pub const morphos = amiga; -pub const brew = gps; -pub const atsc = gps; -pub const go = clr; From d1b60e064060a6678d4c0f6cc20a4d82e06bd51d Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Wed, 3 Apr 2024 21:51:44 -0400 Subject: [PATCH 03/22] use new date_time type --- lib/compiler/aro/aro/Compilation.zig | 38 ++++----- lib/std/crypto/Certificate.zig | 115 +++++++++------------------ lib/std/os/uefi.zig | 41 ++++------ 3 files changed, 71 insertions(+), 123 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index 8329ad440557..fcfe4b97fbbe 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Allocator = mem.Allocator; const assert = std.debug.assert; -const EpochSeconds = std.time.epoch.EpochSeconds; const mem = std.mem; const Interner = @import("../backend.zig").Interner; const Builtins = @import("Builtins.zig"); @@ -195,38 +194,35 @@ fn getTimestamp(comp: *Compilation) !u47 { } fn generateDateAndTime(w: anytype, timestamp: u47) !void { - const epoch_seconds = EpochSeconds{ .secs = timestamp }; - const epoch_day = epoch_seconds.getEpochDay(); - const day_seconds = epoch_seconds.getDaySeconds(); - const year_day = epoch_day.calculateYearDay(); - const month_day = year_day.calculateMonthDay(); + const DateTime = std.date_time.Date16Time; + const dt = DateTime.fromEpoch(timestamp); const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - std.debug.assert(std.time.epoch.Month.jan.numeric() == 1); + std.debug.assert(DateTime.Date.Month.jan.numeric() == 1); - const month_name = month_names[month_day.month.numeric() - 1]; + const month_name = month_names[dt.date.month.numeric() - 1]; try w.print("#define __DATE__ \"{s} {d: >2} {d}\"\n", .{ month_name, - month_day.day_index + 1, - year_day.year, + dt.date.day, + dt.date.year, }); try w.print("#define __TIME__ \"{d:0>2}:{d:0>2}:{d:0>2}\"\n", .{ - day_seconds.getHoursIntoDay(), - day_seconds.getMinutesIntoHour(), - day_seconds.getSecondsIntoMinute(), + dt.time.hour, + dt.time.minute, + dt.time.second, }); const day_names = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; // days since Thu Oct 1 1970 - const day_name = day_names[@intCast((epoch_day.day + 3) % 7)]; + const day_name = day_names[std.math.comptimeMod(dt.date.toEpoch() + 3, day_names.len)]; try w.print("#define __TIMESTAMP__ \"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"\n", .{ day_name, month_name, - month_day.day_index + 1, - day_seconds.getHoursIntoDay(), - day_seconds.getMinutesIntoHour(), - day_seconds.getSecondsIntoMinute(), - year_day.year, + dt.date.day, + dt.time.hour, + dt.time.minute, + dt.time.second, + dt.date.year, }); } @@ -534,8 +530,8 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi if (system_defines_mode == .include_system_defines) { try buf.appendSlice( - \\#define __VERSION__ "Aro - ++ @import("../backend.zig").version_str ++ "\"\n" ++ + \\#define __VERSION__ "Aro + ++ " " ++ @import("../backend.zig").version_str ++ "\"\n" ++ \\#define __Aro__ \\ ); diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index b1cc4fc09550..b7ba951ea6b6 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -190,8 +190,8 @@ pub const Parsed = struct { }; pub const Validity = struct { - not_before: u64, - not_after: u64, + not_before: DateTime.EpochSeconds, + not_after: DateTime.EpochSeconds, }; pub const Slice = der.Element.Slice; @@ -542,8 +542,9 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice { pub const ParseTimeError = error{ CertificateTimeInvalid, CertificateFieldHasWrongDataType }; /// Returns number of seconds since epoch. -pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 { +pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!DateTime.EpochSeconds { const bytes = cert.contents(elem); + switch (elem.identifier.tag) { .utc_time => { // Example: "YYMMDD000000Z" @@ -552,14 +553,18 @@ pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 { if (bytes[12] != 'Z') return error.CertificateTimeInvalid; - return Date.toSeconds(.{ - .year = @as(u16, 2000) + try parseTimeDigits(bytes[0..2], 0, 99), - .month = try parseTimeDigits(bytes[2..4], 1, 12), - .day = try parseTimeDigits(bytes[4..6], 1, 31), - .hour = try parseTimeDigits(bytes[6..8], 0, 23), - .minute = try parseTimeDigits(bytes[8..10], 0, 59), - .second = try parseTimeDigits(bytes[10..12], 0, 59), - }); + return (DateTime{ + .date = .{ + .year = @as(DateTime.Date.Year, 2000) + try parseTimeDigits(bytes[0..2], 0, 99), + .month = @enumFromInt(try parseTimeDigits(bytes[2..4], 1, 12)), + .day = try parseTimeDigits(bytes[4..6], 1, 31), + }, + .time = .{ + .hour = try parseTimeDigits(bytes[6..8], 0, 23), + .minute = try parseTimeDigits(bytes[8..10], 0, 59), + .second = try parseTimeDigits(bytes[10..12], 0, 59), + }, + }).toEpoch(); }, .generalized_time => { // Examples: @@ -568,67 +573,24 @@ pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 { // "19920722132100.3Z" if (bytes.len < 15) return error.CertificateTimeInvalid; - return Date.toSeconds(.{ - .year = try parseYear4(bytes[0..4]), - .month = try parseTimeDigits(bytes[4..6], 1, 12), - .day = try parseTimeDigits(bytes[6..8], 1, 31), - .hour = try parseTimeDigits(bytes[8..10], 0, 23), - .minute = try parseTimeDigits(bytes[10..12], 0, 59), - .second = try parseTimeDigits(bytes[12..14], 0, 59), - }); + return (DateTime{ + .date = .{ + .year = try parseYear4(bytes[0..4]), + .month = @enumFromInt(try parseTimeDigits(bytes[4..6], 1, 12)), + .day = try parseTimeDigits(bytes[6..8], 1, 31), + }, + .time = .{ + .hour = try parseTimeDigits(bytes[8..10], 0, 23), + .minute = try parseTimeDigits(bytes[10..12], 0, 59), + .second = try parseTimeDigits(bytes[12..14], 0, 59), + }, + }).toEpoch(); }, else => return error.CertificateFieldHasWrongDataType, } } -const Date = struct { - /// example: 1999 - year: u16, - /// range: 1 to 12 - month: u8, - /// range: 1 to 31 - day: u8, - /// range: 0 to 59 - hour: u8, - /// range: 0 to 59 - minute: u8, - /// range: 0 to 59 - second: u8, - - /// Convert to number of seconds since epoch. - pub fn toSeconds(date: Date) u64 { - var sec: u64 = 0; - - { - var year: u16 = 1970; - while (year < date.year) : (year += 1) { - const days: u64 = std.time.epoch.getDaysInYear(year); - sec += days * std.time.epoch.secs_per_day; - } - } - - { - const is_leap = std.time.epoch.isLeapYear(date.year); - var month: u4 = 1; - while (month < date.month) : (month += 1) { - const days: u64 = std.time.epoch.getDaysInMonth( - @as(std.time.epoch.YearLeapKind, @enumFromInt(@intFromBool(is_leap))), - @as(std.time.epoch.Month, @enumFromInt(month)), - ); - sec += days * std.time.epoch.secs_per_day; - } - } - - sec += (date.day - 1) * @as(u64, std.time.epoch.secs_per_day); - sec += date.hour * @as(u64, 60 * 60); - sec += date.minute * @as(u64, 60); - sec += date.second; - - return sec; - } -}; - -pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 { +pub fn parseTimeDigits(text: *const [2]u8, min: comptime_int, max: comptime_int) !std.math.IntFittingRange(min, max) { const result = if (use_vectors) result: { const nn: @Vector(2, u16) = .{ text[0], text[1] }; const zero: @Vector(2, u16) = .{ '0', '0' }; @@ -652,22 +614,18 @@ test parseTimeDigits { try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99)); } -pub fn parseYear4(text: *const [4]u8) !u16 { - const result = if (use_vectors) result: { - const nnnn: @Vector(4, u32) = .{ text[0], text[1], text[2], text[3] }; - const zero: @Vector(4, u32) = .{ '0', '0', '0', '0' }; - const mmmm: @Vector(4, u32) = .{ 1000, 100, 10, 1 }; - break :result @reduce(.Add, (nnnn -% zero) *% mmmm); - } else std.fmt.parseInt(u16, text, 10) catch return error.CertificateTimeInvalid; +pub fn parseYear4(text: *const [4]u8) !DateTime.Date.Year { + const result = std.fmt.parseInt(DateTime.Date.Year, text, 10) catch + return error.CertificateTimeInvalid; if (result > 9999) return error.CertificateTimeInvalid; - return @truncate(result); + return result; } test parseYear4 { const expectEqual = std.testing.expectEqual; - try expectEqual(@as(u16, 0), try parseYear4("0000")); - try expectEqual(@as(u16, 9999), try parseYear4("9999")); - try expectEqual(@as(u16, 1988), try parseYear4("1988")); + try expectEqual(0, try parseYear4("0000")); + try expectEqual(9999, try parseYear4("9999")); + try expectEqual(1988, try parseYear4("1988")); const expectError = std.testing.expectError; try expectError(error.CertificateTimeInvalid, parseYear4("999b")); @@ -859,6 +817,7 @@ fn verifyEd25519( const std = @import("../std.zig"); const crypto = std.crypto; const mem = std.mem; +const DateTime = std.date_time.Date16Time; const Certificate = @This(); pub const der = struct { diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 165c3df2495f..64b5d8f07bb3 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -131,30 +131,23 @@ pub const Time = extern struct { /// Time is to be interpreted as local time pub const unspecified_timezone: i16 = 0x7ff; - - fn daysInYear(year: u16, maxMonth: u4) u32 { - const leapYear: std.time.epoch.YearLeapKind = if (std.time.epoch.isLeapYear(year)) .leap else .not_leap; - var days: u32 = 0; - var month: u4 = 0; - while (month < maxMonth) : (month += 1) { - days += std.time.epoch.getDaysInMonth(leapYear, @enumFromInt(month + 1)); - } - return days; - } - - pub fn toEpoch(self: std.os.uefi.Time) u64 { - var year: u16 = 0; - var days: u32 = 0; - - while (year < (self.year - 1971)) : (year += 1) { - days += daysInYear(year + 1970, 12); - } - - days += daysInYear(self.year, @as(u4, @intCast(self.month)) - 1) + self.day; - const hours = self.hour + (days * 24); - const minutes = self.minute + (hours * 60); - const seconds = self.second + (minutes * std.time.s_per_min); - return self.nanosecond + (seconds * std.time.ns_per_s); + pub const DateTime = std.date_time.DateTime(u16, 9); + + pub fn toEpoch(self: @This()) DateTime.EpochSeconds { + const dt = DateTime{ + .date = .{ + .year = self.year, + .month = self.month, + .day = self.day, + }, + .time = .{ + .hour = self.hour, + .minute = self.minute, + .second = self.second, + .fractional_second = self.nanosecond, + }, + }; + return dt.toEpoch(); } }; From 168a8637c60ce349367ae531136de9c6fed3f42d Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 13:23:54 -0400 Subject: [PATCH 04/22] Add timezone. Remove order. Add addition. Add RFC 3339 parsing/formatting. --- lib/std/crypto/Certificate.zig | 6 +- lib/std/date.zig | 12 +- lib/std/date/epoch.zig | 2 + lib/std/date/gregorian.zig | 188 ++++++++++++++++++++++++++----- lib/std/date_time.zig | 172 +++++++++++++++++----------- lib/std/os/uefi.zig | 7 +- lib/std/time.zig | 200 ++++++++++++++++++++++++++++----- 7 files changed, 456 insertions(+), 131 deletions(-) diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index b7ba951ea6b6..16b86b515484 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -190,8 +190,8 @@ pub const Parsed = struct { }; pub const Validity = struct { - not_before: DateTime.EpochSeconds, - not_after: DateTime.EpochSeconds, + not_before: DateTime.EpochSubseconds, + not_after: DateTime.EpochSubseconds, }; pub const Slice = der.Element.Slice; @@ -542,7 +542,7 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice { pub const ParseTimeError = error{ CertificateTimeInvalid, CertificateFieldHasWrongDataType }; /// Returns number of seconds since epoch. -pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!DateTime.EpochSeconds { +pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!DateTime.EpochSubseconds { const bytes = cert.contents(elem); switch (elem.identifier.tag) { diff --git a/lib/std/date.zig b/lib/std/date.zig index 6568a174c891..a817e1a3687b 100644 --- a/lib/std/date.zig +++ b/lib/std/date.zig @@ -1,15 +1,15 @@ pub const gregorian = @import("./date/gregorian.zig"); +pub const epoch = @import("./date/epoch.zig"); -pub fn Date(comptime Year: type) type { +pub const Date = gregorian.Date; +pub fn DatePosix(comptime Year: type) type { return gregorian.Date(Year, epoch.posix); } -pub const Date16 = Date(i16); -pub const Date32 = Date(i32); -pub const Date64 = Date(i64); +pub const Date16 = DatePosix(i16); +pub const Date32 = DatePosix(i32); +pub const Date64 = DatePosix(i64); test { _ = gregorian; } - -const epoch = @import("./date/epoch.zig"); diff --git a/lib/std/date/epoch.zig b/lib/std/date/epoch.zig index f253e1f81c05..e66a27fdadf9 100644 --- a/lib/std/date/epoch.zig +++ b/lib/std/date/epoch.zig @@ -25,6 +25,8 @@ pub const pickos = -732; pub const gps = 3_657; /// 0001-01-01 pub const clr = -719_164; +/// 1582-10-15 +pub const uefi = -141_427; pub const unix = posix; pub const android = posix; diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index c2f984356b73..3484dfb7f9a7 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -30,22 +30,25 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ pub const EpochDays = MakeEpochDays(Year); const UEpochDays = std.meta.Int(.unsigned, @typeInfo(EpochDays).Int.bits); + const IEpochDays = std.meta.Int(.signed, @typeInfo(EpochDays).Int.bits); const K = epoch + era_days * shift; const L = era * shift; + /// Minimum epoch day representable by `Year` - pub const min_day = -K; + const min_day = -K; /// Maximum epoch day representable by `Year` - pub const max_day = (std.math.maxInt(EpochDays) - 3) / 4 - K; + const max_day = (std.math.maxInt(EpochDays) - 3) / 4 - K; // Ensure `toEpochDays` won't cause overflow. - // // If you trigger these assertions, try choosing a different value of `shift`. comptime { std.debug.assert(-L < std.math.minInt(Year)); std.debug.assert(max_day / days_in_year.numerator - L + 1 > std.math.maxInt(Year)); } + /// Calendar which starts at 0000-03-01 to obtain useful counting properties. + /// See section 4 of paper. const Computational = struct { year: UEpochDays, month: UIntFitting(14), @@ -56,9 +59,10 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ const J: UEpochDays = if (N_Y >= last_day_of_jan) 1 else 0; const month: MonthInt = if (J != 0) self.month - 12 else self.month; + const year: EpochDays = @bitCast(self.year +% J -% L); return .{ - .year = @intCast(@as(EpochDays, @bitCast(self.year +% J -% L))), + .year = @intCast(year), .month = @enumFromInt(month), .day = @as(Day, self.day) + 1, }; @@ -79,10 +83,6 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ const Self = @This(); - pub fn order(self: Self, other: Self) std.math.Order { - return difference(self.year, other.year) orelse difference(self.month.numeric(), other.month.numeric()) orelse difference(self.day, other.day) orelse .eq; - } - pub fn fromEpoch(days: EpochDays) Self { // This function is Figure 12 of the paper. // Besides being ported from C++, the following has changed: @@ -92,9 +92,6 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ // - Use bounded int types provided in Section 10 instead of u32 and u64 // - Add computational calendar struct type // - Add comments referencing some proofs - // - // While these changes should allow the reader to understand _how_ these functions - // work, I recommend reading the paper to understand *why*. assert(days > min_day); assert(days < max_day); const mod = std.math.comptimeMod; @@ -145,6 +142,59 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ return @as(EpochDays, @intCast(N)) - K; } + + pub const MonthAdd = std.meta.Int(.signed, @typeInfo(IEpochDays).Int.bits - std.math.log2_int(u16, 12)); + + pub fn add(self: Self, year: Year, month: MonthAdd, day: IEpochDays) Self { + const m = month + self.month.numeric() - 1; + const y = self.year + year + @divFloor(m, 12); + + const ym_epoch_day = Self{ + .year = @intCast(y), + .month = @enumFromInt(std.math.comptimeMod(m, 12) + 1), + .day = 1, + }; + + var epoch_days = ym_epoch_day.toEpoch(); + epoch_days += day + self.day - 1; + + return fromEpoch(epoch_days); + } + + pub const Weekday = WeekdayT; + pub fn weekday(self: Self) Weekday { + // 1970-01-01 is a Thursday. + const epoch_days = self.toEpoch() +% Weekday.thu.numeric(); + return @enumFromInt(std.math.comptimeMod(epoch_days, 7)); + } + + pub fn fromRfc3339(str: *const [10]u8) !Self { + if (str[4] != '-' or str[7] != '-') return error.Parsing; + + const year = try std.fmt.parseInt(IntFittingRange(0, 9999), str[0..4], 10); + const month = try std.fmt.parseInt(Month.Int, str[5..7], 10); + if (month < 1 or month > 12) return error.Parsing; + const m: Month = @enumFromInt(month); + const day = try std.fmt.parseInt(Day, str[8..10], 10); + if (day < 1 or day > m.days(is_leap(year))) return error.Parsing; + + return .{ + .year = @intCast(year), // if YearT is `i8` or `u8` this may fail. increase it to not fail. + .month = m, + .day = day, + }; + } + + pub fn toRfc3339(self: Self, writer: anytype) !void { + if (self.year < 0 or self.year > 9999) return error.Range; + if (self.day < 1 or self.day > 99) return error.Range; + if (self.month.numeric() < 1 or self.month.numeric() > 12) return error.Range; + try writer.print("{d:0>4}-{d:0>2}-{d:0>2}", .{ + @as(UEpochDays, @intCast(self.year)), + self.month.numeric(), + self.day, + }); + } }; } @@ -154,17 +204,108 @@ pub fn Date(comptime Year: type, epoch: comptime_int) type { return DateAdvanced(Year, epoch, shift); } -test Date { - const T = Date(i16, 0); +fn testFromToEpoch(comptime T: type) !void { const d1 = T{ .year = 1970, .month = .jan, .day = 1 }; - const d2 = T{ .year = 1971, .month = .jan, .day = 1 }; + const d2 = T{ .year = 1980, .month = .jan, .day = 1 }; + + try expectEqual(3_652, d2.toEpoch() - d1.toEpoch()); + + // We don't have time to test converting there and back again for every possible i64/u64. + // The paper has already proven it and written tests for i32 and u32. + // Instead let's cycle through the first and last 1 << 16 part of each range. + const min_epoch_day = (T{ .year = std.math.minInt(T.Year), .month = .jan, .day = 1 }).toEpoch(); + const max_epoch_day = (T{ .year = std.math.maxInt(T.Year), .month = .dec, .day = 31 }).toEpoch(); + const range: u128 = @intCast(max_epoch_day - min_epoch_day); + for (0..@min(1 << 16, range)) |i| { + const d3 = min_epoch_day + @as(T.EpochDays, @intCast(i)); + try expectEqual(d3, T.fromEpoch(d3).toEpoch()); + + const d4 = max_epoch_day - @as(T.EpochDays, @intCast(i)); + try expectEqual(d4, T.fromEpoch(d4).toEpoch()); + } +} - try expectEqual(d1.order(d2), .lt); - try expectEqual(365, d2.toEpoch() - d1.toEpoch()); +test "Date from and to epoch" { + try testFromToEpoch(Date(i16, 0)); + try testFromToEpoch(Date(i32, 0)); + try testFromToEpoch(Date(i64, 0)); + + try testFromToEpoch(Date(u16, 0)); + try testFromToEpoch(Date(u32, 0)); + try testFromToEpoch(Date(u64, 0)); + + const epoch = std.date.epoch; + + try testFromToEpoch(Date(u16, epoch.windows)); + try testFromToEpoch(Date(u32, epoch.windows)); + try testFromToEpoch(Date(u64, epoch.windows)); +} + +test "Date RFC3339" { + const T = Date(i16, 0); + try expectEqual(T{ .year = 2000, .month = .jan, .day = 1 }, try T.fromRfc3339("2000-01-01")); + try std.testing.expectError(error.Parsing, T.fromRfc3339("2000T01-01")); + try std.testing.expectError(error.InvalidCharacter, T.fromRfc3339("2000-01-AD")); +} + +test Date { + const T = Date(i16, 0); + const d1 = T{ .year = 1960, .month = .jan, .day = 1 }; + const epoch = T{ .year = 1970, .month = .jan, .day = 1 }; + + try expectEqual(365, (T{ .year = 1971, .month = .jan, .day = 1 }).toEpoch()); + try expectEqual(epoch, T.fromEpoch(0)); + try expectEqual(3_653, epoch.toEpoch() - d1.toEpoch()); + + // overflow + // $ TZ=UTC0 date -d '1970-01-01 +1 year +13 months +32 days' --iso-8601=seconds + try expectEqual( + T{ .year = 1972, .month = .mar, .day = 4 }, + (T{ .year = 1970, .month = .jan, .day = 1 }).add(1, 13, 32), + ); + // underflow + // $ TZ=UTC0 date -d '1972-03-04 -10 year -13 months -32 days' --iso-8601=seconds + try expectEqual( + T{ .year = 1961, .month = .jan, .day = 3 }, + (T{ .year = 1972, .month = .mar, .day = 4 }).add(-10, -13, -32), + ); + + // $ date -d '1970-01-01' + try expectEqual(.thu, epoch.weekday()); + try expectEqual(.thu, epoch.add(0, 0, 7).weekday()); + try expectEqual(.thu, epoch.add(0, 0, -7).weekday()); + // $ date -d '1980-01-01' + try expectEqual(.tue, (T{ .year = 1980, .month = .jan, .day = 1 }).weekday()); + // $ date -d '1960-01-01' + try expectEqual(.fri, d1.weekday()); + + try expectEqual(T{ .year = 2000, .month = .jan, .day = 1 }, try T.fromRfc3339("2000-01-01")); + var buf: [10]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + try (T{ .year = 2000, .month = .jan, .day = 1 }).toRfc3339(stream.writer()); + try std.testing.expectEqualStrings("2000-01-01", stream.getWritten()); } +const WeekdayInt = IntFittingRange(1, 7); +pub const WeekdayT = enum(WeekdayInt) { + sun = 1, + mon = 2, + tue = 3, + wed = 4, + thu = 5, + fri = 6, + sat = 7, + + pub const Int = WeekdayInt; + + /// Convenient conversion to `WeekdayInt`. sun = 1, sat = 7 + pub fn numeric(self: @This()) Int { + return @intFromEnum(self); + } +}; + const MonthInt = IntFittingRange(1, 12); -const MonthT = enum(MonthInt) { +pub const MonthT = enum(MonthInt) { jan = 1, feb = 2, mar = 3, @@ -182,11 +323,11 @@ const MonthT = enum(MonthInt) { pub const Days = IntFittingRange(28, 31); /// Convenient conversion to `MonthInt`. jan = 1, dec = 12 - pub fn numeric(self: MonthT) MonthInt { + pub fn numeric(self: @This()) Int { return @intFromEnum(self); } - pub fn days(self: MonthT, is_leap_year: bool) Days { + pub fn days(self: @This(), is_leap_year: bool) Days { const m: Days = @intCast(self.numeric()); return if (m != 2) 30 | (m ^ (m >> 3)) @@ -227,7 +368,7 @@ test is_leap { try expectEqual(true, is_leap(2400)); } -fn days_between_years(from: usize, to: usize) usize { +pub fn days_between_years(from: usize, to: usize) usize { var res: usize = 0; var i: usize = from; while (i < to) : (i += 1) { @@ -241,13 +382,6 @@ test days_between_years { try expectEqual(146_097, days_between_years(0, 400)); } -/// Returns .gt, .lt, or null -fn difference(a: anytype, b: anytype) ?std.math.Order { - const res = std.math.order(a, b); - if (res != .eq) return res; - return null; -} - /// The Gregorian calendar repeats every 400 years. const era = 400; const era_days: comptime_int = days_between_years(0, 400); // 146_097 diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index 1e0d98b51990..97acb9277ab1 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -1,44 +1,94 @@ -pub fn DateTime(comptime Year: type, time_precision: comptime_int) type { +pub fn DateTime(comptime DateT: type, comptime TimeT: type) type { return struct { date: Date, time: Time = .{}, - pub const Date = date_mod.Date(Year); - pub const Time = time_mod.Time(time_precision); - pub const EpochSeconds = i64; + pub const Date = DateT; + pub const Time = TimeT; + /// Fractional epoch seconds based on `TimeT.precision`: + /// 0 = seconds + /// 3 = milliseconds + /// 6 = microseconds + /// 9 = nanoseconds + pub const EpochSubseconds = std.meta.Int( + @typeInfo(Date.EpochDays).Int.signedness, + @typeInfo(Date.EpochDays).Int.bits + std.math.log2_int_ceil(usize, Time.fs_per_day), + ); const Self = @This(); - pub fn fromEpoch(seconds: EpochSeconds) Self { - const days = @divFloor(seconds, s_per_day * Time.fs_per_s); + /// New date time from fractional epoch seconds. + pub fn fromEpoch(subseconds: EpochSubseconds, time_opts: Time.Options) Self { + const days = @divFloor(subseconds, s_per_day * Time.fs_per_s); const new_date = Date.fromEpoch(@intCast(days)); - const day_seconds = std.math.comptimeMod(seconds, s_per_day * Time.fs_per_s); - const new_time = Time.fromDayFractionalSeconds(day_seconds); + const day_seconds = std.math.comptimeMod(subseconds, s_per_day * Time.fs_per_s); + const new_time = Time.fromDaySeconds(day_seconds, time_opts); return .{ .date = new_date, .time = new_time }; } - pub fn toEpoch(self: Self) EpochSeconds { - var res: EpochSeconds = 0; - res += @as(EpochSeconds, self.date.toEpoch()) * s_per_day * Time.fs_per_s; - res += self.time.toDayFractionalSeconds(); + /// Returns fractional epoch seconds. + pub fn toEpoch(self: Self) EpochSubseconds { + var res: EpochSubseconds = 0; + res += @as(EpochSubseconds, self.date.toEpoch()) * s_per_day * Time.fs_per_s; + res += self.time.toDaySeconds(); return res; } + + pub fn add( + self: Self, + year: Date.Year, + month: Date.MonthAdd, + day: Date.IEpochDays, + hour: i64, + minute: i64, + second: i64, + subsecond: i64, + ) DateTime { + const time = self.time.addWithOverflow(hour, minute, second, subsecond); + const date = self.date.add(year, month, day + time.day_overflow); + return .{ .date = date, .time = time.time }; + } + + pub fn fromRfc3339(str: []const u8) !Self { + if (str.len < 10 + "hh:mm:ssZ".len) return error.Parsing; + if (std.ascii.toUpper(str[10]) != 'T') return error.Parsing; + return .{ + .date = try Date.fromRfc3339(str[0..10]), + .time = try Time.fromRfc3339(str[11..]), + }; + } + + pub fn toRfc3339(self: Self, writer: anytype) !void { + try self.date.toRfc3339(writer); + try writer.writeByte('T'); + try self.time.toRfc3339(writer); + } }; } -pub const Date16Time = DateTime(i16, 0); +pub fn DateTimeAdvanced( + comptime Year: type, + epoch: comptime_int, + time_precision: comptime_int, + comptime time_zoned: bool, +) type { + return DateTime(date_mod.Date(Year, epoch), time_mod.Time(time_precision, time_zoned)); +} + +pub const Date16Time = DateTime(date_mod.Date16, time_mod.Time(0, false)); + comptime { assert(@sizeOf(Date16Time) == 8); } /// Tests EpochSeconds -> DateTime and DateTime -> EpochSeconds -fn testEpoch(secs: i64, dt: Date16Time) !void { - const actual_dt = Date16Time.fromEpoch(secs); - try std.testing.expectEqualDeep(dt, actual_dt); +fn testEpoch(secs: Date16Time.EpochSubseconds, dt: Date16Time) !void { + const actual_dt = Date16Time.fromEpoch(secs, .{}); + try std.testing.expectEqual(dt, actual_dt); try std.testing.expectEqual(secs, dt.toEpoch()); } -test Date16Time { +test "Date epoch" { // $ date -d @31535999 --iso-8601=seconds try testEpoch(0, .{ .date = .{ .year = 1970, .month = .jan, .day = 1 } }); try testEpoch(31535999, .{ @@ -58,62 +108,50 @@ test Date16Time { .date = .{ .year = 1732, .month = .feb, .day = 22 }, .time = .{ .hour = 12, .minute = 30 }, }); - // negative year - try testEpoch(-97506041400, .{ - .date = .{ .year = -1120, .month = .feb, .day = 26 }, - .time = .{ .hour = 20, .minute = 30, .second = 0 }, - }); // minimum date try testEpoch(-1096225401600, .{ .date = .{ .year = std.math.minInt(i16), .month = .jan, .day = 1 }, }); + // $ date -d '32767-12-31 UTC' +%s + try testEpoch(971890876800, .{ + .date = .{ .year = std.math.maxInt(i16), .month = .dec, .day = 31 }, + }); } -// pub const Rfc3339 = struct { -// pub fn parseDate(str: []const u8) !Date { -// if (str.len != 10) return error.Parsing; -// const Rfc3339Year = IntFittingRange(0, 9999); -// const year = try std.fmt.parseInt(Rfc3339Year, str[0..4], 10); -// if (str[4] != '-') return error.Parsing; -// const month = try std.fmt.parseInt(MonthInt, str[5..7], 10); -// if (str[7] != '-') return error.Parsing; -// const day = try std.fmt.parseInt(Date.Day, str[8..10], 10); -// return .{ .year = year, .month = @enumFromInt(month), .day = day }; -// } -// -// pub fn parseTime(str: []const u8) !Time { -// if (str.len < 8) return error.Parsing; -// -// const hour = try std.fmt.parseInt(Time.Hour, str[0..2], 10); -// if (str[2] != ':') return error.Parsing; -// const minute = try std.fmt.parseInt(Time.Minute, str[3..5], 10); -// if (str[5] != ':') return error.Parsing; -// const second = try std.fmt.parseInt(Time.Second, str[6..8], 10); -// // ignore optional subseconds -// // ignore timezone -// -// return .{ .hour = hour, .minute = minute, .second = second }; -// } -// -// pub fn parseDateTime(str: []const u8) !DateTime { -// if (str.len < 10 + 1 + 8) return error.Parsing; -// const date = try parseDate(str[0..10]); -// if (str[10] != 'T') return error.Parsing; -// const time = try parseTime(str[11..]); -// return .{ -// .year = date.year, -// .month = date.month, -// .day = date.day, -// .hour = time.hour, -// .minute = time.minute, -// .second = time.second, -// }; -// } -// }; -// -// fn comptimeParse(comptime time: []const u8) DateTime { -// return Rfc3339.parseDateTime(time) catch unreachable; -// } +test "Date RFC 3339 section 5.8" { + const T = DateTime(date_mod.Date16, time_mod.Time(3, true)); + const expectEqual = std.testing.expectEqual; + const t1 = T{ + .date = .{ .year = 1985, .month = .apr, .day = 12 }, + .time = .{ .hour = 23, .minute = 20, .second = 50, .subsecond = 520 }, + }; + try expectEqual(t1, try T.fromRfc3339("1985-04-12T23:20:50.52Z")); + const t2 = T{ + .date = .{ .year = 1996, .month = .dec, .day = 19 }, + .time = .{ .hour = 16, .minute = 39, .second = 57, .offset = -8 * 60 }, + }; + try expectEqual(t2, try T.fromRfc3339("1996-12-19T16:39:57-08:00")); + const t3 = T{ + .date = .{ .year = 1990, .month = .dec, .day = 31 }, + .time = .{ .hour = 23, .minute = 59, .second = 60 }, + }; + try expectEqual(t3, try T.fromRfc3339("1990-12-31T23:59:60Z")); + const t4 = T{ + .date = .{ .year = 1990, .month = .dec, .day = 31 }, + .time = .{ .hour = 15, .minute = 59, .second = 60, .offset = -8 * 60 }, + }; + try expectEqual(t4, try T.fromRfc3339("1990-12-31T15:59:60-08:00")); + const t5 = T{ + .date = .{ .year = 1937, .month = .jan, .day = 1 }, + .time = .{ .hour = 12, .second = 27, .subsecond = 870, .offset = 20 }, + }; + try expectEqual(t5, try T.fromRfc3339("1937-01-01T12:00:27.87+00:20")); + + var buf: [32]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + try t5.toRfc3339(stream.writer()); + try std.testing.expectEqualStrings("1937-01-01T12:00:27.870+00:20", stream.getWritten()); +} const std = @import("std.zig"); const date_mod = @import("./date.zig"); diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 64b5d8f07bb3..fd9a0344f389 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -131,9 +131,9 @@ pub const Time = extern struct { /// Time is to be interpreted as local time pub const unspecified_timezone: i16 = 0x7ff; - pub const DateTime = std.date_time.DateTime(u16, 9); + pub const DateTime = std.date_time.DateTimeAdvanced(u16, std.date.epoch.uefi, 9, true); - pub fn toEpoch(self: @This()) DateTime.EpochSeconds { + pub fn toEpoch(self: @This()) DateTime.Seconds { const dt = DateTime{ .date = .{ .year = self.year, @@ -144,7 +144,8 @@ pub const Time = extern struct { .hour = self.hour, .minute = self.minute, .second = self.second, - .fractional_second = self.nanosecond, + .subsecond = self.nanosecond, + .timezone = self.timezone, }, }; return dt.toEpoch(); diff --git a/lib/std/time.zig b/lib/std/time.zig index a3dbd1a4cb2a..6de35617182f 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -344,65 +344,215 @@ test Timer { try testing.expect(timer.read() < time_1); } -pub fn Time(precision_: comptime_int) type { +pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); return struct { hour: Hour = 0, minute: Minute = 0, + /// Allows leap seconds. second: Second = 0, /// Milliseconds, microseconds, or nanoseconds. - fractional_second: FractionalSecond = 0, + subsecond: Subsecond = 0, + /// Offset of `hour` and `minute` from in minutes from UTC. + offset: IMinutes = 0, pub const Hour = IntFittingRange(0, 23); pub const Minute = IntFittingRange(0, 59); - pub const Second = IntFittingRange(0, 59); - pub const FractionalSecond = IntFittingRange(0, if (precision_ == 0) 0 else multiplier); - pub const DayFractionalSeconds = IntFittingRange(0, s_per_day * multiplier); + pub const Second = IntFittingRange(0, 60); + pub const Subsecond = IntFittingRange(0, if (precision_ == 0) 0 else multiplier); + pub const DaySubseconds = IntFittingRange(0, s_per_day * multiplier); + const IDaySubseconds = std.meta.Int(.signed, @typeInfo(DaySubseconds).Int.bits + 1); + pub const IMinutes = if (has_offset) IntFittingRange(-24 * 60, 24 * 60) else u0; const Self = @This(); pub const precision = precision_; - pub const fs_per_hour = 60 * 60 * multiplier; - pub const fs_per_minute = 60 * multiplier; pub const fs_per_s = multiplier; + pub const fs_per_min = 60 * fs_per_s; + pub const fs_per_hour = 60 * fs_per_min; + pub const fs_per_day = 24 * fs_per_hour; - pub fn fromDayFractionalSeconds(fractional_seconds: DayFractionalSeconds) Self { - var fs = fractional_seconds; + pub const Options = struct { + offset: IMinutes = 0, + }; + + pub fn fromDaySeconds(seconds: DaySubseconds, options: Options) Self { + const fs_offset = @as(IDaySubseconds, options.offset) * fs_per_min + seconds; + var fs = std.math.comptimeMod(fs_offset, fs_per_day); const hour: Hour = @intCast(@divFloor(fs, fs_per_hour)); - fs -= @as(DayFractionalSeconds, @intCast(hour)) * fs_per_hour; + fs -= @as(DaySubseconds, @intCast(hour)) * fs_per_hour; - const minute: Minute = @intCast(@divFloor(fs, fs_per_minute)); - fs -= @as(DayFractionalSeconds, @intCast(minute)) * fs_per_minute; + const minute: Minute = @intCast(@divFloor(fs, fs_per_min)); + fs -= @as(DaySubseconds, @intCast(minute)) * fs_per_min; const second: Second = @intCast(@divFloor(fs, fs_per_s)); - fs -= @as(DayFractionalSeconds, @intCast(second)) * fs_per_s; + fs -= @as(DaySubseconds, @intCast(second)) * fs_per_s; return .{ .hour = hour, .minute = minute, .second = second, - .fractional_second = @intCast(fs), + .subsecond = @intCast(fs), + .offset = options.offset, }; } - pub fn toDayFractionalSeconds(self: Self) DayFractionalSeconds { - var sec: DayFractionalSeconds = 0; - sec += @as(DayFractionalSeconds, self.hour) * fs_per_hour; - sec += @as(DayFractionalSeconds, self.minute) * fs_per_minute; - sec += @as(DayFractionalSeconds, self.second) * fs_per_s; - sec += @as(DayFractionalSeconds, self.fractional_second); + pub fn toDaySeconds(self: Self) DaySubseconds { + return self.toDaySecondsZone(self.offset); + } + + pub fn toDaySecondsZone(self: Self, zone: IMinutes) DaySubseconds { + var sec = @as(IDaySubseconds, zone) * 60 * multiplier; + sec += @as(IDaySubseconds, self.hour) * fs_per_hour; + sec += @as(IDaySubseconds, self.minute) * fs_per_min; + sec += @as(IDaySubseconds, self.second) * fs_per_s; + sec += @as(IDaySubseconds, self.subsecond); + + return std.math.comptimeMod(sec, s_per_day * multiplier); + } + + /// Does not handle leap seconds. + pub fn addWithOverflow( + self: Self, + hour: i64, + minute: i64, + second: i64, + subsecond: i64, + ) struct { time: Self, day_overflow: i64 } { + const fs = subsecond + self.subsecond; + const s = second + self.second + @divFloor(fs, 1000); + const m = minute + self.minute + @divFloor(s, 60); + const h = hour + self.hour + @divFloor(m, 60); + const overflow = @divFloor(h, 24); + + return .{ + .time = .{ + .subsecond = std.math.comptimeMod(fs, 1000), + .second = std.math.comptimeMod(s, 60), + .minute = std.math.comptimeMod(m, 60), + .hour = std.math.comptimeMod(h, 24), + }, + .day_overflow = overflow, + }; + } - return sec; + /// Does not handle leap seconds. + pub fn add(self: Self, hour: i64, minute: i64, second: i64, subsecond: i64) Self { + return self.addWithOverflow(hour, minute, second, subsecond).time; + } + + pub fn fromRfc3339(str: []const u8) !Self { + if (str.len < "hh:mm:ssZ".len) return error.Parsing; + + if (str[2] != ':' or str[5] != ':') return error.Parsing; + + const hour = try std.fmt.parseInt(Hour, str[0..2], 10); + const minute = try std.fmt.parseInt(Minute, str[3..5], 10); + const second = try std.fmt.parseInt(Second, str[6..8], 10); + + var i: usize = 8; + const subsecond: Subsecond = if (str[i] == '.') brk: { + i += 1; + while (i < str.len and std.ascii.isDigit(str[i])) : (i += 1) {} + if (Subsecond == u0) break :brk 0; + const subsecond_str = str[9..i]; + // Choose largest performant type. + // Ideally, this would allow infinite precision. + const T = f64; + var subsecond = try std.fmt.parseFloat(T, subsecond_str); + const actual_precision: T = @floatFromInt(subsecond_str.len); + subsecond *= std.math.pow(T, 10, precision - actual_precision); + + break :brk @intFromFloat(subsecond); + } else 0; + + // timezone required + if (str.len <= i) return error.Parsing; + + const offset = if (std.ascii.toUpper(str[i]) == 'Z') 0 else brk: { + var sign: IMinutes = 1; + if (str[i] == '-') { + sign = -1; + i += 1; + } else if (str[i] == '+') { + i += 1; + } + + const offset_hour = try std.fmt.parseInt(IMinutes, str[i..][0..2], 10); + if (str[i + 2] != ':') return error.Parsing; + const offset_minute = try std.fmt.parseInt(IMinutes, str[i + 3 ..][0..2], 10); + + break :brk sign * (offset_hour * 60 + offset_minute); + }; + + return .{ .hour = hour, .minute = minute, .second = second, .subsecond = subsecond, .offset = offset }; + } + + pub fn toRfc3339(self: Self, writer: anytype) !void { + try writer.print("{d:0>2}:{d:0>2}:{d:0>2}", .{ self.hour, self.minute, self.second }); + if (self.subsecond != 0) { + // We could trim trailing zeros here to save space. + try writer.print(".{d}", .{self.subsecond}); + } + if (self.offset == 0) { + try writer.writeByte('Z'); + } else { + try writer.writeByte(if (self.offset > 0) '+' else '-'); + const abs: u16 = @intCast(if (self.offset > 0) self.offset else -self.offset); + try writer.print("{d:0>2}:{d:0>2}", .{ abs / 60, abs % 60 }); + } } }; } +test Time { + const TimeMilli = Time(3, false); + const t1 = TimeMilli{}; + const expectEqual = std.testing.expectEqual; + // cause each place to overflow + try expectEqual(TimeMilli{ .hour = 2, .minute = 2, .second = 2, .subsecond = 1 }, t1.add(25, 61, 61, 1001)); + // cause each place to underflow + try expectEqual(TimeMilli{ .hour = 21, .minute = 57, .second = 57, .subsecond = 999 }, t1.add(-25, -61, -61, -1001)); + + const TimeMilliOffset = Time(3, true); + // positive offset + try expectEqual( + TimeMilliOffset{ .hour = 1, .minute = 30, .offset = 90 }, + TimeMilliOffset.fromDaySeconds(0, .{ .offset = 90 }), + ); + // negative offset + try expectEqual( + TimeMilliOffset{ .hour = 22, .minute = 30, .offset = -90 }, + TimeMilliOffset.fromDaySeconds(0, .{ .offset = -90 }), + ); + + try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .offset = -90 }, try TimeMilliOffset.fromRfc3339("22:30:00-01:30")); + try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .offset = 90 }, try TimeMilliOffset.fromRfc3339("22:30:00.0000+01:30")); + try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100 }, try TimeMilliOffset.fromRfc3339("22:30:20.1Z")); + + const expectError = std.testing.expectError; + try expectError(error.Parsing, TimeMilliOffset.fromRfc3339("22:30:20.100")); // missing timezone + try expectError(error.InvalidCharacter, TimeMilliOffset.fromRfc3339("22:30:20.1a00")); + try expectError(error.Parsing, TimeMilliOffset.fromRfc3339("2:00:20Z")); // missing hour digit + + var buf: [32]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const time = TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100 }; + try time.toRfc3339(stream.writer()); + try std.testing.expectEqualStrings("22:30:20.100Z", stream.getWritten()); + + stream.reset(); + const time2 = TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100, .offset = 100 }; + try time2.toRfc3339(stream.writer()); + try std.testing.expectEqualStrings("22:30:20.100+01:40", stream.getWritten()); +} + comptime { - assert(@sizeOf(Time(0)) == 3); - assert(@sizeOf(Time(3)) == 6); - assert(@sizeOf(Time(6)) == 8); - assert(@sizeOf(Time(9)) == 8); + assert(@sizeOf(Time(0, false)) == 3); + assert(@sizeOf(Time(3, false)) == 6); + assert(@sizeOf(Time(6, false)) == 8); + assert(@sizeOf(Time(9, false)) == 8); } test { From 101ed770da8db85ecb5ce80d76bc0f8dc0e7c5f6 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 13:40:51 -0400 Subject: [PATCH 05/22] fix uefi type --- lib/std/os/uefi.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index fd9a0344f389..a64994f4ef67 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -133,7 +133,7 @@ pub const Time = extern struct { pub const unspecified_timezone: i16 = 0x7ff; pub const DateTime = std.date_time.DateTimeAdvanced(u16, std.date.epoch.uefi, 9, true); - pub fn toEpoch(self: @This()) DateTime.Seconds { + pub fn toEpoch(self: @This()) DateTime.EpochSubseconds { const dt = DateTime{ .date = .{ .year = self.year, From c915b3f4d3f24dd4c167da36091c603b17526825 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 14:05:00 -0400 Subject: [PATCH 06/22] add aro `fromEpoch` arg --- lib/compiler/aro/aro/Compilation.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index fcfe4b97fbbe..99972a989349 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -195,7 +195,7 @@ fn getTimestamp(comp: *Compilation) !u47 { fn generateDateAndTime(w: anytype, timestamp: u47) !void { const DateTime = std.date_time.Date16Time; - const dt = DateTime.fromEpoch(timestamp); + const dt = DateTime.fromEpoch(timestamp, .{}); const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; std.debug.assert(DateTime.Date.Month.jan.numeric() == 1); From 22d56ad4bbde268498dd680661312adc5730ff2c Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 16:51:23 -0400 Subject: [PATCH 07/22] add range check to Time.toRfc3339 --- lib/std/time.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index 6de35617182f..817a6af8562c 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -490,6 +490,7 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { } pub fn toRfc3339(self: Self, writer: anytype) !void { + if (self.hour > 24 or self.minute > 59 or self.second > 60) return error.Range; try writer.print("{d:0>2}:{d:0>2}:{d:0>2}", .{ self.hour, self.minute, self.second }); if (self.subsecond != 0) { // We could trim trailing zeros here to save space. @@ -500,7 +501,10 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { } else { try writer.writeByte(if (self.offset > 0) '+' else '-'); const abs: u16 = @intCast(if (self.offset > 0) self.offset else -self.offset); - try writer.print("{d:0>2}:{d:0>2}", .{ abs / 60, abs % 60 }); + const offset_hour = abs / 60; + if (offset_hour < -24 or offset_hour > 24) return error.Range; + const offset_minute = abs % 60; + try writer.print("{d:0>2}:{d:0>2}", .{ offset_hour, offset_minute }); } } }; From a2f707ffcec9d9812b1afc0a48c48421c49c1490 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 18:49:01 -0400 Subject: [PATCH 08/22] add efi epoch --- lib/std/date/epoch.zig | 2 ++ lib/std/os/uefi.zig | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/date/epoch.zig b/lib/std/date/epoch.zig index e66a27fdadf9..7b895431c339 100644 --- a/lib/std/date/epoch.zig +++ b/lib/std/date/epoch.zig @@ -27,6 +27,8 @@ pub const gps = 3_657; pub const clr = -719_164; /// 1582-10-15 pub const uefi = -141_427; +/// 1900-01-01 +pub const efi = -25_567; pub const unix = posix; pub const android = posix; diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index a64994f4ef67..c54db420df7c 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -131,7 +131,7 @@ pub const Time = extern struct { /// Time is to be interpreted as local time pub const unspecified_timezone: i16 = 0x7ff; - pub const DateTime = std.date_time.DateTimeAdvanced(u16, std.date.epoch.uefi, 9, true); + pub const DateTime = std.date_time.DateTimeAdvanced(u16, std.date.epoch.efi, 9, true); pub fn toEpoch(self: @This()) DateTime.EpochSubseconds { const dt = DateTime{ From 71eeeba57c861c27abb61f49e558c1ed532c1951 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 5 Apr 2024 19:43:29 -0400 Subject: [PATCH 09/22] std.time.epoch -> std.date.epoch --- lib/std/os/windows.zig | 4 ++-- lib/std/posix.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 1c0b7d9d8062..d3ed013ccbf9 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2047,13 +2047,13 @@ pub fn peb() *PEB { /// This function returns the number of nanoseconds since the canonical epoch, /// which is the POSIX one (Jan 01, 1970 AD). pub fn fromSysTime(hns: i64) i128 { - const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100); + const adjusted_epoch: i128 = hns + std.date.epoch.windows * (std.time.ns_per_s / 100); return adjusted_epoch * 100; } pub fn toSysTime(ns: i128) i64 { const hns = @divFloor(ns, 100); - return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100); + return @as(i64, @intCast(hns)) - std.date.epoch.windows * (std.time.ns_per_s / 100); } pub fn fileTimeToNanoSeconds(ft: FILETIME) i128 { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index e80e64b45e27..ece0963a9336 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5553,7 +5553,7 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; const ft_per_s = std.time.ns_per_s / 100; tp.* = .{ - .tv_sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.time.epoch.windows, + .tv_sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.date.epoch.windows, .tv_nsec = @as(c_long, @intCast(ft64 % ft_per_s)) * 100, }; return; From 5a52f4af1678c031f2bce80ba4d10ac8ab39c9cb Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Sat, 6 Apr 2024 11:51:24 -0400 Subject: [PATCH 10/22] make monday first day of week --- lib/std/date/gregorian.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index 3484dfb7f9a7..2536c6fbe0a8 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -288,13 +288,13 @@ test Date { const WeekdayInt = IntFittingRange(1, 7); pub const WeekdayT = enum(WeekdayInt) { - sun = 1, - mon = 2, - tue = 3, - wed = 4, - thu = 5, - fri = 6, - sat = 7, + mon = 1, + tue = 2, + wed = 3, + thu = 4, + fri = 5, + sat = 6, + sun = 7, pub const Int = WeekdayInt; From b4c6378b9c0d5908bdf9ce338518d37baa13b717 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Mon, 8 Apr 2024 19:46:43 -0400 Subject: [PATCH 11/22] address FObersteiner feedback --- lib/std/date_time.zig | 12 ++++++------ lib/std/time.zig | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index 97acb9277ab1..9dac8c1225b9 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -12,24 +12,24 @@ pub fn DateTime(comptime DateT: type, comptime TimeT: type) type { /// 9 = nanoseconds pub const EpochSubseconds = std.meta.Int( @typeInfo(Date.EpochDays).Int.signedness, - @typeInfo(Date.EpochDays).Int.bits + std.math.log2_int_ceil(usize, Time.fs_per_day), + @typeInfo(Date.EpochDays).Int.bits + std.math.log2_int_ceil(usize, Time.subseconds_per_day), ); const Self = @This(); - /// New date time from fractional epoch seconds. + /// New date time from fractional seconds since `Date.epoch`. pub fn fromEpoch(subseconds: EpochSubseconds, time_opts: Time.Options) Self { - const days = @divFloor(subseconds, s_per_day * Time.fs_per_s); + const days = @divFloor(subseconds, s_per_day * Time.subseconds_per_s); const new_date = Date.fromEpoch(@intCast(days)); - const day_seconds = std.math.comptimeMod(subseconds, s_per_day * Time.fs_per_s); + const day_seconds = std.math.comptimeMod(subseconds, s_per_day * Time.subseconds_per_s); const new_time = Time.fromDaySeconds(day_seconds, time_opts); return .{ .date = new_date, .time = new_time }; } - /// Returns fractional epoch seconds. + /// Returns fractional seconds since `Date.epoch`. pub fn toEpoch(self: Self) EpochSubseconds { var res: EpochSubseconds = 0; - res += @as(EpochSubseconds, self.date.toEpoch()) * s_per_day * Time.fs_per_s; + res += @as(EpochSubseconds, self.date.toEpoch()) * s_per_day * Time.subseconds_per_s; res += self.time.toDaySeconds(); return res; } diff --git a/lib/std/time.zig b/lib/std/time.zig index 817a6af8562c..99e2e4562baa 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -367,27 +367,27 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { const Self = @This(); pub const precision = precision_; - pub const fs_per_s = multiplier; - pub const fs_per_min = 60 * fs_per_s; - pub const fs_per_hour = 60 * fs_per_min; - pub const fs_per_day = 24 * fs_per_hour; + pub const subseconds_per_s = multiplier; + pub const subseconds_per_min = 60 * subseconds_per_s; + pub const subseconds_per_hour = 60 * subseconds_per_min; + pub const subseconds_per_day = 24 * subseconds_per_hour; pub const Options = struct { offset: IMinutes = 0, }; pub fn fromDaySeconds(seconds: DaySubseconds, options: Options) Self { - const fs_offset = @as(IDaySubseconds, options.offset) * fs_per_min + seconds; - var fs = std.math.comptimeMod(fs_offset, fs_per_day); + const fs_offset = @as(IDaySubseconds, options.offset) * subseconds_per_min + seconds; + var fs = std.math.comptimeMod(fs_offset, subseconds_per_day); - const hour: Hour = @intCast(@divFloor(fs, fs_per_hour)); - fs -= @as(DaySubseconds, @intCast(hour)) * fs_per_hour; + const hour: Hour = @intCast(@divFloor(fs, subseconds_per_hour)); + fs -= @as(DaySubseconds, @intCast(hour)) * subseconds_per_hour; - const minute: Minute = @intCast(@divFloor(fs, fs_per_min)); - fs -= @as(DaySubseconds, @intCast(minute)) * fs_per_min; + const minute: Minute = @intCast(@divFloor(fs, subseconds_per_min)); + fs -= @as(DaySubseconds, @intCast(minute)) * subseconds_per_min; - const second: Second = @intCast(@divFloor(fs, fs_per_s)); - fs -= @as(DaySubseconds, @intCast(second)) * fs_per_s; + const second: Second = @intCast(@divFloor(fs, subseconds_per_s)); + fs -= @as(DaySubseconds, @intCast(second)) * subseconds_per_s; return .{ .hour = hour, @@ -404,9 +404,9 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { pub fn toDaySecondsZone(self: Self, zone: IMinutes) DaySubseconds { var sec = @as(IDaySubseconds, zone) * 60 * multiplier; - sec += @as(IDaySubseconds, self.hour) * fs_per_hour; - sec += @as(IDaySubseconds, self.minute) * fs_per_min; - sec += @as(IDaySubseconds, self.second) * fs_per_s; + sec += @as(IDaySubseconds, self.hour) * subseconds_per_hour; + sec += @as(IDaySubseconds, self.minute) * subseconds_per_min; + sec += @as(IDaySubseconds, self.second) * subseconds_per_s; sec += @as(IDaySubseconds, self.subsecond); return std.math.comptimeMod(sec, s_per_day * multiplier); From b5840aebdc1635838f4045f337f3d95b28eeed67 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Tue, 9 Apr 2024 12:38:53 -0400 Subject: [PATCH 12/22] address @notcancername feedback --- lib/std/date.zig | 8 ++++---- lib/std/date/gregorian.zig | 22 +++++++++++----------- lib/std/date_time.zig | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/std/date.zig b/lib/std/date.zig index a817e1a3687b..538e2070c379 100644 --- a/lib/std/date.zig +++ b/lib/std/date.zig @@ -1,14 +1,14 @@ pub const gregorian = @import("./date/gregorian.zig"); pub const epoch = @import("./date/epoch.zig"); -pub const Date = gregorian.Date; pub fn DatePosix(comptime Year: type) type { return gregorian.Date(Year, epoch.posix); } -pub const Date16 = DatePosix(i16); -pub const Date32 = DatePosix(i32); -pub const Date64 = DatePosix(i64); +/// A Gregorian Date using days since `1970-01-01` for its epoch methods. +/// +/// Supports dates between years -32_768 and 32_768. +pub const Date = DatePosix(i16); test { _ = gregorian; diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index 2536c6fbe0a8..48ac5d0ed6c0 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -200,7 +200,7 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ /// Epoch is days since 1970 pub fn Date(comptime Year: type, epoch: comptime_int) type { - const shift = solve_shift(Year, epoch) catch unreachable; + const shift = solveShift(Year, epoch) catch unreachable; return DateAdvanced(Year, epoch, shift); } @@ -368,7 +368,7 @@ test is_leap { try expectEqual(true, is_leap(2400)); } -pub fn days_between_years(from: usize, to: usize) usize { +pub fn daysBetweenYears(from: usize, to: usize) usize { var res: usize = 0; var i: usize = from; while (i < to) : (i += 1) { @@ -377,14 +377,14 @@ pub fn days_between_years(from: usize, to: usize) usize { return res; } -test days_between_years { - try expectEqual(366, days_between_years(2000, 2001)); - try expectEqual(146_097, days_between_years(0, 400)); +test daysBetweenYears { + try expectEqual(366, daysBetweenYears(2000, 2001)); + try expectEqual(146_097, daysBetweenYears(0, 400)); } /// The Gregorian calendar repeats every 400 years. const era = 400; -const era_days: comptime_int = days_between_years(0, 400); // 146_097 +const era_days: comptime_int = daysBetweenYears(0, 400); // 146_097 /// Number of days between two consecutive March equinoxes const days_in_year = struct { @@ -409,7 +409,7 @@ fn UIntFitting(to: comptime_int) type { /// Finds minimum epoch shift that covers the range: /// [std.math.minInt(YearT), std.math.maxInt(YearT)] -fn solve_shift(comptime Year: type, epoch: comptime_int) !comptime_int { +fn solveShift(comptime Year: type, epoch: comptime_int) !comptime_int { const shift = std.math.maxInt(Year) / era + 1; const L = era * shift; @@ -424,10 +424,10 @@ fn solve_shift(comptime Year: type, epoch: comptime_int) !comptime_int { return shift; } -test solve_shift { - try expectEqual(82, try solve_shift(i16, 719_468)); - try expectEqual(5_368_710, try solve_shift(i32, 719_468)); - try expectEqual(23_058_430_092_136_940, try solve_shift(i64, 719_468)); +test solveShift { + try expectEqual(82, try solveShift(i16, 719_468)); + try expectEqual(5_368_710, try solveShift(i32, 719_468)); + try expectEqual(23_058_430_092_136_940, try solveShift(i64, 719_468)); } const std = @import("std"); diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index 9dac8c1225b9..112318d3a3ee 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -75,7 +75,7 @@ pub fn DateTimeAdvanced( return DateTime(date_mod.Date(Year, epoch), time_mod.Time(time_precision, time_zoned)); } -pub const Date16Time = DateTime(date_mod.Date16, time_mod.Time(0, false)); +pub const Date16Time = DateTime(date_mod.Date, time_mod.Time(0, false)); comptime { assert(@sizeOf(Date16Time) == 8); @@ -119,7 +119,7 @@ test "Date epoch" { } test "Date RFC 3339 section 5.8" { - const T = DateTime(date_mod.Date16, time_mod.Time(3, true)); + const T = DateTime(date_mod.Date, time_mod.Time(3, true)); const expectEqual = std.testing.expectEqual; const t1 = T{ .date = .{ .year = 1985, .month = .apr, .day = 12 }, From 54413d4e184695e73266eb926117bd00cb2c8083 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Tue, 9 Apr 2024 14:23:50 -0400 Subject: [PATCH 13/22] remove rfc 3339 --- lib/std/date.zig | 6 +- lib/std/date/gregorian.zig | 41 ------------ lib/std/date_time.zig | 79 ++++------------------- lib/std/time.zig | 128 +++---------------------------------- 4 files changed, 22 insertions(+), 232 deletions(-) diff --git a/lib/std/date.zig b/lib/std/date.zig index 538e2070c379..b381168e4466 100644 --- a/lib/std/date.zig +++ b/lib/std/date.zig @@ -1,14 +1,10 @@ pub const gregorian = @import("./date/gregorian.zig"); pub const epoch = @import("./date/epoch.zig"); -pub fn DatePosix(comptime Year: type) type { - return gregorian.Date(Year, epoch.posix); -} - /// A Gregorian Date using days since `1970-01-01` for its epoch methods. /// /// Supports dates between years -32_768 and 32_768. -pub const Date = DatePosix(i16); +pub const Date = gregorian.Date(i16, epoch.posix); test { _ = gregorian; diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index 48ac5d0ed6c0..9f725ac325cd 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -167,34 +167,6 @@ pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_ const epoch_days = self.toEpoch() +% Weekday.thu.numeric(); return @enumFromInt(std.math.comptimeMod(epoch_days, 7)); } - - pub fn fromRfc3339(str: *const [10]u8) !Self { - if (str[4] != '-' or str[7] != '-') return error.Parsing; - - const year = try std.fmt.parseInt(IntFittingRange(0, 9999), str[0..4], 10); - const month = try std.fmt.parseInt(Month.Int, str[5..7], 10); - if (month < 1 or month > 12) return error.Parsing; - const m: Month = @enumFromInt(month); - const day = try std.fmt.parseInt(Day, str[8..10], 10); - if (day < 1 or day > m.days(is_leap(year))) return error.Parsing; - - return .{ - .year = @intCast(year), // if YearT is `i8` or `u8` this may fail. increase it to not fail. - .month = m, - .day = day, - }; - } - - pub fn toRfc3339(self: Self, writer: anytype) !void { - if (self.year < 0 or self.year > 9999) return error.Range; - if (self.day < 1 or self.day > 99) return error.Range; - if (self.month.numeric() < 1 or self.month.numeric() > 12) return error.Range; - try writer.print("{d:0>4}-{d:0>2}-{d:0>2}", .{ - @as(UEpochDays, @intCast(self.year)), - self.month.numeric(), - self.day, - }); - } }; } @@ -241,13 +213,6 @@ test "Date from and to epoch" { try testFromToEpoch(Date(u64, epoch.windows)); } -test "Date RFC3339" { - const T = Date(i16, 0); - try expectEqual(T{ .year = 2000, .month = .jan, .day = 1 }, try T.fromRfc3339("2000-01-01")); - try std.testing.expectError(error.Parsing, T.fromRfc3339("2000T01-01")); - try std.testing.expectError(error.InvalidCharacter, T.fromRfc3339("2000-01-AD")); -} - test Date { const T = Date(i16, 0); const d1 = T{ .year = 1960, .month = .jan, .day = 1 }; @@ -278,12 +243,6 @@ test Date { try expectEqual(.tue, (T{ .year = 1980, .month = .jan, .day = 1 }).weekday()); // $ date -d '1960-01-01' try expectEqual(.fri, d1.weekday()); - - try expectEqual(T{ .year = 2000, .month = .jan, .day = 1 }, try T.fromRfc3339("2000-01-01")); - var buf: [10]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - try (T{ .year = 2000, .month = .jan, .day = 1 }).toRfc3339(stream.writer()); - try std.testing.expectEqualStrings("2000-01-01", stream.getWritten()); } const WeekdayInt = IntFittingRange(1, 7); diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index 112318d3a3ee..f54e43f45ea9 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -1,4 +1,4 @@ -pub fn DateTime(comptime DateT: type, comptime TimeT: type) type { +pub fn DateTimeAdvanced(comptime DateT: type, comptime TimeT: type) type { return struct { date: Date, time: Time = .{}, @@ -18,11 +18,11 @@ pub fn DateTime(comptime DateT: type, comptime TimeT: type) type { const Self = @This(); /// New date time from fractional seconds since `Date.epoch`. - pub fn fromEpoch(subseconds: EpochSubseconds, time_opts: Time.Options) Self { + pub fn fromEpoch(subseconds: EpochSubseconds) Self { const days = @divFloor(subseconds, s_per_day * Time.subseconds_per_s); const new_date = Date.fromEpoch(@intCast(days)); const day_seconds = std.math.comptimeMod(subseconds, s_per_day * Time.subseconds_per_s); - const new_time = Time.fromDaySeconds(day_seconds, time_opts); + const new_time = Time.fromDaySeconds(day_seconds); return .{ .date = new_date, .time = new_time }; } @@ -43,47 +43,27 @@ pub fn DateTime(comptime DateT: type, comptime TimeT: type) type { minute: i64, second: i64, subsecond: i64, - ) DateTime { + ) Self { const time = self.time.addWithOverflow(hour, minute, second, subsecond); const date = self.date.add(year, month, day + time.day_overflow); return .{ .date = date, .time = time.time }; } - - pub fn fromRfc3339(str: []const u8) !Self { - if (str.len < 10 + "hh:mm:ssZ".len) return error.Parsing; - if (std.ascii.toUpper(str[10]) != 'T') return error.Parsing; - return .{ - .date = try Date.fromRfc3339(str[0..10]), - .time = try Time.fromRfc3339(str[11..]), - }; - } - - pub fn toRfc3339(self: Self, writer: anytype) !void { - try self.date.toRfc3339(writer); - try writer.writeByte('T'); - try self.time.toRfc3339(writer); - } }; } -pub fn DateTimeAdvanced( - comptime Year: type, - epoch: comptime_int, - time_precision: comptime_int, - comptime time_zoned: bool, -) type { - return DateTime(date_mod.Date(Year, epoch), time_mod.Time(time_precision, time_zoned)); -} - -pub const Date16Time = DateTime(date_mod.Date, time_mod.Time(0, false)); +/// A DateTime using days since `1970-01-01` for its epoch methods. +/// +/// Supports dates between years -32_768 and 32_768. +/// Supports times at a second resolution. +pub const DateTime = DateTimeAdvanced(date_mod.Date, time_mod.Time(0)); comptime { - assert(@sizeOf(Date16Time) == 8); + assert(@sizeOf(DateTime) == 8); } /// Tests EpochSeconds -> DateTime and DateTime -> EpochSeconds -fn testEpoch(secs: Date16Time.EpochSubseconds, dt: Date16Time) !void { - const actual_dt = Date16Time.fromEpoch(secs, .{}); +fn testEpoch(secs: DateTime.EpochSubseconds, dt: DateTime) !void { + const actual_dt = DateTime.fromEpoch(secs); try std.testing.expectEqual(dt, actual_dt); try std.testing.expectEqual(secs, dt.toEpoch()); } @@ -118,41 +98,6 @@ test "Date epoch" { }); } -test "Date RFC 3339 section 5.8" { - const T = DateTime(date_mod.Date, time_mod.Time(3, true)); - const expectEqual = std.testing.expectEqual; - const t1 = T{ - .date = .{ .year = 1985, .month = .apr, .day = 12 }, - .time = .{ .hour = 23, .minute = 20, .second = 50, .subsecond = 520 }, - }; - try expectEqual(t1, try T.fromRfc3339("1985-04-12T23:20:50.52Z")); - const t2 = T{ - .date = .{ .year = 1996, .month = .dec, .day = 19 }, - .time = .{ .hour = 16, .minute = 39, .second = 57, .offset = -8 * 60 }, - }; - try expectEqual(t2, try T.fromRfc3339("1996-12-19T16:39:57-08:00")); - const t3 = T{ - .date = .{ .year = 1990, .month = .dec, .day = 31 }, - .time = .{ .hour = 23, .minute = 59, .second = 60 }, - }; - try expectEqual(t3, try T.fromRfc3339("1990-12-31T23:59:60Z")); - const t4 = T{ - .date = .{ .year = 1990, .month = .dec, .day = 31 }, - .time = .{ .hour = 15, .minute = 59, .second = 60, .offset = -8 * 60 }, - }; - try expectEqual(t4, try T.fromRfc3339("1990-12-31T15:59:60-08:00")); - const t5 = T{ - .date = .{ .year = 1937, .month = .jan, .day = 1 }, - .time = .{ .hour = 12, .second = 27, .subsecond = 870, .offset = 20 }, - }; - try expectEqual(t5, try T.fromRfc3339("1937-01-01T12:00:27.87+00:20")); - - var buf: [32]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - try t5.toRfc3339(stream.writer()); - try std.testing.expectEqualStrings("1937-01-01T12:00:27.870+00:20", stream.getWritten()); -} - const std = @import("std.zig"); const date_mod = @import("./date.zig"); const time_mod = @import("./time.zig"); diff --git a/lib/std/time.zig b/lib/std/time.zig index 99e2e4562baa..ced98966bdeb 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -344,7 +344,7 @@ test Timer { try testing.expect(timer.read() < time_1); } -pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { +pub fn Time(precision_: comptime_int) type { const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); return struct { hour: Hour = 0, @@ -353,8 +353,6 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { second: Second = 0, /// Milliseconds, microseconds, or nanoseconds. subsecond: Subsecond = 0, - /// Offset of `hour` and `minute` from in minutes from UTC. - offset: IMinutes = 0, pub const Hour = IntFittingRange(0, 23); pub const Minute = IntFittingRange(0, 59); @@ -362,7 +360,6 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { pub const Subsecond = IntFittingRange(0, if (precision_ == 0) 0 else multiplier); pub const DaySubseconds = IntFittingRange(0, s_per_day * multiplier); const IDaySubseconds = std.meta.Int(.signed, @typeInfo(DaySubseconds).Int.bits + 1); - pub const IMinutes = if (has_offset) IntFittingRange(-24 * 60, 24 * 60) else u0; const Self = @This(); @@ -372,12 +369,8 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { pub const subseconds_per_hour = 60 * subseconds_per_min; pub const subseconds_per_day = 24 * subseconds_per_hour; - pub const Options = struct { - offset: IMinutes = 0, - }; - - pub fn fromDaySeconds(seconds: DaySubseconds, options: Options) Self { - const fs_offset = @as(IDaySubseconds, options.offset) * subseconds_per_min + seconds; + pub fn fromDaySeconds(seconds: DaySubseconds) Self { + const fs_offset = seconds; var fs = std.math.comptimeMod(fs_offset, subseconds_per_day); const hour: Hour = @intCast(@divFloor(fs, subseconds_per_hour)); @@ -394,16 +387,11 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { .minute = minute, .second = second, .subsecond = @intCast(fs), - .offset = options.offset, }; } pub fn toDaySeconds(self: Self) DaySubseconds { - return self.toDaySecondsZone(self.offset); - } - - pub fn toDaySecondsZone(self: Self, zone: IMinutes) DaySubseconds { - var sec = @as(IDaySubseconds, zone) * 60 * multiplier; + var sec: IDaySubseconds = 0; sec += @as(IDaySubseconds, self.hour) * subseconds_per_hour; sec += @as(IDaySubseconds, self.minute) * subseconds_per_min; sec += @as(IDaySubseconds, self.second) * subseconds_per_s; @@ -441,122 +429,24 @@ pub fn Time(precision_: comptime_int, comptime has_offset: bool) type { pub fn add(self: Self, hour: i64, minute: i64, second: i64, subsecond: i64) Self { return self.addWithOverflow(hour, minute, second, subsecond).time; } - - pub fn fromRfc3339(str: []const u8) !Self { - if (str.len < "hh:mm:ssZ".len) return error.Parsing; - - if (str[2] != ':' or str[5] != ':') return error.Parsing; - - const hour = try std.fmt.parseInt(Hour, str[0..2], 10); - const minute = try std.fmt.parseInt(Minute, str[3..5], 10); - const second = try std.fmt.parseInt(Second, str[6..8], 10); - - var i: usize = 8; - const subsecond: Subsecond = if (str[i] == '.') brk: { - i += 1; - while (i < str.len and std.ascii.isDigit(str[i])) : (i += 1) {} - if (Subsecond == u0) break :brk 0; - const subsecond_str = str[9..i]; - // Choose largest performant type. - // Ideally, this would allow infinite precision. - const T = f64; - var subsecond = try std.fmt.parseFloat(T, subsecond_str); - const actual_precision: T = @floatFromInt(subsecond_str.len); - subsecond *= std.math.pow(T, 10, precision - actual_precision); - - break :brk @intFromFloat(subsecond); - } else 0; - - // timezone required - if (str.len <= i) return error.Parsing; - - const offset = if (std.ascii.toUpper(str[i]) == 'Z') 0 else brk: { - var sign: IMinutes = 1; - if (str[i] == '-') { - sign = -1; - i += 1; - } else if (str[i] == '+') { - i += 1; - } - - const offset_hour = try std.fmt.parseInt(IMinutes, str[i..][0..2], 10); - if (str[i + 2] != ':') return error.Parsing; - const offset_minute = try std.fmt.parseInt(IMinutes, str[i + 3 ..][0..2], 10); - - break :brk sign * (offset_hour * 60 + offset_minute); - }; - - return .{ .hour = hour, .minute = minute, .second = second, .subsecond = subsecond, .offset = offset }; - } - - pub fn toRfc3339(self: Self, writer: anytype) !void { - if (self.hour > 24 or self.minute > 59 or self.second > 60) return error.Range; - try writer.print("{d:0>2}:{d:0>2}:{d:0>2}", .{ self.hour, self.minute, self.second }); - if (self.subsecond != 0) { - // We could trim trailing zeros here to save space. - try writer.print(".{d}", .{self.subsecond}); - } - if (self.offset == 0) { - try writer.writeByte('Z'); - } else { - try writer.writeByte(if (self.offset > 0) '+' else '-'); - const abs: u16 = @intCast(if (self.offset > 0) self.offset else -self.offset); - const offset_hour = abs / 60; - if (offset_hour < -24 or offset_hour > 24) return error.Range; - const offset_minute = abs % 60; - try writer.print("{d:0>2}:{d:0>2}", .{ offset_hour, offset_minute }); - } - } }; } test Time { - const TimeMilli = Time(3, false); + const TimeMilli = Time(3); const t1 = TimeMilli{}; const expectEqual = std.testing.expectEqual; // cause each place to overflow try expectEqual(TimeMilli{ .hour = 2, .minute = 2, .second = 2, .subsecond = 1 }, t1.add(25, 61, 61, 1001)); // cause each place to underflow try expectEqual(TimeMilli{ .hour = 21, .minute = 57, .second = 57, .subsecond = 999 }, t1.add(-25, -61, -61, -1001)); - - const TimeMilliOffset = Time(3, true); - // positive offset - try expectEqual( - TimeMilliOffset{ .hour = 1, .minute = 30, .offset = 90 }, - TimeMilliOffset.fromDaySeconds(0, .{ .offset = 90 }), - ); - // negative offset - try expectEqual( - TimeMilliOffset{ .hour = 22, .minute = 30, .offset = -90 }, - TimeMilliOffset.fromDaySeconds(0, .{ .offset = -90 }), - ); - - try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .offset = -90 }, try TimeMilliOffset.fromRfc3339("22:30:00-01:30")); - try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .offset = 90 }, try TimeMilliOffset.fromRfc3339("22:30:00.0000+01:30")); - try expectEqual(TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100 }, try TimeMilliOffset.fromRfc3339("22:30:20.1Z")); - - const expectError = std.testing.expectError; - try expectError(error.Parsing, TimeMilliOffset.fromRfc3339("22:30:20.100")); // missing timezone - try expectError(error.InvalidCharacter, TimeMilliOffset.fromRfc3339("22:30:20.1a00")); - try expectError(error.Parsing, TimeMilliOffset.fromRfc3339("2:00:20Z")); // missing hour digit - - var buf: [32]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const time = TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100 }; - try time.toRfc3339(stream.writer()); - try std.testing.expectEqualStrings("22:30:20.100Z", stream.getWritten()); - - stream.reset(); - const time2 = TimeMilliOffset{ .hour = 22, .minute = 30, .second = 20, .subsecond = 100, .offset = 100 }; - try time2.toRfc3339(stream.writer()); - try std.testing.expectEqualStrings("22:30:20.100+01:40", stream.getWritten()); } comptime { - assert(@sizeOf(Time(0, false)) == 3); - assert(@sizeOf(Time(3, false)) == 6); - assert(@sizeOf(Time(6, false)) == 8); - assert(@sizeOf(Time(9, false)) == 8); + assert(@sizeOf(Time(0)) == 3); + assert(@sizeOf(Time(3)) == 6); + assert(@sizeOf(Time(6)) == 8); + assert(@sizeOf(Time(9)) == 8); } test { From 39373908bd9194d57bf293605036d81b04b9d0e1 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Tue, 9 Apr 2024 14:42:45 -0400 Subject: [PATCH 14/22] fix uefi timezone --- lib/std/os/uefi.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index c54db420df7c..3dd68ab244e3 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -145,10 +145,9 @@ pub const Time = extern struct { .minute = self.minute, .second = self.second, .subsecond = self.nanosecond, - .timezone = self.timezone, }, }; - return dt.toEpoch(); + return dt.toEpoch() - self.timezone * DateTime.Time.subseconds_per_min; } }; From fdbdf4318dce0b6684574ea939caed01e03421b3 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Tue, 9 Apr 2024 14:48:18 -0400 Subject: [PATCH 15/22] update Date16Time to DateTime --- lib/compiler/aro/aro/Compilation.zig | 4 ++-- lib/std/crypto/Certificate.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index 99972a989349..00864f155bb7 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -194,8 +194,8 @@ fn getTimestamp(comp: *Compilation) !u47 { } fn generateDateAndTime(w: anytype, timestamp: u47) !void { - const DateTime = std.date_time.Date16Time; - const dt = DateTime.fromEpoch(timestamp, .{}); + const DateTime = std.date_time.DateTime; + const dt = DateTime.fromEpoch(timestamp); const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; std.debug.assert(DateTime.Date.Month.jan.numeric() == 1); diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index 16b86b515484..8520fdac5565 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -817,7 +817,7 @@ fn verifyEd25519( const std = @import("../std.zig"); const crypto = std.crypto; const mem = std.mem; -const DateTime = std.date_time.Date16Time; +const DateTime = std.date_time.DateTime; const Certificate = @This(); pub const der = struct { From 32638f70662c8d50494c98aea650aa44b93ecf16 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Tue, 9 Apr 2024 15:44:30 -0400 Subject: [PATCH 16/22] fix comptimeDivFloor --- lib/std/math.zig | 9 +++++---- lib/std/std.zig | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index 6187b575389a..8a72bf2390a5 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -1775,10 +1775,7 @@ pub fn comptimeMod(num: anytype, comptime denom: comptime_int) IntFittingRange(0 fn ComptimeDiv(comptime Num: type, comptime divisor: comptime_int) type { const info = @typeInfo(Num).Int; - var n_bits = info.bits; - n_bits -= log2_int(usize, divisor); - - return std.meta.Int(info.signedness, n_bits); + return std.meta.Int(info.signedness, info.bits - log2(divisor)); } /// Return the quotient of `num` with the smallest integer type @@ -1786,6 +1783,10 @@ pub fn comptimeDivFloor(num: anytype, comptime divisor: comptime_int) ComptimeDi return @intCast(@divFloor(num, divisor)); } +test comptimeDivFloor { + try std.testing.expectEqual(@as(u13, 100), comptimeDivFloor(@as(u16, 1000), 10)); +} + pub const F80 = struct { fraction: u64, exp: u16, diff --git a/lib/std/std.zig b/lib/std/std.zig index 1e2a6e084974..f98e754b5e64 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -18,6 +18,8 @@ pub const BufSet = @import("buf_set.zig").BufSet; pub const ChildProcess = @import("child_process.zig").ChildProcess; pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap; pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql; +pub const Date = date.Date; +pub const DateTime = date_time.DateTime; pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList; pub const DynLib = @import("dynamic_library.zig").DynLib; pub const DynamicBitSet = bit_set.DynamicBitSet; @@ -50,6 +52,7 @@ pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanag pub const TailQueue = DoublyLinkedList; pub const Target = @import("Target.zig"); pub const Thread = @import("Thread.zig"); +pub const Time = time.Time; pub const Treap = @import("treap.zig").Treap; pub const Tz = tz.Tz; pub const Uri = @import("Uri.zig"); From 81376640d322fbc4ee0d0777e5a727bdd425144b Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Wed, 10 Apr 2024 13:51:26 -0400 Subject: [PATCH 17/22] workaround x86_64 @min and airMulWithOverflow compiler bugs --- lib/std/date/gregorian.zig | 5 +++-- lib/std/time.zig | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index 9f725ac325cd..2678fc426ec4 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -187,8 +187,9 @@ fn testFromToEpoch(comptime T: type) !void { // Instead let's cycle through the first and last 1 << 16 part of each range. const min_epoch_day = (T{ .year = std.math.minInt(T.Year), .month = .jan, .day = 1 }).toEpoch(); const max_epoch_day = (T{ .year = std.math.maxInt(T.Year), .month = .dec, .day = 31 }).toEpoch(); - const range: u128 = @intCast(max_epoch_day - min_epoch_day); - for (0..@min(1 << 16, range)) |i| { + const diff = max_epoch_day - min_epoch_day; + const range: usize = if (max_epoch_day - min_epoch_day > 1 << 16) 1 << 16 else @intCast(diff); + for (0..range) |i| { const d3 = min_epoch_day + @as(T.EpochDays, @intCast(i)); try expectEqual(d3, T.fromEpoch(d3).toEpoch()); diff --git a/lib/std/time.zig b/lib/std/time.zig index ced98966bdeb..10399be6c9fd 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -370,23 +370,22 @@ pub fn Time(precision_: comptime_int) type { pub const subseconds_per_day = 24 * subseconds_per_hour; pub fn fromDaySeconds(seconds: DaySubseconds) Self { - const fs_offset = seconds; - var fs = std.math.comptimeMod(fs_offset, subseconds_per_day); + var subseconds = std.math.comptimeMod(seconds, subseconds_per_day); - const hour: Hour = @intCast(@divFloor(fs, subseconds_per_hour)); - fs -= @as(DaySubseconds, @intCast(hour)) * subseconds_per_hour; + const hour = @divFloor(subseconds, subseconds_per_hour); + subseconds -= hour * subseconds_per_hour; - const minute: Minute = @intCast(@divFloor(fs, subseconds_per_min)); - fs -= @as(DaySubseconds, @intCast(minute)) * subseconds_per_min; + const minute = @divFloor(subseconds, subseconds_per_min); + subseconds -= minute * subseconds_per_min; - const second: Second = @intCast(@divFloor(fs, subseconds_per_s)); - fs -= @as(DaySubseconds, @intCast(second)) * subseconds_per_s; + const second = @divFloor(subseconds, subseconds_per_s); + subseconds -= second * subseconds_per_s; return .{ - .hour = hour, - .minute = minute, - .second = second, - .subsecond = @intCast(fs), + .hour = @intCast(hour), + .minute = @intCast(minute), + .second = @intCast(second), + .subsecond = @intCast(subseconds), }; } From 282ff016d452fd49f7b7d7c416b7944222856625 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Thu, 11 Apr 2024 11:57:55 -0400 Subject: [PATCH 18/22] address @Vexu feedback --- lib/std/date/gregorian.zig | 14 +++++++------- lib/std/date_time.zig | 12 ++++++------ lib/std/time.zig | 6 ++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index 2678fc426ec4..f30994bcd4c0 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -16,6 +16,12 @@ /// To solve for `shift`, see `solve_shift`. /// /// [1] https://onlinelibrary.wiley.com/doi/epdf/10.1002/spe.3172 +const std = @import("std"); +const IntFittingRange = std.math.IntFittingRange; +const secs_per_day = std.time.s_per_day; +const expectEqual = std.testing.expectEqual; +const assert = std.debug.assert; + pub fn DateAdvanced(comptime YearT: type, epoch_: comptime_int, shift: comptime_int) type { // Zig's timestamp epoch is 1970-01-01. Ours is Mar 01, 0000 AD const epoch = epoch_ + 719_468; @@ -258,7 +264,7 @@ pub const WeekdayT = enum(WeekdayInt) { pub const Int = WeekdayInt; - /// Convenient conversion to `WeekdayInt`. sun = 1, sat = 7 + /// Convenient conversion to `WeekdayInt`. mon = 1, sun = 7 pub fn numeric(self: @This()) Int { return @intFromEnum(self); } @@ -389,9 +395,3 @@ test solveShift { try expectEqual(5_368_710, try solveShift(i32, 719_468)); try expectEqual(23_058_430_092_136_940, try solveShift(i64, 719_468)); } - -const std = @import("std"); -const IntFittingRange = std.math.IntFittingRange; -const secs_per_day = std.time.s_per_day; -const expectEqual = std.testing.expectEqual; -const assert = std.debug.assert; diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index f54e43f45ea9..3b26345d0d74 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -1,3 +1,9 @@ +const std = @import("std.zig"); +const date_mod = @import("./date.zig"); +const time_mod = @import("./time.zig"); +const s_per_day = time_mod.s_per_day; +const assert = std.debug.assert; + pub fn DateTimeAdvanced(comptime DateT: type, comptime TimeT: type) type { return struct { date: Date, @@ -97,9 +103,3 @@ test "Date epoch" { .date = .{ .year = std.math.maxInt(i16), .month = .dec, .day = 31 }, }); } - -const std = @import("std.zig"); -const date_mod = @import("./date.zig"); -const time_mod = @import("./time.zig"); -const s_per_day = time_mod.s_per_day; -const assert = std.debug.assert; diff --git a/lib/std/time.zig b/lib/std/time.zig index 10399be6c9fd..fabce5922480 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -344,6 +344,12 @@ test Timer { try testing.expect(timer.read() < time_1); } +/// A time of day with a subsecond field capable of holding values +/// between 0 and 10 ** `precision_`. +/// +/// Time(3) = milliseconds +/// Time(6) = microseconds +/// Time(9) = nanoseconds pub fn Time(precision_: comptime_int) type { const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); return struct { From 3196d3a73164b529948ff59cc89895ebe68dd584 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Thu, 11 Apr 2024 12:32:59 -0400 Subject: [PATCH 19/22] adjust Time exports --- lib/std/date_time.zig | 2 +- lib/std/time.zig | 36 +++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/std/date_time.zig b/lib/std/date_time.zig index 3b26345d0d74..cb676a8e98bb 100644 --- a/lib/std/date_time.zig +++ b/lib/std/date_time.zig @@ -61,7 +61,7 @@ pub fn DateTimeAdvanced(comptime DateT: type, comptime TimeT: type) type { /// /// Supports dates between years -32_768 and 32_768. /// Supports times at a second resolution. -pub const DateTime = DateTimeAdvanced(date_mod.Date, time_mod.Time(0)); +pub const DateTime = DateTimeAdvanced(date_mod.Date, time_mod.Time); comptime { assert(@sizeOf(DateTime) == 8); diff --git a/lib/std/time.zig b/lib/std/time.zig index fabce5922480..3f724216bc24 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -347,10 +347,11 @@ test Timer { /// A time of day with a subsecond field capable of holding values /// between 0 and 10 ** `precision_`. /// -/// Time(3) = milliseconds -/// Time(6) = microseconds -/// Time(9) = nanoseconds -pub fn Time(precision_: comptime_int) type { +/// TimeAdvanced(0) = seconds +/// TimeAdvanced(3) = milliseconds +/// TimeAdvanced(6) = microseconds +/// TimeAdvanced(9) = nanoseconds +pub fn TimeAdvanced(precision_: comptime_int) type { const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); return struct { hour: Hour = 0, @@ -363,8 +364,8 @@ pub fn Time(precision_: comptime_int) type { pub const Hour = IntFittingRange(0, 23); pub const Minute = IntFittingRange(0, 59); pub const Second = IntFittingRange(0, 60); - pub const Subsecond = IntFittingRange(0, if (precision_ == 0) 0 else multiplier); - pub const DaySubseconds = IntFittingRange(0, s_per_day * multiplier); + pub const Subsecond = IntFittingRange(0, if (precision_ == 0) 0 else subseconds_per_s); + pub const DaySubseconds = IntFittingRange(0, s_per_day * subseconds_per_s); const IDaySubseconds = std.meta.Int(.signed, @typeInfo(DaySubseconds).Int.bits + 1); const Self = @This(); @@ -402,7 +403,7 @@ pub fn Time(precision_: comptime_int) type { sec += @as(IDaySubseconds, self.second) * subseconds_per_s; sec += @as(IDaySubseconds, self.subsecond); - return std.math.comptimeMod(sec, s_per_day * multiplier); + return std.math.comptimeMod(sec, s_per_day * subseconds_per_s); } /// Does not handle leap seconds. @@ -437,8 +438,7 @@ pub fn Time(precision_: comptime_int) type { }; } -test Time { - const TimeMilli = Time(3); +test TimeAdvanced { const t1 = TimeMilli{}; const expectEqual = std.testing.expectEqual; // cause each place to overflow @@ -448,11 +448,21 @@ test Time { } comptime { - assert(@sizeOf(Time(0)) == 3); - assert(@sizeOf(Time(3)) == 6); - assert(@sizeOf(Time(6)) == 8); - assert(@sizeOf(Time(9)) == 8); + assert(@sizeOf(TimeAdvanced(0)) == 3); + assert(@sizeOf(TimeAdvanced(3)) == 6); + assert(@sizeOf(TimeAdvanced(6)) == 8); + assert(@sizeOf(TimeAdvanced(9)) == 8); + assert(@sizeOf(TimeAdvanced(12)) == 16); } +/// Time with second precision. +pub const Time = TimeAdvanced(0); +/// Time with millisecond precision. +pub const TimeMilli = TimeAdvanced(3); +/// Time with microsecond precision. +/// Note: This is the same size `TimeNano`. If you want the extra precision use that instead. +pub const TimeMicro = TimeAdvanced(6); +/// Time with nanosecond precision. +pub const TimeNano = TimeAdvanced(9); test { _ = epoch; From 42321aa18e7a821024d117aa2bd442f8c4392bf9 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Thu, 11 Apr 2024 13:17:31 -0400 Subject: [PATCH 20/22] add some std.date types --- lib/std/date.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/date.zig b/lib/std/date.zig index b381168e4466..ea066d738290 100644 --- a/lib/std/date.zig +++ b/lib/std/date.zig @@ -5,6 +5,9 @@ pub const epoch = @import("./date/epoch.zig"); /// /// Supports dates between years -32_768 and 32_768. pub const Date = gregorian.Date(i16, epoch.posix); +pub const Month = Date.Month; +pub const Day = Date.Day; +pub const Weekday = Date.Weekday; test { _ = gregorian; From 3b08d55a803fbd6c75d26271dc55e35c7663c5f7 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Fri, 12 Apr 2024 11:42:34 -0400 Subject: [PATCH 21/22] fix comments --- lib/std/date/gregorian.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/date/gregorian.zig b/lib/std/date/gregorian.zig index f30994bcd4c0..be5bfa6a2c23 100644 --- a/lib/std/date/gregorian.zig +++ b/lib/std/date/gregorian.zig @@ -4,7 +4,7 @@ /// A Gregorian calendar date with: /// * A `year: YearT,` which may be unsigned and is relative to 0000-00-00. -/// * Conversion from and to an `EpochDays` type which is the number of days since `epoch`. +/// * Conversion to and from an `EpochDays` type which is the number of days since `epoch`. /// The conversion algorithm used is Euclidean Affine Functions by Neri and Schneider. [1] /// It has been chosen for its speed. /// * A carefully selected epoch `shift` which allows for fast unsigned arithmetic at the cost @@ -13,7 +13,7 @@ /// This implementation requires the `EpochDay` range cover all possible values of `YearT`. /// Providing an invalid combination of `epoch` and `shift` will trigger a comptime assertion. /// -/// To solve for `shift`, see `solve_shift`. +/// To solve for `shift`, see `solveShift`. /// /// [1] https://onlinelibrary.wiley.com/doi/epdf/10.1002/spe.3172 const std = @import("std"); From 261a8397a713cb7ea0c944b76cacd66a37ca3da4 Mon Sep 17 00:00:00 2001 From: clickingbuttons Date: Wed, 17 Apr 2024 20:13:48 -0400 Subject: [PATCH 22/22] try fixing -Dtarget=arm-linux-musleabihf -Dno-lib --- lib/std/time.zig | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index 3f724216bc24..2e6bf183df68 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -352,7 +352,6 @@ test Timer { /// TimeAdvanced(6) = microseconds /// TimeAdvanced(9) = nanoseconds pub fn TimeAdvanced(precision_: comptime_int) type { - const multiplier: comptime_int = try std.math.powi(usize, 10, precision_); return struct { hour: Hour = 0, minute: Minute = 0, @@ -371,6 +370,7 @@ pub fn TimeAdvanced(precision_: comptime_int) type { const Self = @This(); pub const precision = precision_; + const multiplier: comptime_int = std.math.powi(usize, 10, precision_) catch unreachable; pub const subseconds_per_s = multiplier; pub const subseconds_per_min = 60 * subseconds_per_s; pub const subseconds_per_hour = 60 * subseconds_per_min; @@ -447,13 +447,6 @@ test TimeAdvanced { try expectEqual(TimeMilli{ .hour = 21, .minute = 57, .second = 57, .subsecond = 999 }, t1.add(-25, -61, -61, -1001)); } -comptime { - assert(@sizeOf(TimeAdvanced(0)) == 3); - assert(@sizeOf(TimeAdvanced(3)) == 6); - assert(@sizeOf(TimeAdvanced(6)) == 8); - assert(@sizeOf(TimeAdvanced(9)) == 8); - assert(@sizeOf(TimeAdvanced(12)) == 16); -} /// Time with second precision. pub const Time = TimeAdvanced(0); /// Time with millisecond precision.