xref: /aosp_15_r20/external/llvm-libc/src/time/mktime.cpp (revision 71db0c75aadcf003ffe3238005f61d7618a3fead)
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 "src/time/mktime.h"
10 #include "src/__support/common.h"
11 #include "src/__support/macros/config.h"
12 #include "src/time/time_utils.h"
13 
14 namespace LIBC_NAMESPACE_DECL {
15 
16 using LIBC_NAMESPACE::time_utils::TimeConstants;
17 
18 static constexpr int NON_LEAP_YEAR_DAYS_IN_MONTH[] = {31, 28, 31, 30, 31, 30,
19                                                       31, 31, 30, 31, 30, 31};
20 
21 // Returns number of years from (1, year).
get_num_of_leap_years_before(int64_t year)22 static constexpr int64_t get_num_of_leap_years_before(int64_t year) {
23   return (year / 4) - (year / 100) + (year / 400);
24 }
25 
26 // Returns True if year is a leap year.
is_leap_year(const int64_t year)27 static constexpr bool is_leap_year(const int64_t year) {
28   return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
29 }
30 
31 LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
32   // Unlike most C Library functions, mktime doesn't just die on bad input.
33   // TODO(rtenneti); Handle leap seconds.
34   int64_t tm_year_from_base = tm_out->tm_year + TimeConstants::TIME_YEAR_BASE;
35 
36   // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
37   if (sizeof(time_t) == 4 &&
38       tm_year_from_base >= TimeConstants::END_OF32_BIT_EPOCH_YEAR) {
39     if (tm_year_from_base > TimeConstants::END_OF32_BIT_EPOCH_YEAR)
40       return time_utils::out_of_range();
41     if (tm_out->tm_mon > 0)
42       return time_utils::out_of_range();
43     if (tm_out->tm_mday > 19)
44       return time_utils::out_of_range();
45     else if (tm_out->tm_mday == 19) {
46       if (tm_out->tm_hour > 3)
47         return time_utils::out_of_range();
48       else if (tm_out->tm_hour == 3) {
49         if (tm_out->tm_min > 14)
50           return time_utils::out_of_range();
51         else if (tm_out->tm_min == 14) {
52           if (tm_out->tm_sec > 7)
53             return time_utils::out_of_range();
54         }
55       }
56     }
57   }
58 
59   // Years are ints.  A 32-bit year will fit into a 64-bit time_t.
60   // A 64-bit year will not.
61   static_assert(
62       sizeof(int) == 4,
63       "ILP64 is unimplemented. This implementation requires 32-bit integers.");
64 
65   // Calculate number of months and years from tm_mon.
66   int64_t month = tm_out->tm_mon;
67   if (month < 0 || month >= TimeConstants::MONTHS_PER_YEAR - 1) {
68     int64_t years = month / 12;
69     month %= 12;
70     if (month < 0) {
71       years--;
72       month += 12;
73     }
74     tm_year_from_base += years;
75   }
76   bool tm_year_is_leap = is_leap_year(tm_year_from_base);
77 
78   // Calculate total number of days based on the month and the day (tm_mday).
79   int64_t total_days = tm_out->tm_mday - 1;
80   for (int64_t i = 0; i < month; ++i)
81     total_days += NON_LEAP_YEAR_DAYS_IN_MONTH[i];
82   // Add one day if it is a leap year and the month is after February.
83   if (tm_year_is_leap && month > 1)
84     total_days++;
85 
86   // Calculate total numbers of days based on the year.
87   total_days += (tm_year_from_base - TimeConstants::EPOCH_YEAR) *
88                 TimeConstants::DAYS_PER_NON_LEAP_YEAR;
89   if (tm_year_from_base >= TimeConstants::EPOCH_YEAR) {
90     total_days += get_num_of_leap_years_before(tm_year_from_base - 1) -
91                   get_num_of_leap_years_before(TimeConstants::EPOCH_YEAR);
92   } else if (tm_year_from_base >= 1) {
93     total_days -= get_num_of_leap_years_before(TimeConstants::EPOCH_YEAR) -
94                   get_num_of_leap_years_before(tm_year_from_base - 1);
95   } else {
96     // Calculate number of leap years until 0th year.
97     total_days -= get_num_of_leap_years_before(TimeConstants::EPOCH_YEAR) -
98                   get_num_of_leap_years_before(0);
99     if (tm_year_from_base <= 0) {
100       total_days -= 1; // Subtract 1 for 0th year.
101       // Calculate number of leap years until -1 year
102       if (tm_year_from_base < 0) {
103         total_days -= get_num_of_leap_years_before(-tm_year_from_base) -
104                       get_num_of_leap_years_before(1);
105       }
106     }
107   }
108 
109   // TODO(rtenneti): Need to handle timezone and update of tm_isdst.
110   int64_t seconds = tm_out->tm_sec +
111                     tm_out->tm_min * TimeConstants::SECONDS_PER_MIN +
112                     tm_out->tm_hour * TimeConstants::SECONDS_PER_HOUR +
113                     total_days * TimeConstants::SECONDS_PER_DAY;
114 
115   // Update the tm structure's year, month, day, etc. from seconds.
116   if (time_utils::update_from_seconds(seconds, tm_out) < 0)
117     return time_utils::out_of_range();
118 
119   return static_cast<time_t>(seconds);
120 }
121 
122 } // namespace LIBC_NAMESPACE_DECL
123