fediglam/src/util/DateTime.zig

107 lines
3.7 KiB
Zig

const DateTime = @This();
const std = @import("std");
const epoch = std.time.epoch;
seconds_since_epoch: i64,
pub fn parse(str: []const u8) !DateTime {
// TODO: Try other formats
return try parseRfc3339(str);
}
// 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));
const minute_num = @as(i64, try std.fmt.parseInt(u6, str[14..15], 10));
const second_num = @as(i64, try std.fmt.parseInt(u6, str[16..17], 10));
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,
};
}
pub fn now() DateTime {
return .{ .seconds_since_epoch = std.time.timestamp() };
}
pub fn isAfter(lhs: DateTime, rhs: DateTime) bool {
return lhs.seconds_since_epoch > rhs.seconds_since_epoch;
}
pub fn epochSeconds(value: DateTime) std.time.epoch.EpochSeconds {
return .{ .secs = @intCast(u64, value.seconds_since_epoch) };
}
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 {
return value.epochSeconds().getEpochDay().calculateYearDay().calculateMonthDay().day_index;
}
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();
}
const array_len = 20;
pub fn toCharArray(value: DateTime) [array_len + 1]u8 {
var buf: [array_len]u8 = undefined;
_ = std.fmt.bufPrintZ(&buf, "{}", value) catch unreachable;
return buf;
}
pub fn toCharArrayZ(value: DateTime) [array_len + 1:0]u8 {
var buf: [array_len + 1:0]u8 = undefined;
_ = std.fmt.bufPrintZ(&buf, "{}", value) catch unreachable;
return buf;
}
pub fn format(value: DateTime, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
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() },
);
}
pub fn jsonStringify(value: DateTime, _: std.json.StringifyOptions, writer: anytype) !void {
try std.fmt.format(writer, "\"{}\"", .{value});
}