[libc-commits] [libc] 034f562 - Changes to mktime to handle invalid dates, overflow and underflow andcalculating the correct date and thenumber of seconds even if invalid datesare passed as arguments.

Raman Tenneti via libc-commits libc-commits at lists.llvm.org
Mon Feb 22 18:38:05 PST 2021


Author: Raman Tenneti
Date: 2021-02-22T18:37:40-08:00
New Revision: 034f5629256c810ecf4805911e3fe407e562f3b7

URL: https://github.com/llvm/llvm-project/commit/034f5629256c810ecf4805911e3fe407e562f3b7
DIFF: https://github.com/llvm/llvm-project/commit/034f5629256c810ecf4805911e3fe407e562f3b7.diff

LOG: Changes to mktime to handle invalid dates, overflow and underflow andcalculating the correct date and thenumber of seconds even if invalid datesare passed as arguments.

Added tests for invalid dates like the following
  Date 1970-01-01 00:00:-1 is treated as 1969-12-31 23:59:59 and seconds
  are returned for the modified date.

Tested the code by doing ninja check-libc (and cmake).

Reviewed By: sivachandra, rtenneti

Differential Revision: https://reviews.llvm.org/D96684

Added: 
    libc/src/time/time_utils.h
    libc/test/src/time/TmMatcher.h

Modified: 
    libc/src/time/CMakeLists.txt
    libc/src/time/mktime.cpp
    libc/test/src/time/CMakeLists.txt
    libc/test/src/time/mktime_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index dc2d95717d89..a4aa97f12f5f 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -4,6 +4,7 @@ add_entrypoint_object(
     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
index c99cf8733223..3e0d06f96415 100644
--- 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 <limits.h>
 
 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<time_t>(-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<int64_t>(
+                          TimeConstants::NumberOfSecondsInLeapYear) ||
+        total_seconds > INT_MAX * static_cast<int64_t>(
+                                      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 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * t1)) {
                 "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<time_t>(seconds);
 }
 
 } // namespace __llvm_libc

diff  --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
new file mode 100644
index 000000000000..00cc3991a086
--- /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 <stdint.h>
+
+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<time_t>(-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
index c466af4214e2..2e34ac6d6c53 100644
--- a/libc/test/src/time/CMakeLists.txt
+++ b/libc/test/src/time/CMakeLists.txt
@@ -6,6 +6,8 @@ add_libc_unittest(
     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
index 000000000000..3217c5f2e497
--- /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 <time.h>
+
+#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
index 7d4da70da7c5..3e76c586cba7 100644
--- 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 <errno.h>
+#include <limits.h>
 #include <string.h>
 
 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<time_t>(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<time_t>(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<time_t>(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<time_t>(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<time_t>(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);
 }


        


More information about the libc-commits mailing list