Skip to content

Commit

Permalink
add convenient datetime init
Browse files Browse the repository at this point in the history
  • Loading branch information
clickingbuttons committed Apr 27, 2024
1 parent a493555 commit 4cfd6c1
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 60 deletions.
7 changes: 6 additions & 1 deletion src/date.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
const gregorian = @import("./date/gregorian.zig");
//! Gregorian calendars.
pub const gregorian = @import("./date/gregorian.zig");
pub const epoch = @import("./date/epoch.zig");

pub const Date = Gregorian(i16, epoch.posix);
pub const Gregorian = gregorian.Gregorian;
pub const GregorianAdvanced = gregorian.Advanced;

test {
_ = gregorian;
_ = epoch;
}
1 change: 1 addition & 0 deletions src/date/epoch.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const std = @import("std");

/// Useful for calculating days between epochs.
pub const ComptimeDate = struct {
year: comptime_int,
month: Month,
Expand Down
73 changes: 35 additions & 38 deletions src/date/gregorian.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Introduced in 1582 as a revision of the Julian calendar.
//! World standard calendar.
//!
//! Currently implemented using Euclidian Affine Transforms:
//! https://onlinelibrary.wiley.com/doi/epdf/10.1002/spe.3172
//! Introduced in 1582 as a revision of the Julian calendar.
const std = @import("std");
const epoch_mod = @import("./epoch.zig");
const ComptimeDate = epoch_mod.ComptimeDate;
Expand All @@ -10,17 +9,16 @@ const secs_per_day = std.time.s_per_day;
const expectEqual = std.testing.expectEqual;
const assert = std.debug.assert;

/// A proleptic (projected backwards) Gregorian calendar date.
/// `epoch_` is in terms of days since 1970-01-01.
///
/// This implementation requires the `EpochDays` range cover all possible values of `YearT`.
/// A date on the proleptic (projected backwards) Gregorian calendar.
pub fn Advanced(comptime YearT: type, comptime epoch: ComptimeDate, shift: comptime_int) type {
return struct {
year: Year,
month: Month,
day: Day,
month: MonthT,
day: DayT,

pub const Year = YearT;
pub const Month = MonthT;
pub const Day = DayT;

/// Inclusive.
pub const min_epoch_day = daysSince(epoch, ComptimeDate.init(std.math.minInt(Year), 1, 1));
Expand Down Expand Up @@ -74,7 +72,7 @@ pub fn Advanced(comptime YearT: type, comptime epoch: ComptimeDate, shift: compt
return .{
.year = @intCast(year),
.month = @enumFromInt(month),
.day = @as(Day, self.day) + 1,
.day = @as(DayT, self.day) + 1,
};
}

Expand All @@ -98,8 +96,7 @@ pub fn Advanced(comptime YearT: type, comptime epoch: ComptimeDate, shift: compt

const Date = @This();

/// May save some typing vs struct initialization.
pub fn init(year: Year, month: Month, day: Day) Date {
pub fn init(year: Year, month: MonthT, day: DayT) Date {
return .{ .year = year, .month = month, .day = day };
}

Expand Down Expand Up @@ -301,7 +298,7 @@ pub const WeekdayT = enum(WeekdayInt) {
};

const MonthInt = IntFittingRange(1, 12);
pub const Month = enum(MonthInt) {
pub const MonthT = enum(MonthInt) {
jan = 1,
feb = 2,
mar = 3,
Expand Down Expand Up @@ -333,43 +330,43 @@ pub const Month = enum(MonthInt) {
28;
}
};
pub const Day = IntFittingRange(1, 31);

test Month {
try expectEqual(31, Month.jan.days(false));
try expectEqual(29, Month.feb.days(true));
try expectEqual(28, Month.feb.days(false));
try expectEqual(31, Month.mar.days(false));
try expectEqual(30, Month.apr.days(false));
try expectEqual(31, Month.may.days(false));
try expectEqual(30, Month.jun.days(false));
try expectEqual(31, Month.jul.days(false));
try expectEqual(31, Month.aug.days(false));
try expectEqual(30, Month.sep.days(false));
try expectEqual(31, Month.oct.days(false));
try expectEqual(30, Month.nov.days(false));
try expectEqual(31, Month.dec.days(false));
pub const DayT = IntFittingRange(1, 31);

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 {
pub fn isLeap(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));
test isLeap {
try expectEqual(false, isLeap(2095));
try expectEqual(true, isLeap(2096));
try expectEqual(false, isLeap(2100));
try expectEqual(true, isLeap(2400));
}

fn daysSinceJan01(d: ComptimeDate) u16 {
const leap = is_leap(d.year);
const leap = isLeap(d.year);
var res: u16 = d.day;
for (1..d.month + 1) |j| {
const m: Month = @enumFromInt(j);
const m: MonthT = @enumFromInt(j);
res += m.days(leap);
}

Expand All @@ -382,7 +379,7 @@ pub fn daysSince(from: ComptimeDate, to: ComptimeDate) comptime_int {

var i = from.year + eras * era.years;
while (i < to.year) : (i += 1) {
res += if (is_leap(i)) 366 else 365;
res += if (isLeap(i)) 366 else 365;
}

res += @intCast(daysSinceJan01(to));
Expand Down
38 changes: 28 additions & 10 deletions src/date_time.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ pub fn Advanced(comptime DateT: type, comptime TimeT: type) type {

const Self = @This();

pub fn init(year: Date.Year, month: Date.Month, day: Date.Day, hour: Time.Hour, minute: Time.Minute, second: Time.Second, subsecond: Time.Subsecond) Self {
return .{
.date = Date.init(year, month, day),
.time = Time.init(hour, minute, second, subsecond),
};
}

/// New date time from fractional seconds since `Date.epoch`.
pub fn fromEpoch(subseconds: EpochSubseconds) Self {
const days = @divFloor(subseconds, s_per_day * Time.subseconds_per_s);
Expand All @@ -39,19 +46,30 @@ pub fn Advanced(comptime DateT: type, comptime TimeT: type) type {
return res;
}

pub const Duration = struct {
date: Date.Duration,
time: Time.Duration,
};

pub fn add(
self: Self,
year: Date.Year,
month: Date.MonthAdd,
day: Date.IEpochDays,
hour: i64,
minute: i64,
second: i64,
subsecond: i64,
duration: Duration,
) 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 };
const time = self.time.addWithOverflow(duration.time);
var duration_date = duration.date;
duration_date.day += @intCast(time[1]);
const date = self.date.add(duration_date);
return .{ .date = date, .time = time[0] };
}
};
}

test Advanced {
const T = Advanced(date_mod.Date, time_mod.Sec);
const a = T.init(1970, .jan, 1, 0, 0, 0, 0);
const duration = T.Duration{
.date = T.Date.Duration.init(1, 1, 1),
.time = T.Time.Duration.init(25, 1, 1, 0),
};
try std.testing.expectEqual(T.init(1971, .feb, 3, 1, 1, 1, 0), a.add(duration));
}
10 changes: 2 additions & 8 deletions src/root.zig
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
//! Gregorian types.
const std = @import("std");
pub const epoch = @import("./date/epoch.zig");
pub const date = @import("./date.zig");
pub const time = @import("./time.zig");
const date_time = @import("./date_time.zig");

/// 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 = date.Gregorian(i16, epoch.posix);
pub const Date = date.Date;
pub const Month = Date.Month;
pub const Day = Date.Day;
pub const Weekday = Date.Weekday;

/// 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 = date_time.Advanced(Date, time.Sec);
Expand All @@ -29,7 +24,7 @@ fn testEpoch(secs: DateTime.EpochSubseconds, dt: DateTime) !void {
test DateTime {
// $ date -d @31535999 --iso-8601=seconds
try std.testing.expectEqual(8, @sizeOf(DateTime));
try testEpoch(0, .{ .date = .{ .year = 1970, .month = .jan, .day = 1 } });
try testEpoch(0, .{ .date = DateTime.Date.init(1970, .jan, 1) });
try testEpoch(31535999, .{
.date = .{ .year = 1970, .month = .dec, .day = 31 },
.time = .{ .hour = 23, .minute = 59, .second = 59 },
Expand Down Expand Up @@ -60,6 +55,5 @@ test DateTime {

test {
_ = date;
_ = epoch;
_ = time;
}
5 changes: 2 additions & 3 deletions src/time.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ pub fn Advanced(precision_: comptime_int) type {
pub const subseconds_per_hour = 60 * subseconds_per_min;
pub const subseconds_per_day = 24 * subseconds_per_hour;

/// May save some typing vs struct initialization.
pub fn init(hour: Hour, minute: Minute, second: Second, subsecond: Subsecond) Self {
return .{ .hour = hour, .minute = minute, .second = second, .subsecond = subsecond };
}
Expand Down Expand Up @@ -87,14 +86,14 @@ pub fn Advanced(precision_: comptime_int) type {
/// Returns value and how many days overflowed.
pub fn addWithOverflow(self: Self, duration: Duration) struct { Self, i64 } {
const fs = duration.subsecond + self.subsecond;
const s = duration.second + self.second + @divFloor(fs, 1000);
const s = duration.second + self.second + @divFloor(@as(i64, fs), 1000);
const m = duration.minute + self.minute + @divFloor(s, 60);
const h = duration.hour + self.hour + @divFloor(m, 60);
const overflow = @divFloor(h, 24);

return .{
Self{
.subsecond = std.math.comptimeMod(fs, 1000),
.subsecond = if (Duration.Subsecond == u0) 0 else std.math.comptimeMod(fs, 1000),
.second = std.math.comptimeMod(s, 60),
.minute = std.math.comptimeMod(m, 60),
.hour = std.math.comptimeMod(h, 24),
Expand Down

0 comments on commit 4cfd6c1

Please sign in to comment.