--- /dev/null
+//===-- 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 "include/errno.h"
+
+#include "src/__support/common.h"
+#include "src/errno/llvmlibc_errno.h"
+#include "src/time/mktime.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) {
+ 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);
+}
+
+time_t LLVM_LIBC_ENTRYPOINT(mktime)(struct tm *t1) {
+ // 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();
+
+ // 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();
+ }
+
+ // Years are ints. A 32-bit year will fit into a 64-bit time_t.
+ // A 64-bit year will not.
+ static_assert(sizeof(int) == 4,
+ "ILP64 is unimplemented. This implementation requires "
+ "32-bit integers.");
+
+ if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1))
+ return outOfRange();
+ 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];
+ // Add one day if it is a leap year and the month is after February.
+ if (tmYearIsLeap && t1->tm_mon > 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++;
+ }
+ }
+
+ 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;
+}
+
+} // namespace __llvm_libc
--- /dev/null
+//===-- Unittests for mktime ----------------------------------------------===//
+//
+// 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/mktime.h"
+#include "test/ErrnoSetterMatcher.h"
+#include "utils/UnitTest/Test.h"
+
+#include <errno.h>
+#include <string.h>
+
+using __llvm_libc::testing::ErrnoSetterMatcher::Fails;
+
+static constexpr time_t OutOfRangeReturnValue = -1;
+
+// 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) {
+ struct tm temp = {.tm_sec = sec,
+ .tm_min = min,
+ .tm_hour = hour,
+ .tm_mday = mday,
+ .tm_mon = month,
+ .tm_year = year - 1900};
+ *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);
+ return __llvm_libc::mktime(tm_data);
+}
+
+TEST(MkTime, FailureSetsErrno) {
+ struct tm tm_data;
+ initialize_tm_data(&tm_data, 0, 0, 0, 0, 0, -1);
+ EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW));
+}
+
+TEST(MkTime, MktimeTestsInvalidSeconds) {
+ 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, MktimeTestsInvalidYear) {
+ struct tm tm_data;
+ EXPECT_EQ(call_mktime(&tm_data, 1969, 0, 0, 0, 0, 0), OutOfRangeReturnValue);
+}
+
+TEST(MkTime, MktimeTestsInvalidEndOf32BitEpochYear) {
+ if (sizeof(time_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);
+ // 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);
+ // 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);
+ // 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);
+ // 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);
+ // 2039-01-19 03:14:07 tests overflow of the year.
+ EXPECT_EQ(call_mktime(&tm_data, 2039, 0, 19, 3, 14, 7),
+ OutOfRangeReturnValue);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, 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);
+}
+
+TEST(MkTime, MktimeTests64BitYear) {
+ if (sizeof(time_t) == 4)
+ return;
+ // Mon Jan 1 12:50:50 2170
+ 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);
+
+ // 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);
+}