xref: /llvm-project/libc/src/time/time_utils.cpp (revision eaae52c1fd459f9c0a147361bc3b962238faba5c)
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/time_utils.h"
10 #include "src/__support/common.h"
11 
12 #include <limits.h>
13 
14 namespace __llvm_libc {
15 namespace time_utils {
16 
17 using __llvm_libc::time_utils::TimeConstants;
18 
19 static int64_t computeRemainingYears(int64_t daysPerYears,
20                                      int64_t quotientYears,
21                                      int64_t *remainingDays) {
22   int64_t years = *remainingDays / daysPerYears;
23   if (years == quotientYears)
24     years--;
25   *remainingDays -= years * daysPerYears;
26   return years;
27 }
28 
29 // First, divide "total_seconds" by the number of seconds in a day to get the
30 // number of days since Jan 1 1970. The remainder will be used to calculate the
31 // number of Hours, Minutes and Seconds.
32 //
33 // Then, adjust that number of days by a constant to be the number of days
34 // since Mar 1 2000. Year 2000 is a multiple of 400, the leap year cycle. This
35 // makes it easier to count how many leap years have passed using division.
36 //
37 // While calculating numbers of years in the days, the following algorithm
38 // subdivides the days into the number of 400 years, the number of 100 years and
39 // the number of 4 years. These numbers of cycle years are used in calculating
40 // leap day. This is similar to the algorithm used in  getNumOfLeapYearsBefore()
41 // and isLeapYear(). Then compute the total number of years in days from these
42 // subdivided units.
43 //
44 // Compute the number of months from the remaining days. Finally, adjust years
45 // to be 1900 and months to be from January.
46 int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm) {
47   // Days in month starting from March in the year 2000.
48   static const char daysInMonth[] = {31 /* Mar */, 30, 31, 30, 31, 31,
49                                      30,           31, 30, 31, 31, 29};
50 
51   if (sizeof(time_t) == 4) {
52     if (total_seconds < 0x80000000)
53       return time_utils::OutOfRange();
54     if (total_seconds > 0x7FFFFFFF)
55       return time_utils::OutOfRange();
56   } else {
57     if (total_seconds <
58             INT_MIN * static_cast<int64_t>(
59                           TimeConstants::NumberOfSecondsInLeapYear) ||
60         total_seconds > INT_MAX * static_cast<int64_t>(
61                                       TimeConstants::NumberOfSecondsInLeapYear))
62       return time_utils::OutOfRange();
63   }
64 
65   int64_t seconds = total_seconds - TimeConstants::SecondsUntil2000MarchFirst;
66   int64_t days = seconds / TimeConstants::SecondsPerDay;
67   int64_t remainingSeconds = seconds % TimeConstants::SecondsPerDay;
68   if (remainingSeconds < 0) {
69     remainingSeconds += TimeConstants::SecondsPerDay;
70     days--;
71   }
72 
73   int64_t wday = (TimeConstants::WeekDayOf2000MarchFirst + days) %
74                  TimeConstants::DaysPerWeek;
75   if (wday < 0)
76     wday += TimeConstants::DaysPerWeek;
77 
78   // Compute the number of 400 year cycles.
79   int64_t numOfFourHundredYearCycles = days / TimeConstants::DaysPer400Years;
80   int64_t remainingDays = days % TimeConstants::DaysPer400Years;
81   if (remainingDays < 0) {
82     remainingDays += TimeConstants::DaysPer400Years;
83     numOfFourHundredYearCycles--;
84   }
85 
86   // The reminder number of years after computing number of
87   // "four hundred year cycles" will be 4 hundred year cycles or less in 400
88   // years.
89   int64_t numOfHundredYearCycles =
90       computeRemainingYears(TimeConstants::DaysPer100Years, 4, &remainingDays);
91 
92   // The reminder number of years after computing number of
93   // "hundred year cycles" will be 25 four year cycles or less in 100 years.
94   int64_t numOfFourYearCycles =
95       computeRemainingYears(TimeConstants::DaysPer4Years, 25, &remainingDays);
96 
97   // The reminder number of years after computing number of "four year cycles"
98   // will be 4 one year cycles or less in 4 years.
99   int64_t remainingYears = computeRemainingYears(
100       TimeConstants::DaysPerNonLeapYear, 4, &remainingDays);
101 
102   // Calculate number of years from year 2000.
103   int64_t years = remainingYears + 4 * numOfFourYearCycles +
104                   100 * numOfHundredYearCycles +
105                   400LL * numOfFourHundredYearCycles;
106 
107   int leapDay =
108       !remainingYears && (numOfFourYearCycles || !numOfHundredYearCycles);
109 
110   int64_t yday = remainingDays + 31 + 28 + leapDay;
111   if (yday >= TimeConstants::DaysPerNonLeapYear + leapDay)
112     yday -= TimeConstants::DaysPerNonLeapYear + leapDay;
113 
114   int64_t months = 0;
115   while (daysInMonth[months] <= remainingDays) {
116     remainingDays -= daysInMonth[months];
117     months++;
118   }
119 
120   if (months >= TimeConstants::MonthsPerYear - 2) {
121     months -= TimeConstants::MonthsPerYear;
122     years++;
123   }
124 
125   if (years > INT_MAX || years < INT_MIN)
126     return time_utils::OutOfRange();
127 
128   // All the data (years, month and remaining days) was calculated from
129   // March, 2000. Thus adjust the data to be from January, 1900.
130   tm->tm_year = years + 2000 - TimeConstants::TimeYearBase;
131   tm->tm_mon = months + 2;
132   tm->tm_mday = remainingDays + 1;
133   tm->tm_wday = wday;
134   tm->tm_yday = yday;
135 
136   tm->tm_hour = remainingSeconds / TimeConstants::SecondsPerHour;
137   tm->tm_min = remainingSeconds / TimeConstants::SecondsPerMin %
138                TimeConstants::SecondsPerMin;
139   tm->tm_sec = remainingSeconds % TimeConstants::SecondsPerMin;
140   // TODO(rtenneti): Need to handle timezone and update of tm_isdst.
141   tm->tm_isdst = 0;
142 
143   return 0;
144 }
145 
146 } // namespace time_utils
147 } // namespace __llvm_libc
148