From eaae52c1fd459f9c0a147361bc3b962238faba5c Mon Sep 17 00:00:00 2001 From: Raman Tenneti Date: Thu, 11 Mar 2021 16:17:50 -0800 Subject: [PATCH] This introduces gmtime to LLVM libc, based on C99/C2X/Single Unix Spec. This change doesn't handle TIMEZONE, tm_isdst and leap seconds. Moved shared code between mktime and gmtime into time_utils.cpp. Reviewed By: sivachandra Differential Revision: https://reviews.llvm.org/D98467 --- libc/config/linux/api.td | 1 + libc/config/linux/x86_64/entrypoints.txt | 1 + libc/spec/stdc.td | 6 + libc/src/time/CMakeLists.txt | 23 ++- libc/src/time/gmtime.cpp | 29 ++++ libc/src/time/gmtime.h | 22 +++ libc/src/time/mktime.cpp | 130 +------------- libc/src/time/time_utils.cpp | 147 ++++++++++++++++ libc/src/time/time_utils.h | 4 + libc/test/src/time/CMakeLists.txt | 2 + libc/test/src/time/gmtime_test.cpp | 288 +++++++++++++++++++++++++++++++ libc/test/src/time/mktime_test.cpp | 18 +- 12 files changed, 532 insertions(+), 139 deletions(-) create mode 100644 libc/src/time/gmtime.cpp create mode 100644 libc/src/time/gmtime.h create mode 100644 libc/src/time/time_utils.cpp create mode 100644 libc/test/src/time/gmtime_test.cpp diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index fa7db68..c8dfd3b 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -249,6 +249,7 @@ def TimeAPI : PublicAPI<"time.h"> { ]; let Functions = [ + "gmtime", "mktime", ]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index a3fffae..8dca712 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -176,6 +176,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.threads.thrd_join # time.h entrypoints + libc.src.time.gmtime libc.src.time.mktime # unistd.h entrypoints diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td index 9b332a8..8afb37f 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -5,6 +5,7 @@ def StdC : StandardSpec<"stdc"> { RestrictedPtrType FILERestrictedPtr = RestrictedPtrType; NamedType StructTmType = NamedType<"struct tm">; PtrType StructTmPtr = PtrType; + PtrType TimeTTypePtr = PtrType; HeaderSpec Assert = HeaderSpec< "assert.h", @@ -598,6 +599,11 @@ def StdC : StandardSpec<"stdc"> { [], // Enumerations [ FunctionSpec< + "gmtime", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< "mktime", RetValSpec, [ArgSpec] diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt index a4aa97f..c11a658 100644 --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -1,11 +1,32 @@ +add_object_library( + time_utils + SRCS + time_utils.cpp + HDRS + time_utils.h +) + +add_entrypoint_object( + gmtime + SRCS + gmtime.cpp + HDRS + gmtime.h + DEPENDS + .time_utils + libc.include.errno + libc.include.time + libc.src.errno.__errno_location +) + add_entrypoint_object( mktime SRCS mktime.cpp HDRS mktime.h - time_utils.h DEPENDS + .time_utils libc.include.errno libc.include.time libc.src.errno.__errno_location diff --git a/libc/src/time/gmtime.cpp b/libc/src/time/gmtime.cpp new file mode 100644 index 0000000..04991a5 --- /dev/null +++ b/libc/src/time/gmtime.cpp @@ -0,0 +1,29 @@ +//===-- Implementation of gmtime function ---------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/time/gmtime.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(struct tm *, gmtime, (const time_t *timer)) { + static struct tm tm_out; + time_t seconds = *timer; + // Update the tm structure's year, month, day, etc. from seconds. + if (time_utils::UpdateFromSeconds(seconds, &tm_out) < 0) { + time_utils::OutOfRange(); + return nullptr; + } + + return &tm_out; +} + +} // namespace __llvm_libc diff --git a/libc/src/time/gmtime.h b/libc/src/time/gmtime.h new file mode 100644 index 0000000..8891a8c --- /dev/null +++ b/libc/src/time/gmtime.h @@ -0,0 +1,22 @@ +//===-- Implementation header of gmtime -------------------------*- 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_GMTIME_H +#define LLVM_LIBC_SRC_TIME_GMTIME_H + +#include + +namespace __llvm_libc { + +struct tm *gmtime(const time_t *timer); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_GMTIME_H + +#include "include/time.h" diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp index a352cc6..35970b1 100644 --- a/libc/src/time/mktime.cpp +++ b/libc/src/time/mktime.cpp @@ -29,134 +29,6 @@ static constexpr bool isLeapYear(const int64_t year) { return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); } -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 * tm_out)) { // Unlike most C Library functions, mktime doesn't just die on bad input. // TODO(rtenneti); Handle leap seconds. @@ -236,7 +108,7 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) { totalDays * TimeConstants::SecondsPerDay; // Update the tm structure's year, month, day, etc. from seconds. - if (updateFromSeconds(seconds, tm_out) < 0) + if (time_utils::UpdateFromSeconds(seconds, tm_out) < 0) return time_utils::OutOfRange(); return static_cast(seconds); diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp new file mode 100644 index 0000000..11ac2fb --- /dev/null +++ b/libc/src/time/time_utils.cpp @@ -0,0 +1,147 @@ +//===-- Implementation of mktime function ---------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/time/time_utils.h" +#include "src/__support/common.h" + +#include + +namespace __llvm_libc { +namespace time_utils { + +using __llvm_libc::time_utils::TimeConstants; + +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; +} + +// 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. +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; + // TODO(rtenneti): Need to handle timezone and update of tm_isdst. + tm->tm_isdst = 0; + + return 0; +} + +} // namespace time_utils +} // namespace __llvm_libc diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h index 48bbf7a..c87124e 100644 --- a/libc/src/time/time_utils.h +++ b/libc/src/time/time_utils.h @@ -59,6 +59,10 @@ static inline time_t OutOfRange() { return static_cast(-1); } +// Update the "tm" structure's year, month, etc. members from seconds. +// "total_seconds" is the number of seconds since January 1st, 1970. +extern int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm); + } // namespace time_utils } // namespace __llvm_libc diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt index 2e34ac6..690cdce 100644 --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -5,9 +5,11 @@ add_libc_unittest( SUITE libc_time_unittests SRCS + gmtime_test.cpp mktime_test.cpp HDRS TmMatcher.h DEPENDS + libc.src.time.gmtime libc.src.time.mktime ) diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp new file mode 100644 index 0000000..ba24d1f --- /dev/null +++ b/libc/test/src/time/gmtime_test.cpp @@ -0,0 +1,288 @@ +//===-- Unittests for gmtime ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/time/gmtime.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; +using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; +using __llvm_libc::time_utils::TimeConstants; + +TEST(LlvmLibcGmTime, OutOfRange) { + time_t seconds = 1 + INT_MAX * static_cast( + TimeConstants::NumberOfSecondsInLeapYear); + struct tm *tm_data = __llvm_libc::gmtime(&seconds); + EXPECT_TRUE(tm_data == nullptr); + EXPECT_EQ(llvmlibc_errno, EOVERFLOW); + + llvmlibc_errno = 0; + seconds = + INT_MIN * static_cast(TimeConstants::NumberOfSecondsInLeapYear) - + 1; + tm_data = __llvm_libc::gmtime(&seconds); + EXPECT_TRUE(tm_data == nullptr); + EXPECT_EQ(llvmlibc_errno, EOVERFLOW); +} + +TEST(LlvmLibcGmTime, InvalidSeconds) { + time_t seconds = 0; + struct tm *tm_data = nullptr; + // -1 second from 1970-01-01 00:00:00 returns 1969-12-31 23:59:59. + seconds = -1; + tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = 60; + tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, InvalidMinutes) { + time_t seconds = 0; + struct tm *tm_data = nullptr; + // -1 minute from 1970-01-01 00:00:00 returns 1969-12-31 23:59:00. + seconds = -TimeConstants::SecondsPerMin; + tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = 60 * TimeConstants::SecondsPerMin; + tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, InvalidHours) { + time_t seconds = 0; + struct tm *tm_data = nullptr; + // -1 hour from 1970-01-01 00:00:00 returns 1969-12-31 23:00:00. + seconds = -TimeConstants::SecondsPerHour; + tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = 24 * TimeConstants::SecondsPerHour; + tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, InvalidYear) { + // -1 year from 1970-01-01 00:00:00 returns 1969-01-01 00:00:00. + time_t seconds = + -TimeConstants::DaysPerNonLeapYear * TimeConstants::SecondsPerDay; + struct tm *tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, InvalidMonths) { + time_t seconds = 0; + struct tm *tm_data = nullptr; + // -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00. + seconds = -31 * TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = TimeConstants::DaysPerNonLeapYear * TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, InvalidDays) { + time_t seconds = 0; + struct tm *tm_data = nullptr; + // -1 day from 1970-01-01 00:00:00 returns 1969-12-31 00:00:00. + seconds = -1 * TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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); + + // 1970-01-32 00:00:00 returns 1970-02-01 00:00:00. + seconds = 31 * TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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); + + // 1970-02-29 00:00:00 returns 1970-03-01 00:00:00. + seconds = 59 * TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = ((2 * TimeConstants::DaysPerNonLeapYear) + 60) * + TimeConstants::SecondsPerDay; + tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, EndOf32BitEpochYear) { + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + time_t seconds = 0x7FFFFFFF; + struct tm *tm_data = __llvm_libc::gmtime(&seconds); + 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(LlvmLibcGmTime, Max64BitYear) { + if (sizeof(time_t) == 4) + return; + // Mon Jan 1 12:50:50 2170 (200 years from 1970), + time_t seconds = 6311479850; + struct tm *tm_data = __llvm_libc::gmtime(&seconds); + 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. + seconds = 67767976202043050; + tm_data = __llvm_libc::gmtime(&seconds); + 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); +} diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp index 3e76c58..93290d3 100644 --- a/libc/test/src/time/mktime_test.cpp +++ b/libc/test/src/time/mktime_test.cpp @@ -50,7 +50,7 @@ TEST(LlvmLibcMkTime, FailureSetsErrno) { EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW)); } -TEST(LlvmLibcMkTime, MkTimesInvalidSeconds) { +TEST(LlvmLibcMkTime, InvalidSeconds) { struct tm tm_data; // -1 second from 1970-01-01 00:00:00 returns 1969-12-31 23:59:59. EXPECT_THAT(call_mktime(&tm_data, @@ -96,7 +96,7 @@ TEST(LlvmLibcMkTime, MkTimesInvalidSeconds) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) { +TEST(LlvmLibcMkTime, InvalidMinutes) { struct tm tm_data; // -1 minute from 1970-01-01 00:00:00 returns 1969-12-31 23:59:00. EXPECT_THAT(call_mktime(&tm_data, @@ -142,7 +142,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) { +TEST(LlvmLibcMkTime, InvalidHours) { struct tm tm_data; // -1 hour from 1970-01-01 00:00:00 returns 1969-12-31 23:00:00. EXPECT_THAT(call_mktime(&tm_data, @@ -188,7 +188,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) { +TEST(LlvmLibcMkTime, InvalidYear) { struct tm tm_data; // -1 year from 1970-01-01 00:00:00 returns 1969-01-01 00:00:00. EXPECT_THAT(call_mktime(&tm_data, @@ -214,7 +214,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) { +TEST(LlvmLibcMkTime, InvalidEndOf32BitEpochYear) { if (sizeof(size_t) != 4) return; struct tm tm_data; @@ -238,7 +238,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) { Succeeds(TimeConstants::OutOfRangeReturnValue)); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) { +TEST(LlvmLibcMkTime, InvalidMonths) { struct tm tm_data; // -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00. EXPECT_THAT(call_mktime(&tm_data, @@ -285,7 +285,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) { +TEST(LlvmLibcMkTime, InvalidDays) { struct tm tm_data; // -1 day from 1970-01-01 00:00:00 returns 1969-12-31 00:00:00. EXPECT_THAT(call_mktime(&tm_data, @@ -377,7 +377,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) { +TEST(LlvmLibcMkTime, EndOf32BitEpochYear) { 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. @@ -403,7 +403,7 @@ TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) { tm_data); } -TEST(LlvmLibcMkTime, MktimeTests64BitYear) { +TEST(LlvmLibcMkTime, Max64BitYear) { if (sizeof(time_t) == 4) return; // Mon Jan 1 12:50:50 2170 (200 years from 1970), -- 2.7.4