Changes to mktime to handle invalid dates, overflow and underflow andcalculating...
authorRaman Tenneti <rtenneti@google.com>
Tue, 23 Feb 2021 02:03:11 +0000 (18:03 -0800)
committerRaman Tenneti <rtenneti@google.com>
Tue, 23 Feb 2021 02:37:40 +0000 (18:37 -0800)
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

libc/src/time/CMakeLists.txt
libc/src/time/mktime.cpp
libc/src/time/time_utils.h [new file with mode: 0644]
libc/test/src/time/CMakeLists.txt
libc/test/src/time/TmMatcher.h [new file with mode: 0644]
libc/test/src/time/mktime_test.cpp

index dc2d957..a4aa97f 100644 (file)
@@ -4,6 +4,7 @@ add_entrypoint_object(
     mktime.cpp
   HDRS
     mktime.h
+    time_utils.h
   DEPENDS
     libc.include.errno
     libc.include.time
index c99cf87..3e0d06f 100644 (file)
 //
 //===----------------------------------------------------------------------===//
 
-#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 (file)
index 0000000..00cc399
--- /dev/null
@@ -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
index c466af4..2e34ac6 100644 (file)
@@ -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 (file)
index 0000000..3217c5f
--- /dev/null
@@ -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
index 7d4da70..3e76c58 100644 (file)
 //===----------------------------------------------------------------------===//
 
 #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);
 }