1 //===-- Implementation of mktime function ---------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "include/errno.h" 10 11 #include "src/__support/common.h" 12 #include "src/errno/llvmlibc_errno.h" 13 #include "src/time/mktime.h" 14 15 namespace __llvm_libc { 16 17 constexpr int SecondsPerMin = 60; 18 constexpr int MinutesPerHour = 60; 19 constexpr int HoursPerDay = 24; 20 constexpr int DaysPerWeek = 7; 21 constexpr int MonthsPerYear = 12; 22 constexpr int DaysPerNonLeapYear = 365; 23 constexpr int TimeYearBase = 1900; 24 constexpr int EpochYear = 1970; 25 constexpr int EpochWeekDay = 4; 26 // The latest time that can be represented in this form is 03:14:07 UTC on 27 // Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the 28 // start of the epoch). This means that systems using a 32-bit time_t type are 29 // susceptible to the Year 2038 problem. 30 constexpr int EndOf32BitEpochYear = 2038; 31 32 constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30, 33 31, 31, 30, 31, 30, 31}; 34 35 constexpr bool isLeapYear(const time_t year) { 36 return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); 37 } 38 39 // POSIX.1-2017 requires this. 40 static inline time_t outOfRange() { 41 llvmlibc_errno = EOVERFLOW; 42 return static_cast<time_t>(-1); 43 } 44 45 time_t LLVM_LIBC_ENTRYPOINT(mktime)(struct tm *t1) { 46 // Unlike most C Library functions, mktime doesn't just die on bad input. 47 // TODO(rtenneti); Handle leap seconds. Handle out of range time and date 48 // values that don't overflow or underflow. 49 // TODO (rtenneti): Implement the following suggestion Siva: "As we start 50 // accumulating the seconds, we should be able to check if the next amount of 51 // seconds to be added can lead to an overflow. If it does, return the 52 // overflow value. If not keep accumulating. The benefit is that, we don't 53 // have to validate every input, and also do not need the special cases for 54 // sizeof(time_t) == 4". 55 if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1)) 56 return outOfRange(); 57 if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1)) 58 return outOfRange(); 59 if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1)) 60 return outOfRange(); 61 time_t tmYearFromBase = t1->tm_year + TimeYearBase; 62 63 if (tmYearFromBase < EpochYear) 64 return outOfRange(); 65 66 // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038. 67 if (sizeof(time_t) == 4 && tmYearFromBase >= EndOf32BitEpochYear) { 68 if (tmYearFromBase > EndOf32BitEpochYear) 69 return outOfRange(); 70 if (t1->tm_mon > 0) 71 return outOfRange(); 72 if (t1->tm_mday > 19) 73 return outOfRange(); 74 if (t1->tm_hour > 3) 75 return outOfRange(); 76 if (t1->tm_min > 14) 77 return outOfRange(); 78 if (t1->tm_sec > 7) 79 return outOfRange(); 80 } 81 82 // Years are ints. A 32-bit year will fit into a 64-bit time_t. 83 // A 64-bit year will not. 84 static_assert(sizeof(int) == 4, 85 "ILP64 is unimplemented. This implementation requires " 86 "32-bit integers."); 87 88 if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1)) 89 return outOfRange(); 90 bool tmYearIsLeap = isLeapYear(tmYearFromBase); 91 time_t daysInMonth = NonLeapYearDaysInMonth[t1->tm_mon]; 92 // Add one day if it is a leap year and the month is February. 93 if (tmYearIsLeap && t1->tm_mon == 1) 94 ++daysInMonth; 95 if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth) 96 return outOfRange(); 97 98 time_t totalDays = t1->tm_mday - 1; 99 for (int i = 0; i < t1->tm_mon; ++i) 100 totalDays += NonLeapYearDaysInMonth[i]; 101 // Add one day if it is a leap year and the month is after February. 102 if (tmYearIsLeap && t1->tm_mon > 1) 103 totalDays++; 104 t1->tm_yday = totalDays; 105 totalDays += (tmYearFromBase - EpochYear) * DaysPerNonLeapYear; 106 107 // Add an extra day for each leap year, starting with 1972 108 for (time_t year = EpochYear + 2; year < tmYearFromBase;) { 109 if (isLeapYear(year)) { 110 totalDays += 1; 111 year += 4; 112 } else { 113 year++; 114 } 115 } 116 117 t1->tm_wday = (EpochWeekDay + totalDays) % DaysPerWeek; 118 if (t1->tm_wday < 0) 119 t1->tm_wday += DaysPerWeek; 120 // TODO(rtenneti): Need to handle timezone and update of tm_isdst. 121 return t1->tm_sec + t1->tm_min * SecondsPerMin + 122 t1->tm_hour * MinutesPerHour * SecondsPerMin + 123 totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin; 124 } 125 126 } // namespace __llvm_libc 127