blob: c99cf87332232ea64b735a92f3fa1fa150b8e19f [file] [log] [blame]
//===-- 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);
}
LLVM_LIBC_FUNCTION(time_t, 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