AK/DOSPackedTime+Tests: Add helpers for parsing Unix timestamps

This commit is contained in:
implicitfield
2025-04-26 01:10:19 +03:00
committed by Sönke Holz
parent 32edf98bc4
commit 97e18c96e5
4 changed files with 113 additions and 0 deletions

View File

@@ -5,9 +5,12 @@
*/
#include <AK/DOSPackedTime.h>
#include <AK/Error.h>
namespace AK {
constexpr auto seconds_per_day = 86'400;
UnixDateTime time_from_packed_dos(DOSPackedDate date, DOSPackedTime time)
{
if (date.value == 0)
@@ -36,4 +39,52 @@ DOSPackedTime to_packed_dos_time(unsigned hour, unsigned minute, unsigned second
return time;
}
// FIXME: Improve these naive algorithms.
ErrorOr<DOSPackedDate> to_packed_dos_date(UnixDateTime const& unix_date_time)
{
auto truncated_seconds_since_epoch = unix_date_time.truncated_seconds_since_epoch();
if (truncated_seconds_since_epoch < first_dos_representable_unix_timestamp || truncated_seconds_since_epoch > static_cast<i64>(last_dos_representable_unix_timestamp))
return EINVAL;
auto years_since_epoch = 0;
auto leftover_seconds = truncated_seconds_since_epoch;
while (leftover_seconds >= days_in_year(years_since_epoch + 1970) * seconds_per_day) {
leftover_seconds -= days_in_year(years_since_epoch + 1970) * seconds_per_day;
++years_since_epoch;
}
auto month_of_year = 1;
for (; month_of_year <= 12; ++month_of_year) {
auto seconds_in_current_month = days_in_month(years_since_epoch + 1970, month_of_year) * seconds_per_day;
if (leftover_seconds < seconds_in_current_month)
break;
leftover_seconds -= seconds_in_current_month;
}
VERIFY(month_of_year <= 12);
auto day = leftover_seconds / seconds_per_day + 1;
return to_packed_dos_date(years_since_epoch + 1970, month_of_year, day);
}
ErrorOr<DOSPackedTime> to_packed_dos_time(UnixDateTime const& unix_date_time)
{
constexpr auto seconds_per_hour = 3'600;
constexpr auto seconds_per_minute = 60;
auto date = TRY(to_packed_dos_date(unix_date_time));
auto truncated_seconds_since_epoch = unix_date_time.truncated_seconds_since_epoch();
auto leftover_seconds = truncated_seconds_since_epoch - days_since_epoch(date.year + first_dos_year, date.month, date.day) * seconds_per_day;
auto hours = leftover_seconds / seconds_per_hour;
leftover_seconds -= hours * seconds_per_hour;
auto minutes = leftover_seconds / seconds_per_minute;
leftover_seconds -= minutes * seconds_per_minute;
return to_packed_dos_time(hours, minutes, leftover_seconds);
}
}

View File

@@ -32,10 +32,14 @@ union DOSPackedDate {
static_assert(sizeof(DOSPackedDate) == 2);
inline constexpr u16 first_dos_year = 1980;
inline constexpr u32 first_dos_representable_unix_timestamp = UnixDateTime::from_unix_time_parts(1980, 1, 1, 0, 0, 0, 0).seconds_since_epoch();
inline constexpr u64 last_dos_representable_unix_timestamp = UnixDateTime::from_unix_time_parts(2107, 12, 31, 23, 59, 59, 0).seconds_since_epoch();
UnixDateTime time_from_packed_dos(DOSPackedDate, DOSPackedTime);
DOSPackedDate to_packed_dos_date(unsigned year, unsigned month, unsigned day);
DOSPackedTime to_packed_dos_time(unsigned hour, unsigned minute, unsigned second);
ErrorOr<DOSPackedDate> to_packed_dos_date(UnixDateTime const&);
ErrorOr<DOSPackedTime> to_packed_dos_time(UnixDateTime const&);
}

View File

@@ -23,6 +23,7 @@ set(AK_TEST_SOURCES
TestComplex.cpp
TestConstrainedStream.cpp
TestCoroutine.cpp
TestDOSPackedTime.cpp
TestDisjointChunks.cpp
TestDistinctNumeric.cpp
TestDoublyLinkedList.cpp

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2025, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <AK/DOSPackedTime.h>
#include <AK/Time.h>
TEST_CASE(test_date_serialization)
{
auto matches = [](i32 year, u8 month, u8 day) -> bool {
DOSPackedDate date = {};
date.year = year - 1980;
date.month = month;
date.day = day;
return MUST(to_packed_dos_date(UnixDateTime::from_unix_time_parts(year, month, day, 0, 0, 0, 0))).value == date.value;
};
EXPECT(matches(1980, 1, 1));
EXPECT(matches(2000, 1, 1));
EXPECT(matches(2016, 2, 29));
EXPECT(matches(2016, 3, 1));
EXPECT(matches(2017, 2, 28));
EXPECT(matches(2017, 3, 1));
EXPECT(matches(2018, 10, 10));
EXPECT(matches(2025, 4, 26));
}
TEST_CASE(test_time_serialization)
{
auto matches = [](u8 hour, u8 minute, u8 second) -> bool {
DOSPackedTime time = {};
time.hour = hour;
time.minute = minute;
// This can only handle 2-second intervals, since it's stored in only 5 bits.
time.second = second / 2;
return MUST(to_packed_dos_time(UnixDateTime::from_unix_time_parts(2025, 1, 1, hour, minute, second, 0))).value == time.value;
};
auto test_hour = [&matches](u8 hour) -> bool {
for (size_t minute = 0; minute < 60; ++minute) {
for (size_t second = 0; second < 60; ++second) {
if (!matches(hour, minute, second))
return false;
}
}
return true;
};
EXPECT(test_hour(0));
EXPECT(test_hour(23));
}