Skip to content

Commit a760e7f

Browse files
[libc] create TimeReader to look at a struct tm (#126138)
In the process of adding strftime (#122556) I wrote this utility class to simplify reading from a struct tm. It provides helper functions that return basically everything needed by strftime. It's not tested directly, but it is thoroughly exercised by the strftime tests.
1 parent ad6cd7e commit a760e7f

File tree

6 files changed

+369
-100
lines changed

6 files changed

+369
-100
lines changed

libc/include/llvm-libc-types/struct_tm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct tm {
1919
int tm_wday; // days since Sunday
2020
int tm_yday; // days since January
2121
int tm_isdst; // Daylight Saving Time flag
22+
// TODO: add tm_gmtoff and tm_zone? (posix extensions)
2223
};
2324

2425
#endif // LLVM_LIBC_TYPES_STRUCT_TM_H

libc/src/time/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ add_object_library(
2222
DEPENDS
2323
libc.include.time
2424
libc.src.__support.CPP.limits
25+
libc.src.__support.CPP.string_view
26+
libc.src.__support.CPP.optional
2527
libc.src.errno.errno
2628
.time_constants
2729
libc.hdr.types.time_t

libc/src/time/mktime.cpp

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,100 +14,8 @@
1414

1515
namespace LIBC_NAMESPACE_DECL {
1616

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

11220
// Update the tm structure's year, month, day, etc. from seconds.
11321
if (time_utils::update_from_seconds(seconds, tm_out) < 0)

libc/src/time/time_constants.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace LIBC_NAMESPACE_DECL {
1818
namespace time_constants {
1919

2020
enum Month : int {
21-
JANUARY,
21+
JANUARY = 0,
2222
FEBRUARY,
2323
MARCH,
2424
APRIL,
@@ -32,14 +32,28 @@ enum Month : int {
3232
DECEMBER
3333
};
3434

35+
enum WeekDay : int {
36+
SUNDAY = 0,
37+
MONDAY,
38+
TUESDAY,
39+
WEDNESDAY,
40+
THURSDAY,
41+
FRIDAY,
42+
SATURDAY
43+
};
44+
3545
constexpr int SECONDS_PER_MIN = 60;
3646
constexpr int MINUTES_PER_HOUR = 60;
3747
constexpr int HOURS_PER_DAY = 24;
3848
constexpr int DAYS_PER_WEEK = 7;
49+
constexpr int WEEKS_PER_YEAR = 52;
3950
constexpr int MONTHS_PER_YEAR = 12;
4051
constexpr int DAYS_PER_NON_LEAP_YEAR = 365;
4152
constexpr int DAYS_PER_LEAP_YEAR = 366;
4253

54+
constexpr int LAST_DAY_OF_NON_LEAP_YEAR = DAYS_PER_NON_LEAP_YEAR - 1;
55+
constexpr int LAST_DAY_OF_LEAP_YEAR = DAYS_PER_LEAP_YEAR - 1;
56+
4357
constexpr int SECONDS_PER_HOUR = SECONDS_PER_MIN * MINUTES_PER_HOUR;
4458
constexpr int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
4559
constexpr int NUMBER_OF_SECONDS_IN_LEAP_YEAR =
@@ -49,6 +63,8 @@ constexpr int TIME_YEAR_BASE = 1900;
4963
constexpr int EPOCH_YEAR = 1970;
5064
constexpr int EPOCH_WEEK_DAY = 4;
5165

66+
constexpr int ISO_FIRST_DAY_OF_YEAR = 3; // the 4th day of the year, 0-indexed.
67+
5268
// For asctime the behavior is undefined if struct tm's tm_wday or tm_mon are
5369
// not within the normal ranges as defined in <time.h>, or if struct tm's
5470
// tm_year exceeds {INT_MAX}-1990, or if the below asctime_internal algorithm

libc/src/time/time_utils.cpp

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,103 @@
1212
#include "src/__support/macros/config.h"
1313
#include "src/time/time_constants.h"
1414

15+
#include <stdint.h>
16+
1517
namespace LIBC_NAMESPACE_DECL {
1618
namespace time_utils {
1719

20+
// TODO: clean this up in a followup patch
21+
int64_t mktime_internal(const tm *tm_out) {
22+
// Unlike most C Library functions, mktime doesn't just die on bad input.
23+
// TODO(rtenneti); Handle leap seconds.
24+
int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;
25+
26+
// 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
27+
if (sizeof(time_t) == 4 &&
28+
tm_year_from_base >= time_constants::END_OF32_BIT_EPOCH_YEAR) {
29+
if (tm_year_from_base > time_constants::END_OF32_BIT_EPOCH_YEAR)
30+
return time_utils::out_of_range();
31+
if (tm_out->tm_mon > 0)
32+
return time_utils::out_of_range();
33+
if (tm_out->tm_mday > 19)
34+
return time_utils::out_of_range();
35+
else if (tm_out->tm_mday == 19) {
36+
if (tm_out->tm_hour > 3)
37+
return time_utils::out_of_range();
38+
else if (tm_out->tm_hour == 3) {
39+
if (tm_out->tm_min > 14)
40+
return time_utils::out_of_range();
41+
else if (tm_out->tm_min == 14) {
42+
if (tm_out->tm_sec > 7)
43+
return time_utils::out_of_range();
44+
}
45+
}
46+
}
47+
}
48+
49+
// Years are ints. A 32-bit year will fit into a 64-bit time_t.
50+
// A 64-bit year will not.
51+
static_assert(
52+
sizeof(int) == 4,
53+
"ILP64 is unimplemented. This implementation requires 32-bit integers.");
54+
55+
// Calculate number of months and years from tm_mon.
56+
int64_t month = tm_out->tm_mon;
57+
if (month < 0 || month >= time_constants::MONTHS_PER_YEAR - 1) {
58+
int64_t years = month / 12;
59+
month %= 12;
60+
if (month < 0) {
61+
years--;
62+
month += 12;
63+
}
64+
tm_year_from_base += years;
65+
}
66+
bool tm_year_is_leap = time_utils::is_leap_year(tm_year_from_base);
67+
68+
// Calculate total number of days based on the month and the day (tm_mday).
69+
int64_t total_days = tm_out->tm_mday - 1;
70+
for (int64_t i = 0; i < month; ++i)
71+
total_days += time_constants::NON_LEAP_YEAR_DAYS_IN_MONTH[i];
72+
// Add one day if it is a leap year and the month is after February.
73+
if (tm_year_is_leap && month > 1)
74+
total_days++;
75+
76+
// Calculate total numbers of days based on the year.
77+
total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
78+
time_constants::DAYS_PER_NON_LEAP_YEAR;
79+
if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
80+
total_days +=
81+
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1) -
82+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
83+
} else if (tm_year_from_base >= 1) {
84+
total_days -=
85+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
86+
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1);
87+
} else {
88+
// Calculate number of leap years until 0th year.
89+
total_days -=
90+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
91+
time_utils::get_num_of_leap_years_before(0);
92+
if (tm_year_from_base <= 0) {
93+
total_days -= 1; // Subtract 1 for 0th year.
94+
// Calculate number of leap years until -1 year
95+
if (tm_year_from_base < 0) {
96+
total_days -=
97+
time_utils::get_num_of_leap_years_before(-tm_year_from_base) -
98+
time_utils::get_num_of_leap_years_before(1);
99+
}
100+
}
101+
}
102+
103+
// TODO: https://github.com/llvm/llvm-project/issues/121962
104+
// Need to handle timezone and update of tm_isdst.
105+
int64_t seconds = tm_out->tm_sec +
106+
tm_out->tm_min * time_constants::SECONDS_PER_MIN +
107+
tm_out->tm_hour * time_constants::SECONDS_PER_HOUR +
108+
total_days * time_constants::SECONDS_PER_DAY;
109+
return seconds;
110+
}
111+
18112
static int64_t computeRemainingYears(int64_t daysPerYears,
19113
int64_t quotientYears,
20114
int64_t *remainingDays) {
@@ -42,7 +136,7 @@ static int64_t computeRemainingYears(int64_t daysPerYears,
42136
//
43137
// Compute the number of months from the remaining days. Finally, adjust years
44138
// to be 1900 and months to be from January.
45-
int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
139+
int64_t update_from_seconds(int64_t total_seconds, tm *tm) {
46140
// Days in month starting from March in the year 2000.
47141
static const char daysInMonth[] = {31 /* Mar */, 30, 31, 30, 31, 31,
48142
30, 31, 30, 31, 31, 29};

0 commit comments

Comments
 (0)