Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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