Skip to content

Commit bedf3a0

Browse files
[libc] create TimeReader to look at a struct tm
In the process of adding strftime (llvm#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 ae7f7c4 commit bedf3a0

File tree

6 files changed

+368
-94
lines changed

6 files changed

+368
-94
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: 16 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,6 +32,16 @@ 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;
@@ -40,6 +50,9 @@ constexpr int MONTHS_PER_YEAR = 12;
4050
constexpr int DAYS_PER_NON_LEAP_YEAR = 365;
4151
constexpr int DAYS_PER_LEAP_YEAR = 366;
4252

53+
constexpr int LAST_DAY_OF_NON_LEAP_YEAR = DAYS_PER_NON_LEAP_YEAR - 1;
54+
constexpr int LAST_DAY_OF_LEAP_YEAR = DAYS_PER_LEAP_YEAR - 1;
55+
4356
constexpr int SECONDS_PER_HOUR = SECONDS_PER_MIN * MINUTES_PER_HOUR;
4457
constexpr int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
4558
constexpr int NUMBER_OF_SECONDS_IN_LEAP_YEAR =
@@ -49,6 +62,8 @@ constexpr int TIME_YEAR_BASE = 1900;
4962
constexpr int EPOCH_YEAR = 1970;
5063
constexpr int EPOCH_WEEK_DAY = 4;
5164

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

libc/src/time/time_utils.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,98 @@
1515
namespace LIBC_NAMESPACE_DECL {
1616
namespace time_utils {
1717

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

0 commit comments

Comments
 (0)