2022-07-18 07:03:29 +00:00
|
|
|
const DateTime = @This();
|
|
|
|
|
|
|
|
const std = @import("std");
|
2022-09-15 01:12:07 +00:00
|
|
|
const epoch = std.time.epoch;
|
2022-07-18 07:03:29 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
pub const Duration = struct {
|
|
|
|
seconds: i64 = 0,
|
|
|
|
};
|
|
|
|
|
2022-07-18 07:03:29 +00:00
|
|
|
seconds_since_epoch: i64,
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
// Tries the following methods for parsing, in order:
|
|
|
|
// 1. treats the string as a RFC 3339 DateTime
|
|
|
|
// 2. treats the string as the number of seconds since epoch
|
2022-09-15 01:12:07 +00:00
|
|
|
pub fn parse(str: []const u8) !DateTime {
|
2022-10-04 02:41:59 +00:00
|
|
|
return if (parseRfc3339(str)) |v|
|
|
|
|
v
|
|
|
|
else |_| if (std.fmt.parseInt(i64, str, 10)) |v|
|
|
|
|
DateTime{ .seconds_since_epoch = v }
|
|
|
|
else |_|
|
|
|
|
error.UnknownFormat;
|
2022-09-15 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 04:49:36 +00:00
|
|
|
pub const JsonParseAs = []const u8;
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
pub fn add(self: DateTime, duration: Duration) DateTime {
|
|
|
|
return DateTime{
|
|
|
|
.seconds_since_epoch = self.seconds_since_epoch + duration.seconds,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn sub(self: DateTime, duration: Duration) DateTime {
|
|
|
|
return DateTime{
|
|
|
|
.seconds_since_epoch = self.seconds_since_epoch - duration.seconds,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-09-15 01:12:07 +00:00
|
|
|
// TODO: Validate non-numeric aspects of datetime
|
|
|
|
// TODO: Don't panic on bad string
|
|
|
|
// TODO: Make seconds optional (see ActivityStreams 2.0 spec §2.3)
|
|
|
|
// TODO: Handle times before 1970
|
|
|
|
pub fn parseRfc3339(str: []const u8) !DateTime {
|
|
|
|
const year_num = try std.fmt.parseInt(u16, str[0..4], 10);
|
|
|
|
const month_num = try std.fmt.parseInt(std.meta.Tag(epoch.Month), str[5..7], 10);
|
|
|
|
const day_num = @as(i64, try std.fmt.parseInt(u9, str[8..10], 10));
|
|
|
|
const hour_num = @as(i64, try std.fmt.parseInt(u5, str[11..13], 10));
|
2022-10-04 02:41:59 +00:00
|
|
|
const minute_num = @as(i64, try std.fmt.parseInt(u6, str[14..16], 10));
|
|
|
|
const second_num = @as(i64, try std.fmt.parseInt(u6, str[17..19], 10));
|
2022-09-15 01:12:07 +00:00
|
|
|
|
|
|
|
const is_leap_year = epoch.isLeapYear(year_num);
|
|
|
|
const leap_days_preceding_epoch = comptime epoch.epoch_year / 4 - epoch.epoch_year / 100 + epoch.epoch_year / 400;
|
|
|
|
const leap_days_preceding_year = year_num / 4 - year_num / 100 + year_num / 400 - leap_days_preceding_epoch - if (is_leap_year) @as(i64, 1) else 0;
|
|
|
|
|
|
|
|
const epoch_day = (year_num - epoch.epoch_year) * 365 + leap_days_preceding_year + year_day: {
|
|
|
|
var days_preceding_month: i64 = 0;
|
|
|
|
var month_i: i64 = 1;
|
|
|
|
while (month_i < month_num) : (month_i += 1) {
|
|
|
|
days_preceding_month += epoch.getDaysInMonth(if (is_leap_year) .leap else .not_leap, @intToEnum(epoch.Month, month_i));
|
|
|
|
}
|
|
|
|
break :year_day days_preceding_month + day_num;
|
|
|
|
};
|
|
|
|
|
|
|
|
const day_second = (hour_num * 60 + minute_num) * 60 + second_num;
|
|
|
|
|
|
|
|
return DateTime{
|
|
|
|
.seconds_since_epoch = epoch_day * epoch.secs_per_day + day_second,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-07-18 07:03:29 +00:00
|
|
|
pub fn now() DateTime {
|
|
|
|
return .{ .seconds_since_epoch = std.time.timestamp() };
|
|
|
|
}
|
|
|
|
|
2022-09-08 06:56:29 +00:00
|
|
|
pub fn isAfter(lhs: DateTime, rhs: DateTime) bool {
|
|
|
|
return lhs.seconds_since_epoch > rhs.seconds_since_epoch;
|
|
|
|
}
|
|
|
|
|
2022-07-18 07:03:29 +00:00
|
|
|
pub fn epochSeconds(value: DateTime) std.time.epoch.EpochSeconds {
|
2022-10-11 05:19:58 +00:00
|
|
|
return .{ .secs = std.math.cast(u64, value.seconds_since_epoch) orelse 0 };
|
2022-07-18 07:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn year(value: DateTime) std.time.epoch.Year {
|
|
|
|
return value.epochSeconds().getEpochDay().calculateYearDay().year;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn month(value: DateTime) std.time.epoch.Month {
|
|
|
|
return value.epochSeconds().getEpochDay().calculateYearDay().calculateMonthDay().month;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn day(value: DateTime) u9 {
|
2022-07-18 07:37:10 +00:00
|
|
|
return value.epochSeconds().getEpochDay().calculateYearDay().calculateMonthDay().day_index;
|
2022-07-18 07:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn hour(value: DateTime) u5 {
|
|
|
|
return value.epochSeconds().getDaySeconds().getHoursIntoDay();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn minute(value: DateTime) u6 {
|
|
|
|
return value.epochSeconds().getDaySeconds().getMinutesIntoHour();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn second(value: DateTime) u6 {
|
|
|
|
return value.epochSeconds().getDaySeconds().getSecondsIntoMinute();
|
|
|
|
}
|
|
|
|
|
2022-09-15 01:12:07 +00:00
|
|
|
const array_len = 20;
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
pub fn toCharArray(value: DateTime) [array_len]u8 {
|
2022-09-15 01:12:07 +00:00
|
|
|
var buf: [array_len]u8 = undefined;
|
2022-10-12 02:19:34 +00:00
|
|
|
_ = std.fmt.bufPrint(&buf, "{}", .{value}) catch unreachable;
|
2022-09-15 01:12:07 +00:00
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn toCharArrayZ(value: DateTime) [array_len + 1:0]u8 {
|
|
|
|
var buf: [array_len + 1:0]u8 = undefined;
|
2022-10-12 02:19:34 +00:00
|
|
|
_ = std.fmt.bufPrintZ(&buf, "{}", .{value}) catch unreachable;
|
2022-09-15 01:12:07 +00:00
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2022-12-12 10:42:24 +00:00
|
|
|
pub fn format(value: DateTime, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
|
|
if (comptime std.ascii.eqlIgnoreCase(fmt, "rfc3339") or fmt.len == 0) {
|
|
|
|
return std.fmt.format(
|
|
|
|
writer,
|
|
|
|
"{:0>4}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}Z",
|
|
|
|
.{ value.year(), value.month().numeric(), value.day(), value.hour(), value.minute(), value.second() },
|
|
|
|
);
|
|
|
|
} else @compileError("Unknown DateTime format " ++ fmt);
|
2022-07-18 07:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn jsonStringify(value: DateTime, _: std.json.StringifyOptions, writer: anytype) !void {
|
2022-12-12 10:42:24 +00:00
|
|
|
try std.fmt.format(writer, "\"{rfc3339}\"", .{value});
|
2022-07-18 07:03:29 +00:00
|
|
|
}
|