diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -4,6 +4,7 @@ mktime.cpp HDRS mktime.h + time_utils.h DEPENDS libc.include.errno libc.include.time diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp --- a/libc/src/time/mktime.cpp +++ b/libc/src/time/mktime.cpp @@ -6,77 +6,174 @@ // //===----------------------------------------------------------------------===// -#include "include/errno.h" - -#include "src/__support/common.h" -#include "src/errno/llvmlibc_errno.h" #include "src/time/mktime.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +#include namespace __llvm_libc { -constexpr int SecondsPerMin = 60; -constexpr int MinutesPerHour = 60; -constexpr int HoursPerDay = 24; -constexpr int DaysPerWeek = 7; -constexpr int MonthsPerYear = 12; -constexpr int DaysPerNonLeapYear = 365; -constexpr int TimeYearBase = 1900; -constexpr int EpochYear = 1970; -constexpr int EpochWeekDay = 4; -// The latest time that can be represented in this form is 03:14:07 UTC on -// Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the -// start of the epoch). This means that systems using a 32-bit time_t type are -// susceptible to the Year 2038 problem. -constexpr int EndOf32BitEpochYear = 2038; - -constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31}; - -constexpr bool isLeapYear(const time_t year) { +using __llvm_libc::time_utils::TimeConstants; + +// Returns number of years from (1, year). +static constexpr int64_t getNumOfLeapYearsBefore(int64_t year) { + return (year / 4) - (year / 100) + (year / 400); +} + +// Returns True if year is a leap year. +static constexpr bool isLeapYear(const int64_t year) { return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); } -// POSIX.1-2017 requires this. -static inline time_t outOfRange() { - llvmlibc_errno = EOVERFLOW; - return static_cast(-1); +static int64_t computeRemainingYears(int64_t daysPerYears, + int64_t quotientYears, + int64_t *remainingDays) { + int64_t years = *remainingDays / daysPerYears; + if (years == quotientYears) + years--; + *remainingDays -= years * daysPerYears; + return years; +} + +// Update the "tm" structure's year, month, etc. members from seconds. +// "total_seconds" is the number of seconds since January 1st, 1970. +// +// First, divide "total_seconds" by the number of seconds in a day to get the +// number of days since Jan 1 1970. The remainder will be used to calculate the +// number of Hours, Minutes and Seconds. +// +// Then, adjust that number of days by a constant to be the number of days +// since Mar 1 2000. Year 2000 is a multiple of 400, the leap year cycle. This +// makes it easier to count how many leap years have passed using division. +// +// While calculating numbers of years in the days, the following algorithm +// subdivides the days into the number of 400 years, the number of 100 years and +// the number of 4 years. These numbers of cycle years are used in calculating +// leap day. This is similar to the algorithm used in getNumOfLeapYearsBefore() +// and isLeapYear(). Then compute the total number of years in days from these +// subdivided units. +// +// Compute the number of months from the remaining days. Finally, adjust years +// to be 1900 and months to be from January. +static int64_t updateFromSeconds(int64_t total_seconds, struct 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}; + + if (sizeof(time_t) == 4) { + if (total_seconds < 0x80000000) + return time_utils::OutOfRange(); + if (total_seconds > 0x7FFFFFFF) + return time_utils::OutOfRange(); + } else { + if (total_seconds < + INT_MIN * static_cast( + TimeConstants::NumberOfSecondsInLeapYear) || + total_seconds > INT_MAX * static_cast( + TimeConstants::NumberOfSecondsInLeapYear)) + return time_utils::OutOfRange(); + } + + int64_t seconds = total_seconds - TimeConstants::SecondsUntil2000MarchFirst; + int64_t days = seconds / TimeConstants::SecondsPerDay; + int64_t remainingSeconds = seconds % TimeConstants::SecondsPerDay; + if (remainingSeconds < 0) { + remainingSeconds += TimeConstants::SecondsPerDay; + days--; + } + + int64_t wday = (TimeConstants::WeekDayOf2000MarchFirst + days) % + TimeConstants::DaysPerWeek; + if (wday < 0) + wday += TimeConstants::DaysPerWeek; + + // Compute the number of 400 year cycles. + int64_t numOfFourHundredYearCycles = days / TimeConstants::DaysPer400Years; + int64_t remainingDays = days % TimeConstants::DaysPer400Years; + if (remainingDays < 0) { + remainingDays += TimeConstants::DaysPer400Years; + numOfFourHundredYearCycles--; + } + + // The reminder number of years after computing number of + // "four hundred year cycles" will be 4 hundred year cycles or less in 400 + // years. + int64_t numOfHundredYearCycles = + computeRemainingYears(TimeConstants::DaysPer100Years, 4, &remainingDays); + + // The reminder number of years after computing number of + // "hundred year cycles" will be 25 four year cycles or less in 100 years. + int64_t numOfFourYearCycles = + computeRemainingYears(TimeConstants::DaysPer4Years, 25, &remainingDays); + + // The reminder number of years after computing number of "four year cycles" + // will be 4 one year cycles or less in 4 years. + int64_t remainingYears = computeRemainingYears( + TimeConstants::DaysPerNonLeapYear, 4, &remainingDays); + + // Calculate number of years from year 2000. + int64_t years = remainingYears + 4 * numOfFourYearCycles + + 100 * numOfHundredYearCycles + + 400LL * numOfFourHundredYearCycles; + + int leapDay = + !remainingYears && (numOfFourYearCycles || !numOfHundredYearCycles); + + int64_t yday = remainingDays + 31 + 28 + leapDay; + if (yday >= TimeConstants::DaysPerNonLeapYear + leapDay) + yday -= TimeConstants::DaysPerNonLeapYear + leapDay; + + int64_t months = 0; + while (daysInMonth[months] <= remainingDays) { + remainingDays -= daysInMonth[months]; + months++; + } + + if (months >= TimeConstants::MonthsPerYear - 2) { + months -= TimeConstants::MonthsPerYear; + years++; + } + + if (years > INT_MAX || years < INT_MIN) + return time_utils::OutOfRange(); + + // All the data (years, month and remaining days) was calculated from + // March, 2000. Thus adjust the data to be from January, 1900. + tm->tm_year = years + 2000 - TimeConstants::TimeYearBase; + tm->tm_mon = months + 2; + tm->tm_mday = remainingDays + 1; + tm->tm_wday = wday; + tm->tm_yday = yday; + + tm->tm_hour = remainingSeconds / TimeConstants::SecondsPerHour; + tm->tm_min = remainingSeconds / TimeConstants::SecondsPerMin % + TimeConstants::SecondsPerMin; + tm->tm_sec = remainingSeconds % TimeConstants::SecondsPerMin; + + return 0; } -LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * t1)) { +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. Handle out of range time and date - // values that don't overflow or underflow. - // TODO (rtenneti): Implement the following suggestion Siva: "As we start - // accumulating the seconds, we should be able to check if the next amount of - // seconds to be added can lead to an overflow. If it does, return the - // overflow value. If not keep accumulating. The benefit is that, we don't - // have to validate every input, and also do not need the special cases for - // sizeof(time_t) == 4". - if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1)) - return outOfRange(); - if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1)) - return outOfRange(); - if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1)) - return outOfRange(); - time_t tmYearFromBase = t1->tm_year + TimeYearBase; - - if (tmYearFromBase < EpochYear) - return outOfRange(); + // TODO(rtenneti); Handle leap seconds. + int64_t tmYearFromBase = tm_out->tm_year + TimeConstants::TimeYearBase; // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038. - if (sizeof(time_t) == 4 && tmYearFromBase >= EndOf32BitEpochYear) { - if (tmYearFromBase > EndOf32BitEpochYear) - return outOfRange(); - if (t1->tm_mon > 0) - return outOfRange(); - if (t1->tm_mday > 19) - return outOfRange(); - if (t1->tm_hour > 3) - return outOfRange(); - if (t1->tm_min > 14) - return outOfRange(); - if (t1->tm_sec > 7) - return outOfRange(); + if (sizeof(time_t) == 4 && + tmYearFromBase >= TimeConstants::EndOf32BitEpochYear) { + if (tmYearFromBase > TimeConstants::EndOf32BitEpochYear) + return time_utils::OutOfRange(); + if (tm_out->tm_mon > 0) + return time_utils::OutOfRange(); + if (tm_out->tm_mday > 19) + return time_utils::OutOfRange(); + if (tm_out->tm_hour > 3) + return time_utils::OutOfRange(); + if (tm_out->tm_min > 14) + return time_utils::OutOfRange(); + if (tm_out->tm_sec > 7) + return time_utils::OutOfRange(); } // Years are ints. A 32-bit year will fit into a 64-bit time_t. @@ -85,42 +182,61 @@ "ILP64 is unimplemented. This implementation requires " "32-bit integers."); - if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1)) - return outOfRange(); + // Calculate number of months and years from tm_mon. + int64_t month = tm_out->tm_mon; + if (month < 0 || month >= TimeConstants::MonthsPerYear - 1) { + int64_t years = month / 12; + month %= 12; + if (month < 0) { + years--; + month += 12; + } + tmYearFromBase += years; + } bool tmYearIsLeap = isLeapYear(tmYearFromBase); - time_t daysInMonth = NonLeapYearDaysInMonth[t1->tm_mon]; - // Add one day if it is a leap year and the month is February. - if (tmYearIsLeap && t1->tm_mon == 1) - ++daysInMonth; - if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth) - return outOfRange(); - - time_t totalDays = t1->tm_mday - 1; - for (int i = 0; i < t1->tm_mon; ++i) - totalDays += NonLeapYearDaysInMonth[i]; + + // Calculate total number of days based on the month and the day (tm_mday). + int64_t totalDays = tm_out->tm_mday - 1; + for (int64_t i = 0; i < month; ++i) + totalDays += TimeConstants::NonLeapYearDaysInMonth[i]; // Add one day if it is a leap year and the month is after February. - if (tmYearIsLeap && t1->tm_mon > 1) + if (tmYearIsLeap && month > 1) totalDays++; - t1->tm_yday = totalDays; - totalDays += (tmYearFromBase - EpochYear) * DaysPerNonLeapYear; - - // Add an extra day for each leap year, starting with 1972 - for (time_t year = EpochYear + 2; year < tmYearFromBase;) { - if (isLeapYear(year)) { - totalDays += 1; - year += 4; - } else { - year++; + + // Calculate total numbers of days based on the year. + totalDays += (tmYearFromBase - TimeConstants::EpochYear) * + TimeConstants::DaysPerNonLeapYear; + if (tmYearFromBase >= TimeConstants::EpochYear) { + totalDays += getNumOfLeapYearsBefore(tmYearFromBase - 1) - + getNumOfLeapYearsBefore(TimeConstants::EpochYear); + } else if (tmYearFromBase >= 1) { + totalDays -= getNumOfLeapYearsBefore(TimeConstants::EpochYear) - + getNumOfLeapYearsBefore(tmYearFromBase - 1); + } else { + // Calculate number of leap years until 0th year. + totalDays -= getNumOfLeapYearsBefore(TimeConstants::EpochYear) - + getNumOfLeapYearsBefore(0); + if (tmYearFromBase <= 0) { + totalDays -= 1; // Subtract 1 for 0th year. + // Calculate number of leap years until -1 year + if (tmYearFromBase < 0) { + totalDays -= getNumOfLeapYearsBefore(-tmYearFromBase) - + getNumOfLeapYearsBefore(1); + } } } - t1->tm_wday = (EpochWeekDay + totalDays) % DaysPerWeek; - if (t1->tm_wday < 0) - t1->tm_wday += DaysPerWeek; // TODO(rtenneti): Need to handle timezone and update of tm_isdst. - return t1->tm_sec + t1->tm_min * SecondsPerMin + - t1->tm_hour * MinutesPerHour * SecondsPerMin + - totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin; + int64_t seconds = tm_out->tm_sec + + tm_out->tm_min * TimeConstants::SecondsPerMin + + tm_out->tm_hour * TimeConstants::SecondsPerHour + + totalDays * TimeConstants::SecondsPerDay; + + // Update the tm structure's year, month, day, etc. from seconds. + if (updateFromSeconds(seconds, tm_out) < 0) + return time_utils::OutOfRange(); + + return static_cast(seconds); } } // namespace __llvm_libc diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h new file mode 100644 --- /dev/null +++ b/libc/src/time/time_utils.h @@ -0,0 +1,68 @@ +//===-- Collection of utils for mktime and friends --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_TIME_TIME_UTILS_H +#define LLVM_LIBC_SRC_TIME_TIME_UTILS_H + +#include "include/errno.h" + +#include "src/errno/llvmlibc_errno.h" +#include "src/time/mktime.h" + +#include + +namespace __llvm_libc { +namespace time_utils { + +struct TimeConstants { + static constexpr int SecondsPerMin = 60; + static constexpr int SecondsPerHour = 3600; + static constexpr int SecondsPerDay = 86400; + static constexpr int DaysPerWeek = 7; + static constexpr int MonthsPerYear = 12; + static constexpr int DaysPerNonLeapYear = 365; + static constexpr int DaysPerLeapYear = 366; + static constexpr int TimeYearBase = 1900; + static constexpr int EpochYear = 1970; + static constexpr int EpochWeekDay = 4; + static constexpr int NumberOfSecondsInLeapYear = + (DaysPerNonLeapYear + 1) * SecondsPerDay; + + /* 2000-03-01 (mod 400 year, immediately after feb29 */ + static constexpr int64_t SecondsUntil2000MarchFirst = + (946684800LL + SecondsPerDay * (31 + 29)); + static constexpr int WeekDayOf2000MarchFirst = 3; + + static constexpr int DaysPer400Years = + (DaysPerNonLeapYear * 400 + (400 / 4) - 3); + static constexpr int DaysPer100Years = + (DaysPerNonLeapYear * 100 + (100 / 4) - 1); + static constexpr int DaysPer4Years = (DaysPerNonLeapYear * 4 + 1); + + // The latest time that can be represented in this form is 03:14:07 UTC on + // Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the + // start of the epoch). This means that systems using a 32-bit time_t type are + // susceptible to the Year 2038 problem. + static constexpr int EndOf32BitEpochYear = 2038; + + static constexpr int NonLeapYearDaysInMonth[] = {31, 28, 31, 30, 31, 30, 30, + 31, 31, 30, 31, 30, 31}; + + static constexpr time_t OutOfRangeReturnValue = -1; +}; + +// POSIX.1-2017 requires this. +static inline time_t OutOfRange() { + llvmlibc_errno = EOVERFLOW; + return static_cast(-1); +} + +} // namespace time_utils +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_TIME_UTILS_H diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -6,6 +6,8 @@ libc_time_unittests SRCS mktime_test.cpp + HDRS + TmMatcher.h DEPENDS libc.src.time.mktime ) diff --git a/libc/test/src/time/TmMatcher.h b/libc/test/src/time/TmMatcher.h new file mode 100644 --- /dev/null +++ b/libc/test/src/time/TmMatcher.h @@ -0,0 +1,69 @@ +//===---- TmMatchers.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_TEST_SRC_TIME_TM_MATCHER_H +#define LLVM_LIBC_TEST_SRC_TIME_TM_MATCHER_H + +#include + +#include "utils/UnitTest/Test.h" + +namespace __llvm_libc { +namespace tmmatcher { +namespace testing { + +class StructTmMatcher : public __llvm_libc::testing::Matcher<::tm> { + ::tm expected; + ::tm actual; + +public: + StructTmMatcher(::tm expectedValue) : expected(expectedValue) {} + + bool match(::tm actualValue) { + actual = actualValue; + return (actual.tm_sec == expected.tm_sec || + actual.tm_min == expected.tm_min || + actual.tm_hour == expected.tm_hour || + actual.tm_mday == expected.tm_mday || + actual.tm_mon == expected.tm_mon || + actual.tm_year == expected.tm_year || + actual.tm_wday == expected.tm_wday || + actual.tm_yday == expected.tm_yday || + actual.tm_isdst == expected.tm_isdst); + } + + void describeValue(const char *label, ::tm value, + __llvm_libc::testutils::StreamWrapper &stream) { + stream << label; + stream << " sec: " << value.tm_sec; + stream << " min: " << value.tm_min; + stream << " hour: " << value.tm_hour; + stream << " mday: " << value.tm_mday; + stream << " mon: " << value.tm_mon; + stream << " year: " << value.tm_year; + stream << " wday: " << value.tm_wday; + stream << " yday: " << value.tm_yday; + stream << " isdst: " << value.tm_isdst; + stream << '\n'; + } + + void explainError(__llvm_libc::testutils::StreamWrapper &stream) override { + describeValue("Expected tm_struct value: ", expected, stream); + describeValue(" Actual tm_struct value: ", actual, stream); + } +}; + +} // namespace testing +} // namespace tmmatcher +} // namespace __llvm_libc + +#define EXPECT_TM_EQ(expected, actual) \ + EXPECT_THAT((actual), \ + __llvm_libc::tmmatcher::testing::StructTmMatcher((expected))) + +#endif // LLVM_LIBC_TEST_SRC_TIME_TM_MATCHER_H diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp --- a/libc/test/src/time/mktime_test.cpp +++ b/libc/test/src/time/mktime_test.cpp @@ -7,151 +7,439 @@ //===----------------------------------------------------------------------===// #include "src/time/mktime.h" +#include "src/time/time_utils.h" #include "test/ErrnoSetterMatcher.h" +#include "test/src/time/TmMatcher.h" #include "utils/UnitTest/Test.h" #include +#include #include using __llvm_libc::testing::ErrnoSetterMatcher::Fails; - -static constexpr time_t OutOfRangeReturnValue = -1; +using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; +using __llvm_libc::time_utils::TimeConstants; // A helper function to initialize tm data structure. static inline void initialize_tm_data(struct tm *tm_data, int year, int month, - int mday, int hour, int min, int sec) { + int mday, int hour, int min, int sec, + int wday, int yday) { struct tm temp = {.tm_sec = sec, .tm_min = min, .tm_hour = hour, .tm_mday = mday, - .tm_mon = month, - .tm_year = year - 1900}; + .tm_mon = month - 1, // tm_mon starts with 0 for Jan + // years since 1900 + .tm_year = year - TimeConstants::TimeYearBase, + .tm_wday = wday, + .tm_yday = yday}; *tm_data = temp; } static inline time_t call_mktime(struct tm *tm_data, int year, int month, - int mday, int hour, int min, int sec) { - initialize_tm_data(tm_data, year, month, mday, hour, min, sec); + int mday, int hour, int min, int sec, int wday, + int yday) { + initialize_tm_data(tm_data, year, month, mday, hour, min, sec, wday, yday); return __llvm_libc::mktime(tm_data); } TEST(LlvmLibcMkTime, FailureSetsErrno) { struct tm tm_data; - initialize_tm_data(&tm_data, 0, 0, 0, 0, 0, -1); + initialize_tm_data(&tm_data, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1, + 0, 0); EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW)); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidSeconds) { +TEST(LlvmLibcMkTime, MkTimesInvalidSeconds) { struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, -1), OutOfRangeReturnValue); - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, 60), OutOfRangeReturnValue); + // -1 second from 1970-01-01 00:00:00 returns 1969-12-31 23:59:59. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + -1, // sec + 0, // wday + 0), // yday + Succeeds(-1)); + EXPECT_TM_EQ((tm{59, // sec + 59, // min + 23, // hr + 31, // day + 12 - 1, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 3, // wday + 364, // yday + 0}), + tm_data); + // 60 seconds from 1970-01-01 00:00:00 returns 1970-01-01 00:01:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 60, // sec + 0, // wday + 0), // yday + Succeeds(60)); + EXPECT_TM_EQ((tm{0, // sec + 1, // min + 0, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 1970 - TimeConstants::TimeYearBase, // year + 4, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) { struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, -1, 0), OutOfRangeReturnValue); - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 1, 0, 60, 0), OutOfRangeReturnValue); + // -1 minute from 1970-01-01 00:00:00 returns 1969-12-31 23:59:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + -1, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(-TimeConstants::SecondsPerMin)); + EXPECT_TM_EQ((tm{0, // sec + 59, // min + 23, // hr + 31, // day + 11, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 3, // wday + 0, // yday + 0}), + tm_data); + // 60 minutes from 1970-01-01 00:00:00 returns 1970-01-01 01:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 60, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(60 * TimeConstants::SecondsPerMin)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 1, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 1970 - TimeConstants::TimeYearBase, // year + 4, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) { struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, -1, 0, 0), OutOfRangeReturnValue); - EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 24, 0, 0), OutOfRangeReturnValue); + // -1 hour from 1970-01-01 00:00:00 returns 1969-12-31 23:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + -1, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(-TimeConstants::SecondsPerHour)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 23, // hr + 31, // day + 11, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 3, // wday + 0, // yday + 0}), + tm_data); + // 24 hours from 1970-01-01 00:00:00 returns 1970-01-02 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 1, // day + 24, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(24 * TimeConstants::SecondsPerHour)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 2, // day + 0, // tm_mon starts with 0 for Jan + 1970 - TimeConstants::TimeYearBase, // year + 5, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) { struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 1969, 0, 0, 0, 0, 0), OutOfRangeReturnValue); + // -1 year from 1970-01-01 00:00:00 returns 1969-01-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1969, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(-TimeConstants::DaysPerNonLeapYear * + TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 3, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) { - if (sizeof(time_t) != 4) + if (sizeof(size_t) != 4) return; struct tm tm_data; // 2038-01-19 03:14:08 tests overflow of the second in 2038. - EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 8), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2038, 1, 19, 3, 14, 8, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); // 2038-01-19 03:15:07 tests overflow of the minute in 2038. - EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 15, 7), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2038, 1, 19, 3, 15, 7, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); // 2038-01-19 04:14:07 tests overflow of the hour in 2038. - EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 4, 14, 7), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2038, 1, 19, 4, 14, 7, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); // 2038-01-20 03:14:07 tests overflow of the day in 2038. - EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 20, 3, 14, 7), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2038, 1, 20, 3, 14, 7, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); // 2038-02-19 03:14:07 tests overflow of the month in 2038. - EXPECT_EQ(call_mktime(&tm_data, 2038, 1, 19, 3, 14, 7), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2038, 2, 19, 3, 14, 7, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); // 2039-01-19 03:14:07 tests overflow of the year. - EXPECT_EQ(call_mktime(&tm_data, 2039, 0, 19, 3, 14, 7), - OutOfRangeReturnValue); + EXPECT_THAT(call_mktime(&tm_data, 2039, 1, 19, 3, 14, 7, 0, 0), + Succeeds(TimeConstants::OutOfRangeReturnValue)); } TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) { struct tm tm_data; - // Before Jan of 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, -1, 15, 0, 0, 0), - OutOfRangeReturnValue); - // After Dec of 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, 12, 15, 0, 0, 0), - OutOfRangeReturnValue); + // -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 0, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(-31 * TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 12 - 1, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 1, // wday + 0, // yday + 0}), + tm_data); + // 1970-13-01 00:00:00 returns 1971-01-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 13, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(TimeConstants::DaysPerNonLeapYear * + TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 1971 - TimeConstants::TimeYearBase, // year + 5, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) { struct tm tm_data; - // -1 day of Jan, 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, 0, -1, 0, 0, 0), OutOfRangeReturnValue); - // 32 day of Jan, 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 32, 0, 0, 0), OutOfRangeReturnValue); - // 29 day of Feb, 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, 1, 29, 0, 0, 0), OutOfRangeReturnValue); - // 30 day of Feb, 1972 - EXPECT_EQ(call_mktime(&tm_data, 1972, 1, 30, 0, 0, 0), OutOfRangeReturnValue); - // 31 day of Apr, 1970 - EXPECT_EQ(call_mktime(&tm_data, 1970, 3, 31, 0, 0, 0), OutOfRangeReturnValue); -} + // -1 day from 1970-01-01 00:00:00 returns 1969-12-31 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 0, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(-1 * TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 31, // day + 11, // tm_mon starts with 0 for Jan + 1969 - TimeConstants::TimeYearBase, // year + 3, // wday + 0, // yday + 0}), + tm_data); -TEST(LlvmLibcMkTime, MktimeTestsStartEpochYear) { - // Thu Jan 1 00:00:00 1970 - struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 0, 0, 0), static_cast(0)); - EXPECT_EQ(4, tm_data.tm_wday); - EXPECT_EQ(0, tm_data.tm_yday); -} + // 1970-01-32 00:00:00 returns 1970-02-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 1, // month + 32, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(31 * TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 1970 - TimeConstants::TimeYearBase, // year + 0, // wday + 0, // yday + 0}), + tm_data); -TEST(LlvmLibcMkTime, MktimeTestsEpochYearRandomTime) { - // Thu Jan 1 12:50:50 1970 - struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 12, 50, 50), - static_cast(46250)); - EXPECT_EQ(4, tm_data.tm_wday); - EXPECT_EQ(0, tm_data.tm_yday); + // 1970-02-29 00:00:00 returns 1970-03-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1970, // year + 2, // month + 29, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(59 * TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 2, // tm_mon starts with 0 for Jan + 1970 - TimeConstants::TimeYearBase, // year + 0, // wday + 0, // yday + 0}), + tm_data); + + // 1972-02-30 00:00:00 returns 1972-03-01 00:00:00. + EXPECT_THAT(call_mktime(&tm_data, + 1972, // year + 2, // month + 30, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0), // yday + Succeeds(((2 * TimeConstants::DaysPerNonLeapYear) + 60) * + TimeConstants::SecondsPerDay)); + EXPECT_TM_EQ((tm{0, // sec + 0, // min + 0, // hr + 1, // day + 2, // tm_mon starts with 0 for Jan + 1972 - TimeConstants::TimeYearBase, // year + 3, // wday + 0, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) { struct tm tm_data; // Test for maximum value of a signed 32-bit integer. // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. - EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 7), - static_cast(0x7FFFFFFF)); - EXPECT_EQ(2, tm_data.tm_wday); - EXPECT_EQ(18, tm_data.tm_yday); + EXPECT_THAT(call_mktime(&tm_data, + 2038, // year + 1, // month + 19, // day + 3, // hr + 14, // min + 7, // sec + 0, // wday + 0), // yday + Succeeds(0x7FFFFFFF)); + EXPECT_TM_EQ((tm{7, // sec + 14, // min + 3, // hr + 19, // day + 0, // tm_mon starts with 0 for Jan + 2038 - TimeConstants::TimeYearBase, // year + 2, // wday + 7, // yday + 0}), + tm_data); } TEST(LlvmLibcMkTime, MktimeTests64BitYear) { if (sizeof(time_t) == 4) return; - // Mon Jan 1 12:50:50 2170 + // Mon Jan 1 12:50:50 2170 (200 years from 1970), struct tm tm_data; - EXPECT_EQ(call_mktime(&tm_data, 2170, 0, 1, 12, 50, 50), - static_cast(6311479850)); - EXPECT_EQ(1, tm_data.tm_wday); - EXPECT_EQ(0, tm_data.tm_yday); + EXPECT_THAT(call_mktime(&tm_data, + 2170, // year + 1, // month + 1, // day + 12, // hr + 50, // min + 50, // sec + 0, // wday + 0), // yday + Succeeds(6311479850)); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2170 - TimeConstants::TimeYearBase, // year + 1, // wday + 50, // yday + 0}), + tm_data); // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. - EXPECT_EQ(call_mktime(&tm_data, 2147483647, 0, 1, 12, 50, 50), - static_cast(67767976202043050)); - EXPECT_EQ(2, tm_data.tm_wday); - EXPECT_EQ(0, tm_data.tm_yday); + EXPECT_THAT(call_mktime(&tm_data, 2147483647, 1, 1, 12, 50, 50, 0, 0), + Succeeds(67767976202043050)); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2147483647 - TimeConstants::TimeYearBase, // year + 2, // wday + 50, // yday + 0}), + tm_data); }