Skip to content

[libc] create TimeReader to look at a struct tm #126138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libc/include/llvm-libc-types/struct_tm.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct tm {
int tm_wday; // days since Sunday
int tm_yday; // days since January
int tm_isdst; // Daylight Saving Time flag
// TODO: add tm_gmtoff and tm_zone? (posix extensions)
};

#endif // LLVM_LIBC_TYPES_STRUCT_TM_H
2 changes: 2 additions & 0 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_object_library(
DEPENDS
libc.include.time
libc.src.__support.CPP.limits
libc.src.__support.CPP.string_view
libc.src.__support.CPP.optional
libc.src.errno.errno
.time_constants
libc.hdr.types.time_t
Expand Down
94 changes: 1 addition & 93 deletions libc/src/time/mktime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,100 +14,8 @@

namespace LIBC_NAMESPACE_DECL {

// Returns number of years from (1, year).
static constexpr int64_t get_num_of_leap_years_before(int64_t year) {
return (year / 4) - (year / 100) + (year / 400);
}

// Returns True if year is a leap year.
static constexpr bool is_leap_year(const int64_t year) {
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
}

LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds.
int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;

// 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
if (sizeof(time_t) == 4 &&
tm_year_from_base >= time_constants::END_OF32_BIT_EPOCH_YEAR) {
if (tm_year_from_base > time_constants::END_OF32_BIT_EPOCH_YEAR)
return time_utils::out_of_range();
if (tm_out->tm_mon > 0)
return time_utils::out_of_range();
if (tm_out->tm_mday > 19)
return time_utils::out_of_range();
else if (tm_out->tm_mday == 19) {
if (tm_out->tm_hour > 3)
return time_utils::out_of_range();
else if (tm_out->tm_hour == 3) {
if (tm_out->tm_min > 14)
return time_utils::out_of_range();
else if (tm_out->tm_min == 14) {
if (tm_out->tm_sec > 7)
return time_utils::out_of_range();
}
}
}
}

// Years are ints. A 32-bit year will fit into a 64-bit time_t.
// A 64-bit year will not.
static_assert(
sizeof(int) == 4,
"ILP64 is unimplemented. This implementation requires 32-bit integers.");

// Calculate number of months and years from tm_mon.
int64_t month = tm_out->tm_mon;
if (month < 0 || month >= time_constants::MONTHS_PER_YEAR - 1) {
int64_t years = month / 12;
month %= 12;
if (month < 0) {
years--;
month += 12;
}
tm_year_from_base += years;
}
bool tm_year_is_leap = is_leap_year(tm_year_from_base);

// Calculate total number of days based on the month and the day (tm_mday).
int64_t total_days = tm_out->tm_mday - 1;
for (int64_t i = 0; i < month; ++i)
total_days += time_constants::NON_LEAP_YEAR_DAYS_IN_MONTH[i];
// Add one day if it is a leap year and the month is after February.
if (tm_year_is_leap && month > 1)
total_days++;

// Calculate total numbers of days based on the year.
total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
time_constants::DAYS_PER_NON_LEAP_YEAR;
if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
total_days += get_num_of_leap_years_before(tm_year_from_base - 1) -
get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
} else if (tm_year_from_base >= 1) {
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
get_num_of_leap_years_before(tm_year_from_base - 1);
} else {
// Calculate number of leap years until 0th year.
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
get_num_of_leap_years_before(0);
if (tm_year_from_base <= 0) {
total_days -= 1; // Subtract 1 for 0th year.
// Calculate number of leap years until -1 year
if (tm_year_from_base < 0) {
total_days -= get_num_of_leap_years_before(-tm_year_from_base) -
get_num_of_leap_years_before(1);
}
}
}

// TODO: https://github.com/llvm/llvm-project/issues/121962
// Need to handle timezone and update of tm_isdst.
int64_t seconds = tm_out->tm_sec +
tm_out->tm_min * time_constants::SECONDS_PER_MIN +
tm_out->tm_hour * time_constants::SECONDS_PER_HOUR +
total_days * time_constants::SECONDS_PER_DAY;
int64_t seconds = time_utils::mktime_internal(tm_out);

// Update the tm structure's year, month, day, etc. from seconds.
if (time_utils::update_from_seconds(seconds, tm_out) < 0)
Expand Down
18 changes: 17 additions & 1 deletion libc/src/time/time_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace LIBC_NAMESPACE_DECL {
namespace time_constants {

enum Month : int {
JANUARY,
JANUARY = 0,
FEBRUARY,
MARCH,
APRIL,
Expand All @@ -32,14 +32,28 @@ enum Month : int {
DECEMBER
};

enum WeekDay : int {
SUNDAY = 0,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};

constexpr int SECONDS_PER_MIN = 60;
constexpr int MINUTES_PER_HOUR = 60;
constexpr int HOURS_PER_DAY = 24;
constexpr int DAYS_PER_WEEK = 7;
constexpr int WEEKS_PER_YEAR = 52;
constexpr int MONTHS_PER_YEAR = 12;
constexpr int DAYS_PER_NON_LEAP_YEAR = 365;
constexpr int DAYS_PER_LEAP_YEAR = 366;

constexpr int LAST_DAY_OF_NON_LEAP_YEAR = DAYS_PER_NON_LEAP_YEAR - 1;
constexpr int LAST_DAY_OF_LEAP_YEAR = DAYS_PER_LEAP_YEAR - 1;

constexpr int SECONDS_PER_HOUR = SECONDS_PER_MIN * MINUTES_PER_HOUR;
constexpr int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
constexpr int NUMBER_OF_SECONDS_IN_LEAP_YEAR =
Expand All @@ -49,6 +63,8 @@ constexpr int TIME_YEAR_BASE = 1900;
constexpr int EPOCH_YEAR = 1970;
constexpr int EPOCH_WEEK_DAY = 4;

constexpr int ISO_FIRST_DAY_OF_YEAR = 3; // the 4th day of the year, 0-indexed.

// For asctime the behavior is undefined if struct tm's tm_wday or tm_mon are
// not within the normal ranges as defined in <time.h>, or if struct tm's
// tm_year exceeds {INT_MAX}-1990, or if the below asctime_internal algorithm
Expand Down
96 changes: 95 additions & 1 deletion libc/src/time/time_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,103 @@
#include "src/__support/macros/config.h"
#include "src/time/time_constants.h"

#include <stdint.h>

namespace LIBC_NAMESPACE_DECL {
namespace time_utils {

// TODO: clean this up in a followup patch
int64_t mktime_internal(const tm *tm_out) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds.
int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;

// 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
if (sizeof(time_t) == 4 &&
tm_year_from_base >= time_constants::END_OF32_BIT_EPOCH_YEAR) {
if (tm_year_from_base > time_constants::END_OF32_BIT_EPOCH_YEAR)
return time_utils::out_of_range();
if (tm_out->tm_mon > 0)
return time_utils::out_of_range();
if (tm_out->tm_mday > 19)
return time_utils::out_of_range();
else if (tm_out->tm_mday == 19) {
if (tm_out->tm_hour > 3)
return time_utils::out_of_range();
else if (tm_out->tm_hour == 3) {
if (tm_out->tm_min > 14)
return time_utils::out_of_range();
else if (tm_out->tm_min == 14) {
if (tm_out->tm_sec > 7)
return time_utils::out_of_range();
}
}
}
}

// Years are ints. A 32-bit year will fit into a 64-bit time_t.
// A 64-bit year will not.
static_assert(
sizeof(int) == 4,
"ILP64 is unimplemented. This implementation requires 32-bit integers.");

// Calculate number of months and years from tm_mon.
int64_t month = tm_out->tm_mon;
if (month < 0 || month >= time_constants::MONTHS_PER_YEAR - 1) {
int64_t years = month / 12;
month %= 12;
if (month < 0) {
years--;
month += 12;
}
tm_year_from_base += years;
}
bool tm_year_is_leap = time_utils::is_leap_year(tm_year_from_base);

// Calculate total number of days based on the month and the day (tm_mday).
int64_t total_days = tm_out->tm_mday - 1;
for (int64_t i = 0; i < month; ++i)
total_days += time_constants::NON_LEAP_YEAR_DAYS_IN_MONTH[i];
// Add one day if it is a leap year and the month is after February.
if (tm_year_is_leap && month > 1)
total_days++;

// Calculate total numbers of days based on the year.
total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
time_constants::DAYS_PER_NON_LEAP_YEAR;
if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
total_days +=
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1) -
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
} else if (tm_year_from_base >= 1) {
total_days -=
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1);
} else {
// Calculate number of leap years until 0th year.
total_days -=
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
time_utils::get_num_of_leap_years_before(0);
if (tm_year_from_base <= 0) {
total_days -= 1; // Subtract 1 for 0th year.
// Calculate number of leap years until -1 year
if (tm_year_from_base < 0) {
total_days -=
time_utils::get_num_of_leap_years_before(-tm_year_from_base) -
time_utils::get_num_of_leap_years_before(1);
}
}
}

// TODO: https://github.com/llvm/llvm-project/issues/121962
// Need to handle timezone and update of tm_isdst.
int64_t seconds = tm_out->tm_sec +
tm_out->tm_min * time_constants::SECONDS_PER_MIN +
tm_out->tm_hour * time_constants::SECONDS_PER_HOUR +
total_days * time_constants::SECONDS_PER_DAY;
return seconds;
}

static int64_t computeRemainingYears(int64_t daysPerYears,
int64_t quotientYears,
int64_t *remainingDays) {
Expand Down Expand Up @@ -42,7 +136,7 @@ static int64_t computeRemainingYears(int64_t daysPerYears,
//
// Compute the number of months from the remaining days. Finally, adjust years
// to be 1900 and months to be from January.
int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
int64_t update_from_seconds(int64_t total_seconds, tm *tm) {
// Days in month starting from March in the year 2000.
static const char daysInMonth[] = {31 /* Mar */, 30, 31, 30, 31, 31,
30, 31, 30, 31, 31, 29};
Expand Down
Loading