diff options
| author | Michael Jones <michaelrj@google.com> | 2025-02-11 14:37:15 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-11 14:37:15 -0800 |
| commit | a760e7faacb79e7ff0ae23d3ae370d1aa6e52666 (patch) | |
| tree | 25976a4b133411df490acfb1c39abb335ae3b087 /libc/src/time/time_utils.cpp | |
| parent | ad6cd7e8b2590dcbb8aed92ac416ca889986471d (diff) | |
[libc] create TimeReader to look at a struct tm (#126138)
In the process of adding strftime (#122556) I wrote this utility class
to simplify reading from a struct tm. It provides helper functions that
return basically everything needed by strftime. It's not tested
directly, but it is thoroughly exercised by the strftime tests.
Diffstat (limited to 'libc/src/time/time_utils.cpp')
| -rw-r--r-- | libc/src/time/time_utils.cpp | 96 |
1 files changed, 95 insertions, 1 deletions
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp index abc93b8cb961..3ccb2dd93496 100644 --- a/libc/src/time/time_utils.cpp +++ b/libc/src/time/time_utils.cpp @@ -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) { @@ -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}; |
