• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===-- Collection of utils for mktime and friends --------------*- C++ -*-===//
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 #ifndef LLVM_LIBC_SRC_TIME_TIME_UTILS_H
10 #define LLVM_LIBC_SRC_TIME_TIME_UTILS_H
11 
12 #include "hdr/types/size_t.h"
13 #include "hdr/types/struct_tm.h"
14 #include "hdr/types/time_t.h"
15 #include "src/__support/CPP/optional.h"
16 #include "src/__support/CPP/string_view.h"
17 #include "src/__support/common.h"
18 #include "src/__support/macros/config.h"
19 #include "src/errno/libc_errno.h"
20 #include "time_constants.h"
21 
22 #include <stdint.h>
23 
24 namespace LIBC_NAMESPACE_DECL {
25 namespace time_utils {
26 
27 // calculates the seconds from the epoch for tm_in. Does not update the struct,
28 // you must call update_from_seconds for that.
29 int64_t mktime_internal(const tm *tm_out);
30 
31 // Update the "tm" structure's year, month, etc. members from seconds.
32 // "total_seconds" is the number of seconds since January 1st, 1970.
33 int64_t update_from_seconds(int64_t total_seconds, tm *tm);
34 
35 // TODO(michaelrj): move these functions to use ErrorOr instead of setting
36 // errno. They always accompany a specific return value so we only need the one
37 // variable.
38 
39 // POSIX.1-2017 requires this.
out_of_range()40 LIBC_INLINE time_t out_of_range() {
41 #ifdef EOVERFLOW
42   // For non-POSIX uses of the standard C time functions, where EOVERFLOW is
43   // not defined, it's OK not to set errno at all. The plain C standard doesn't
44   // require it.
45   libc_errno = EOVERFLOW;
46 #endif
47   return time_constants::OUT_OF_RANGE_RETURN_VALUE;
48 }
49 
invalid_value()50 LIBC_INLINE void invalid_value() { libc_errno = EINVAL; }
51 
asctime(const tm * timeptr,char * buffer,size_t bufferLength)52 LIBC_INLINE char *asctime(const tm *timeptr, char *buffer,
53                           size_t bufferLength) {
54   if (timeptr == nullptr || buffer == nullptr) {
55     invalid_value();
56     return nullptr;
57   }
58   if (timeptr->tm_wday < 0 ||
59       timeptr->tm_wday > (time_constants::DAYS_PER_WEEK - 1)) {
60     invalid_value();
61     return nullptr;
62   }
63   if (timeptr->tm_mon < 0 ||
64       timeptr->tm_mon > (time_constants::MONTHS_PER_YEAR - 1)) {
65     invalid_value();
66     return nullptr;
67   }
68 
69   // TODO(michaelr): move this to use the strftime machinery
70   // equivalent to strftime(buffer, bufferLength, "%a %b %T %Y\n", timeptr)
71   int written_size = __builtin_snprintf(
72       buffer, bufferLength, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
73       time_constants::WEEK_DAY_NAMES[timeptr->tm_wday].data(),
74       time_constants::MONTH_NAMES[timeptr->tm_mon].data(), timeptr->tm_mday,
75       timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec,
76       time_constants::TIME_YEAR_BASE + timeptr->tm_year);
77   if (written_size < 0)
78     return nullptr;
79   if (static_cast<size_t>(written_size) >= bufferLength) {
80     out_of_range();
81     return nullptr;
82   }
83   return buffer;
84 }
85 
gmtime_internal(const time_t * timer,tm * result)86 LIBC_INLINE tm *gmtime_internal(const time_t *timer, tm *result) {
87   int64_t seconds = *timer;
88   // Update the tm structure's year, month, day, etc. from seconds.
89   if (update_from_seconds(seconds, result) < 0) {
90     out_of_range();
91     return nullptr;
92   }
93 
94   return result;
95 }
96 
97 // TODO: localtime is not yet implemented and a temporary solution is to
98 //       use gmtime, https://github.com/llvm/llvm-project/issues/107597
localtime(const time_t * t_ptr)99 LIBC_INLINE tm *localtime(const time_t *t_ptr) {
100   static tm result;
101   return time_utils::gmtime_internal(t_ptr, &result);
102 }
103 
104 // Returns number of years from (1, year).
get_num_of_leap_years_before(int64_t year)105 LIBC_INLINE constexpr int64_t get_num_of_leap_years_before(int64_t year) {
106   return (year / 4) - (year / 100) + (year / 400);
107 }
108 
109 // Returns True if year is a leap year.
is_leap_year(const int64_t year)110 LIBC_INLINE constexpr bool is_leap_year(const int64_t year) {
111   return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
112 }
113 
get_days_in_year(const int year)114 LIBC_INLINE constexpr int get_days_in_year(const int year) {
115   return is_leap_year(year) ? time_constants::DAYS_PER_LEAP_YEAR
116                             : time_constants::DAYS_PER_NON_LEAP_YEAR;
117 }
118 
119 // This is a helper class that takes a struct tm and lets you inspect its
120 // values. Where relevant, results are bounds checked and returned as optionals.
121 // This class does not, however, do data normalization except where necessary.
122 // It will faithfully return a date of 9999-99-99, even though that makes no
123 // sense.
124 class TMReader final {
125   const tm *timeptr;
126 
127   template <size_t N>
128   LIBC_INLINE constexpr cpp::optional<cpp::string_view>
bounds_check(const cpp::array<cpp::string_view,N> & arr,int index)129   bounds_check(const cpp::array<cpp::string_view, N> &arr, int index) const {
130     if (index >= 0 && index < static_cast<int>(arr.size()))
131       return arr[index];
132     return cpp::nullopt;
133   }
134 
135 public:
TMReader(const tm * tmptr)136   LIBC_INLINE constexpr explicit TMReader(const tm *tmptr) : timeptr(tmptr) {}
137 
138   // Strings
139   LIBC_INLINE constexpr cpp::optional<cpp::string_view>
get_weekday_short_name()140   get_weekday_short_name() const {
141     return bounds_check(time_constants::WEEK_DAY_NAMES, timeptr->tm_wday);
142   }
143 
144   LIBC_INLINE constexpr cpp::optional<cpp::string_view>
get_weekday_full_name()145   get_weekday_full_name() const {
146     return bounds_check(time_constants::WEEK_DAY_FULL_NAMES, timeptr->tm_wday);
147   }
148 
149   LIBC_INLINE constexpr cpp::optional<cpp::string_view>
get_month_short_name()150   get_month_short_name() const {
151     return bounds_check(time_constants::MONTH_NAMES, timeptr->tm_mon);
152   }
153 
154   LIBC_INLINE constexpr cpp::optional<cpp::string_view>
get_month_full_name()155   get_month_full_name() const {
156     return bounds_check(time_constants::MONTH_FULL_NAMES, timeptr->tm_mon);
157   }
158 
get_am_pm()159   LIBC_INLINE constexpr cpp::string_view get_am_pm() const {
160     if (timeptr->tm_hour < 12)
161       return "AM";
162     return "PM";
163   }
164 
get_timezone_name()165   LIBC_INLINE constexpr cpp::string_view get_timezone_name() const {
166     // TODO: timezone support
167     return "UTC";
168   }
169 
170   // Numbers
get_sec()171   LIBC_INLINE constexpr int get_sec() const { return timeptr->tm_sec; }
get_min()172   LIBC_INLINE constexpr int get_min() const { return timeptr->tm_min; }
get_hour()173   LIBC_INLINE constexpr int get_hour() const { return timeptr->tm_hour; }
get_mday()174   LIBC_INLINE constexpr int get_mday() const { return timeptr->tm_mday; }
get_mon()175   LIBC_INLINE constexpr int get_mon() const { return timeptr->tm_mon; }
get_yday()176   LIBC_INLINE constexpr int get_yday() const { return timeptr->tm_yday; }
get_wday()177   LIBC_INLINE constexpr int get_wday() const { return timeptr->tm_wday; }
get_isdst()178   LIBC_INLINE constexpr int get_isdst() const { return timeptr->tm_isdst; }
179 
180   // returns the year, counting from 1900
get_year_raw()181   LIBC_INLINE constexpr int get_year_raw() const { return timeptr->tm_year; }
182   // returns the year, counting from 0
get_year()183   LIBC_INLINE constexpr int get_year() const {
184     return timeptr->tm_year + time_constants::TIME_YEAR_BASE;
185   }
186 
is_leap_year()187   LIBC_INLINE constexpr int is_leap_year() const {
188     return time_utils::is_leap_year(get_year());
189   }
190 
get_iso_wday()191   LIBC_INLINE constexpr int get_iso_wday() const {
192     using time_constants::DAYS_PER_WEEK;
193     using time_constants::MONDAY;
194     // ISO uses a week that starts on Monday, but struct tm starts its week on
195     // Sunday. This function normalizes the weekday so that it always returns a
196     // value 0-6
197     const int NORMALIZED_WDAY = timeptr->tm_wday % DAYS_PER_WEEK;
198     return (NORMALIZED_WDAY + (DAYS_PER_WEEK - MONDAY)) % DAYS_PER_WEEK;
199   }
200 
201   // returns the week of the current year, with weeks starting on start_day.
get_week(time_constants::WeekDay start_day)202   LIBC_INLINE constexpr int get_week(time_constants::WeekDay start_day) const {
203     using time_constants::DAYS_PER_WEEK;
204     // The most recent start_day. The rest of the days into the current week
205     // don't count, so ignore them.
206     // Also add 7 to handle start_day > tm_wday
207     const int start_of_cur_week =
208         timeptr->tm_yday -
209         ((timeptr->tm_wday + DAYS_PER_WEEK - start_day) % DAYS_PER_WEEK);
210 
211     // The original formula is ceil((start_of_cur_week + 1) / DAYS_PER_WEEK)
212     // That becomes (start_of_cur_week + 1 + DAYS_PER_WEEK - 1) / DAYS_PER_WEEK)
213     // Which simplifies to (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK
214     const int ceil_weeks_since_start =
215         (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK;
216 
217     return ceil_weeks_since_start;
218   }
219 
get_iso_week()220   LIBC_INLINE constexpr int get_iso_week() const {
221     using time_constants::DAYS_PER_WEEK;
222     using time_constants::ISO_FIRST_DAY_OF_YEAR;
223     using time_constants::MONDAY;
224     using time_constants::WeekDay;
225     using time_constants::WEEKS_PER_YEAR;
226 
227     constexpr WeekDay START_DAY = MONDAY;
228 
229     // The most recent start_day. The rest of the days into the current week
230     // don't count, so ignore them.
231     // Also add 7 to handle start_day > tm_wday
232     const int start_of_cur_week =
233         timeptr->tm_yday -
234         ((timeptr->tm_wday + DAYS_PER_WEEK - START_DAY) % DAYS_PER_WEEK);
235 
236     // if the week starts in the previous year, and also if the 4th of this year
237     // is not in this week.
238     if (start_of_cur_week < -3) {
239       const int days_into_prev_year =
240           get_days_in_year(get_year() - 1) + start_of_cur_week;
241       // Each year has at least 52 weeks, but a year's last week will be 53 if
242       // its first week starts in the previous year and its last week ends
243       // in the next year. We know get_year() - 1 must extend into get_year(),
244       // so here we check if it also extended into get_year() - 2 and add 1 week
245       // if it does.
246       return WEEKS_PER_YEAR +
247              ((days_into_prev_year % DAYS_PER_WEEK) > ISO_FIRST_DAY_OF_YEAR);
248     }
249 
250     // subtract 1 to account for yday being 0 indexed
251     const int days_until_end_of_year =
252         get_days_in_year(get_year()) - start_of_cur_week - 1;
253 
254     // if there are less than 3 days from the start of this week to the end of
255     // the year, then there must be 4 days in this week in the next year, which
256     // means that this week is the first week of that year.
257     if (days_until_end_of_year < 3)
258       return 1;
259 
260     // else just calculate the current week like normal.
261     const int ceil_weeks_since_start =
262         (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK;
263 
264     // add 1 if this year's first week starts in the previous year.
265     const int WEEK_STARTS_IN_PREV_YEAR =
266         ((start_of_cur_week + time_constants::DAYS_PER_WEEK) %
267          time_constants::DAYS_PER_WEEK) > time_constants::ISO_FIRST_DAY_OF_YEAR;
268     return ceil_weeks_since_start + WEEK_STARTS_IN_PREV_YEAR;
269   }
270 
get_iso_year()271   LIBC_INLINE constexpr int get_iso_year() const {
272     const int BASE_YEAR = get_year();
273     // The ISO year is the same as a standard year for all dates after the start
274     // of the first week and before the last week. Since the first ISO week of a
275     // year starts on the 4th, anything after that is in this year.
276     if (timeptr->tm_yday >= time_constants::ISO_FIRST_DAY_OF_YEAR &&
277         timeptr->tm_yday < time_constants::DAYS_PER_NON_LEAP_YEAR -
278                                time_constants::DAYS_PER_WEEK)
279       return BASE_YEAR;
280 
281     const int ISO_WDAY = get_iso_wday();
282     // The first week of the ISO year is defined as the week containing the
283     // 4th day of January.
284 
285     // first week
286     if (timeptr->tm_yday < time_constants::ISO_FIRST_DAY_OF_YEAR) {
287       /*
288       If jan 4 is in this week, then we're in BASE_YEAR, else we're in the
289       previous year. The formula's been rearranged so here's the derivation:
290 
291               +--------+-- days until jan 4
292               |        |
293        wday + (4 - yday) < 7
294        |               |
295        +---------------+-- weekday of jan 4
296 
297        rearranged to get all the constants on one side:
298 
299        wday - yday < 7 - 4
300       */
301       const int IS_CUR_YEAR = (ISO_WDAY - timeptr->tm_yday <
302                                time_constants::DAYS_PER_WEEK -
303                                    time_constants::ISO_FIRST_DAY_OF_YEAR);
304       return BASE_YEAR - !IS_CUR_YEAR;
305     }
306 
307     // last week
308     const int DAYS_LEFT_IN_YEAR =
309         get_days_in_year(get_year()) - timeptr->tm_yday;
310     /*
311     Similar to above, we're checking if jan 4 (of next year) is in this week. If
312     it is, this is in the next year. Note that this also handles the case of
313     yday > days in year gracefully.
314 
315            +------------------+-- days until jan 4 (of next year)
316            |                  |
317     wday + (4 + remaining days) < 7
318     |                         |
319     +-------------------------+-- weekday of jan 4
320 
321     rearranging we get:
322 
323     wday + remaining days < 7 - 4
324     */
325     const int IS_NEXT_YEAR =
326         (ISO_WDAY + DAYS_LEFT_IN_YEAR <
327          time_constants::DAYS_PER_WEEK - time_constants::ISO_FIRST_DAY_OF_YEAR);
328     return BASE_YEAR + IS_NEXT_YEAR;
329   }
330 
get_epoch()331   LIBC_INLINE time_t get_epoch() const {
332     return static_cast<time_t>(mktime_internal(timeptr));
333   }
334 
335   // returns the timezone offset in microwave time:
336   // return (hours * 100) + minutes;
337   // This means that a shift of -4:30 is returned as -430, simplifying
338   // conversion.
get_timezone_offset()339   LIBC_INLINE constexpr int get_timezone_offset() const {
340     // TODO: timezone support
341     return 0;
342   }
343 };
344 
345 } // namespace time_utils
346 } // namespace LIBC_NAMESPACE_DECL
347 
348 #endif // LLVM_LIBC_SRC_TIME_TIME_UTILS_H
349