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