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