xref: /llvm-project/libc/src/time/mktime.cpp (revision f9c2377fb68e5051b3061186c507f7b87db2a8b2)
16f0f844eSRaman Tenneti //===-- Implementation of mktime function ---------------------------------===//
26f0f844eSRaman Tenneti //
36f0f844eSRaman Tenneti // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
46f0f844eSRaman Tenneti // See https://llvm.org/LICENSE.txt for license information.
56f0f844eSRaman Tenneti // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66f0f844eSRaman Tenneti //
76f0f844eSRaman Tenneti //===----------------------------------------------------------------------===//
86f0f844eSRaman Tenneti 
96f0f844eSRaman Tenneti #include "src/time/mktime.h"
10034f5629SRaman Tenneti #include "src/__support/common.h"
115ff3ff33SPetr Hosek #include "src/__support/macros/config.h"
12*f9c2377fSMichael Jones #include "src/time/time_constants.h"
13034f5629SRaman Tenneti #include "src/time/time_utils.h"
14034f5629SRaman Tenneti 
155ff3ff33SPetr Hosek namespace LIBC_NAMESPACE_DECL {
166f0f844eSRaman Tenneti 
17034f5629SRaman Tenneti // Returns number of years from (1, year).
181c92911eSMichael Jones static constexpr int64_t get_num_of_leap_years_before(int64_t year) {
19034f5629SRaman Tenneti   return (year / 4) - (year / 100) + (year / 400);
20034f5629SRaman Tenneti }
216f0f844eSRaman Tenneti 
22034f5629SRaman Tenneti // Returns True if year is a leap year.
231c92911eSMichael Jones static constexpr bool is_leap_year(const int64_t year) {
246f0f844eSRaman Tenneti   return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
256f0f844eSRaman Tenneti }
266f0f844eSRaman Tenneti 
27034f5629SRaman Tenneti LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
28034f5629SRaman Tenneti   // Unlike most C Library functions, mktime doesn't just die on bad input.
29034f5629SRaman Tenneti   // TODO(rtenneti); Handle leap seconds.
30*f9c2377fSMichael Jones   int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;
316f0f844eSRaman Tenneti 
326f0f844eSRaman Tenneti   // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
33034f5629SRaman Tenneti   if (sizeof(time_t) == 4 &&
34*f9c2377fSMichael Jones       tm_year_from_base >= time_constants::END_OF32_BIT_EPOCH_YEAR) {
35*f9c2377fSMichael Jones     if (tm_year_from_base > time_constants::END_OF32_BIT_EPOCH_YEAR)
361c92911eSMichael Jones       return time_utils::out_of_range();
37034f5629SRaman Tenneti     if (tm_out->tm_mon > 0)
381c92911eSMichael Jones       return time_utils::out_of_range();
39034f5629SRaman Tenneti     if (tm_out->tm_mday > 19)
401c92911eSMichael Jones       return time_utils::out_of_range();
4181d82739SSimon Tatham     else if (tm_out->tm_mday == 19) {
42034f5629SRaman Tenneti       if (tm_out->tm_hour > 3)
431c92911eSMichael Jones         return time_utils::out_of_range();
4481d82739SSimon Tatham       else if (tm_out->tm_hour == 3) {
45034f5629SRaman Tenneti         if (tm_out->tm_min > 14)
461c92911eSMichael Jones           return time_utils::out_of_range();
4781d82739SSimon Tatham         else if (tm_out->tm_min == 14) {
48034f5629SRaman Tenneti           if (tm_out->tm_sec > 7)
491c92911eSMichael Jones             return time_utils::out_of_range();
506f0f844eSRaman Tenneti         }
5181d82739SSimon Tatham       }
5281d82739SSimon Tatham     }
5381d82739SSimon Tatham   }
546f0f844eSRaman Tenneti 
556f0f844eSRaman Tenneti   // Years are ints.  A 32-bit year will fit into a 64-bit time_t.
566f0f844eSRaman Tenneti   // A 64-bit year will not.
5780225af4SMikhail R. Gadelha   static_assert(
5880225af4SMikhail R. Gadelha       sizeof(int) == 4,
5980225af4SMikhail R. Gadelha       "ILP64 is unimplemented. This implementation requires 32-bit integers.");
606f0f844eSRaman Tenneti 
61034f5629SRaman Tenneti   // Calculate number of months and years from tm_mon.
62034f5629SRaman Tenneti   int64_t month = tm_out->tm_mon;
63*f9c2377fSMichael Jones   if (month < 0 || month >= time_constants::MONTHS_PER_YEAR - 1) {
64034f5629SRaman Tenneti     int64_t years = month / 12;
65034f5629SRaman Tenneti     month %= 12;
66034f5629SRaman Tenneti     if (month < 0) {
67034f5629SRaman Tenneti       years--;
68034f5629SRaman Tenneti       month += 12;
69034f5629SRaman Tenneti     }
701c92911eSMichael Jones     tm_year_from_base += years;
71034f5629SRaman Tenneti   }
721c92911eSMichael Jones   bool tm_year_is_leap = is_leap_year(tm_year_from_base);
736f0f844eSRaman Tenneti 
74034f5629SRaman Tenneti   // Calculate total number of days based on the month and the day (tm_mday).
751c92911eSMichael Jones   int64_t total_days = tm_out->tm_mday - 1;
76034f5629SRaman Tenneti   for (int64_t i = 0; i < month; ++i)
77*f9c2377fSMichael Jones     total_days += time_constants::NON_LEAP_YEAR_DAYS_IN_MONTH[i];
786f0f844eSRaman Tenneti   // Add one day if it is a leap year and the month is after February.
791c92911eSMichael Jones   if (tm_year_is_leap && month > 1)
801c92911eSMichael Jones     total_days++;
816f0f844eSRaman Tenneti 
82034f5629SRaman Tenneti   // Calculate total numbers of days based on the year.
83*f9c2377fSMichael Jones   total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
84*f9c2377fSMichael Jones                 time_constants::DAYS_PER_NON_LEAP_YEAR;
85*f9c2377fSMichael Jones   if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
861c92911eSMichael Jones     total_days += get_num_of_leap_years_before(tm_year_from_base - 1) -
87*f9c2377fSMichael Jones                   get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
881c92911eSMichael Jones   } else if (tm_year_from_base >= 1) {
89*f9c2377fSMichael Jones     total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
901c92911eSMichael Jones                   get_num_of_leap_years_before(tm_year_from_base - 1);
916f0f844eSRaman Tenneti   } else {
92034f5629SRaman Tenneti     // Calculate number of leap years until 0th year.
93*f9c2377fSMichael Jones     total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
941c92911eSMichael Jones                   get_num_of_leap_years_before(0);
951c92911eSMichael Jones     if (tm_year_from_base <= 0) {
961c92911eSMichael Jones       total_days -= 1; // Subtract 1 for 0th year.
97034f5629SRaman Tenneti       // Calculate number of leap years until -1 year
981c92911eSMichael Jones       if (tm_year_from_base < 0) {
991c92911eSMichael Jones         total_days -= get_num_of_leap_years_before(-tm_year_from_base) -
1001c92911eSMichael Jones                       get_num_of_leap_years_before(1);
101034f5629SRaman Tenneti       }
1026f0f844eSRaman Tenneti     }
1036f0f844eSRaman Tenneti   }
1046f0f844eSRaman Tenneti 
1050a58a1c9SNick Desaulniers   // TODO: https://github.com/llvm/llvm-project/issues/121962
1060a58a1c9SNick Desaulniers   // Need to handle timezone and update of tm_isdst.
107034f5629SRaman Tenneti   int64_t seconds = tm_out->tm_sec +
108*f9c2377fSMichael Jones                     tm_out->tm_min * time_constants::SECONDS_PER_MIN +
109*f9c2377fSMichael Jones                     tm_out->tm_hour * time_constants::SECONDS_PER_HOUR +
110*f9c2377fSMichael Jones                     total_days * time_constants::SECONDS_PER_DAY;
111034f5629SRaman Tenneti 
112034f5629SRaman Tenneti   // Update the tm structure's year, month, day, etc. from seconds.
1131c92911eSMichael Jones   if (time_utils::update_from_seconds(seconds, tm_out) < 0)
1141c92911eSMichael Jones     return time_utils::out_of_range();
115034f5629SRaman Tenneti 
116034f5629SRaman Tenneti   return static_cast<time_t>(seconds);
1176f0f844eSRaman Tenneti }
1186f0f844eSRaman Tenneti 
1195ff3ff33SPetr Hosek } // namespace LIBC_NAMESPACE_DECL
120