handle times before 1970

This commit is contained in:
Emil Lerch 2022-02-15 17:28:27 -08:00
parent 49f3f48aa8
commit fc8a73a9c4
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -83,31 +83,75 @@ pub fn parseIso8601Timestamp(data: []const u8) !i64 {
} }
fn dateTimeToTimestamp(datetime: DateTime) !i64 { fn dateTimeToTimestamp(datetime: DateTime) !i64 {
if (datetime.month > 12 or const epoch = DateTime{
datetime.day > 31 or .year = 1970,
datetime.hour >= 24 or .month = 1,
datetime.minute >= 60 or .day = 1,
datetime.second >= 60) return error.DateTimeOutOfRange; .hour = 0,
const epoch_year = 1970; .minute = 0,
if (datetime.year < epoch_year) return error.DatesBeforeEpochNotImplemented; .second = 0,
const leap_years_between = leapYearsBetween(epoch_year, datetime.year); };
return secondsBetween(epoch, datetime);
}
const DateTimeToTimestampError = error{
DateTimeOutOfRange,
};
fn secondsBetween(start: DateTime, end: DateTime) DateTimeToTimestampError!i64 {
try validateDatetime(start);
try validateDatetime(end);
if (end.year < start.year) return -1 * try secondsBetween(end, start);
if (start.month != 1 or
start.day != 1 or
start.hour != 0 or
start.minute != 0 or
start.second != 0)
{
const seconds_into_start_year = secondsFromBeginningOfYear(
start.year,
start.month,
start.day,
start.hour,
start.minute,
start.second,
);
const new_start = DateTime{
.year = start.year,
.month = 1,
.day = 1,
.hour = 0,
.minute = 0,
.second = 0,
};
return (try secondsBetween(new_start, end)) - seconds_into_start_year;
}
const leap_years_between = leapYearsBetween(start.year, end.year);
var add_days: u1 = 0; var add_days: u1 = 0;
const years_diff = std.math.absCast(@as(i17, datetime.year) - @as(i17, epoch_year)); const years_diff = end.year - start.year;
std.log.debug("Years from epoch: {d}, Leap years: {d}", .{ years_diff, leap_years_between }); std.log.debug("Years from epoch: {d}, Leap years: {d}", .{ years_diff, leap_years_between });
var days_diff: i32 = (years_diff * DAYS_PER_YEAR) + leap_years_between + add_days; var days_diff: i32 = (years_diff * DAYS_PER_YEAR) + leap_years_between + add_days;
std.log.debug("Days with leap year, without month: {d}", .{days_diff}); std.log.debug("Days with leap year, without month: {d}", .{days_diff});
const seconds_into_year = secondsFromBeginningOfYear( const seconds_into_year = secondsFromBeginningOfYear(
datetime.year, end.year,
datetime.month, end.month,
datetime.day, end.day,
datetime.hour, end.hour,
datetime.minute, end.minute,
datetime.second, end.second,
); );
return (days_diff * SECONDS_PER_DAY) + @as(i64, seconds_into_year); return (days_diff * SECONDS_PER_DAY) + @as(i64, seconds_into_year);
} }
fn validateDatetime(dt: DateTime) !void {
if (dt.month > 12 or
dt.day > 31 or
dt.hour >= 24 or
dt.minute >= 60 or
dt.second >= 60) return error.DateTimeOutOfRange;
}
fn secondsFromBeginningOfYear(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) u32 { fn secondsFromBeginningOfYear(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) u32 {
const current_year_is_leap_year = isLeapYear(year); const current_year_is_leap_year = isLeapYear(year);
const leap_year_days_per_month: [12]u5 = .{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; const leap_year_days_per_month: [12]u5 = .{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
@ -201,8 +245,6 @@ test "Convert timestamp to datetime" {
} }
test "Convert datetime to timestamp" { test "Convert datetime to timestamp" {
std.testing.log_level = .debug;
std.log.debug("\n", .{});
try std.testing.expectEqual(@as(i64, 1598607147), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 8, .day = 28, .hour = 9, .minute = 32, .second = 27 })); try std.testing.expectEqual(@as(i64, 1598607147), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 8, .day = 28, .hour = 9, .minute = 32, .second = 27 }));
try std.testing.expectEqual(@as(i64, 1604207167), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 11, .day = 1, .hour = 5, .minute = 6, .second = 7 })); try std.testing.expectEqual(@as(i64, 1604207167), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 11, .day = 1, .hour = 5, .minute = 6, .second = 7 }));
try std.testing.expectEqual(@as(i64, 1440938160), try dateTimeToTimestamp(DateTime{ .year = 2015, .month = 08, .day = 30, .hour = 12, .minute = 36, .second = 00 })); try std.testing.expectEqual(@as(i64, 1440938160), try dateTimeToTimestamp(DateTime{ .year = 2015, .month = 08, .day = 30, .hour = 12, .minute = 36, .second = 00 }));
@ -213,17 +255,6 @@ test "Convert ISO8601 string to timestamp" {
try std.testing.expectEqual(@as(i64, 1604207167), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 11, .day = 1, .hour = 5, .minute = 6, .second = 7 })); try std.testing.expectEqual(@as(i64, 1604207167), try dateTimeToTimestamp(DateTime{ .year = 2020, .month = 11, .day = 1, .hour = 5, .minute = 6, .second = 7 }));
try std.testing.expectEqual(@as(i64, 1440938160), try dateTimeToTimestamp(DateTime{ .year = 2015, .month = 08, .day = 30, .hour = 12, .minute = 36, .second = 00 })); try std.testing.expectEqual(@as(i64, 1440938160), try dateTimeToTimestamp(DateTime{ .year = 2015, .month = 08, .day = 30, .hour = 12, .minute = 36, .second = 00 }));
} }
// TODO: I think before epoch, the best approach is to flip the epoch and the test "Convert datetime to timestamp before 1970" {
// input date, calculate the answer, then flip signs. However, this requires try std.testing.expectEqual(@as(i64, -449392815), try dateTimeToTimestamp(DateTime{ .year = 1955, .month = 10, .day = 05, .hour = 16, .minute = 39, .second = 45 }));
// re-designing the algorithm to start from something other than midnight }
// January 1st. This will require an overhaul, so for now, we'll leave
// this unimplemented.
//
// test "Convert datetime to timestamp before 1970" {
// std.testing.log_level = .debug;
// std.log.debug("\n", .{});
// try std.testing.expectEqual(@as(i64, -449392815000), try dateTimeToTimestamp(DateTime{ .year = 1955, .month = 10, .day = 05, .hour = 16, .minute = 39, .second = 45 }));
//
//
// 1955.1 - .1 + x = 1970
// }