Skip to content

Commit 6f0f844

Browse files
Initial commit of mktime.
This introduces mktime to LLVM libc, based on C99/C2X/Single Unix Spec. Co-authored-by: Jeff Bailey <[email protected]> This change doesn't handle TIMEZONE, tm_isdst and leap seconds. It returns -1 for invalid dates. I have verified the return results for all the possible dates with glibc's mktime. TODO: + Handle leap seconds. + Handle out of range time and date values that don't overflow or underflow. + Implement the following suggestion Siva - As we start accumulating the seconds, we should be able to check if the next amount of seconds to be added can lead to an overflow. If it does, return the overflow value. If not keep accumulating. The benefit is that, we don't have to validate every input, and also do not need the special cases for sizeof(time_t) == 4. + Handle timezone and update of tm_isdst Reviewed By: sivachandra Differential Revision: https://reviews.llvm.org/D91551
1 parent 40659cd commit 6f0f844

File tree

14 files changed

+413
-0
lines changed

14 files changed

+413
-0
lines changed

libc/config/linux/api.td

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ def SSizeT : TypeDecl<"ssize_t"> {
2020
}];
2121
}
2222

23+
def StructTm: TypeDecl<"struct tm"> {
24+
let Decl = [{
25+
struct tm {
26+
int tm_sec; // seconds after the minute
27+
int tm_min; // minutes after the hour
28+
int tm_hour; // hours since midnight
29+
int tm_mday; // day of the month
30+
int tm_mon; // months since January
31+
int tm_year; // years since 1900
32+
int tm_wday; // days since Sunday
33+
int tm_yday; // days since January
34+
int tm_isdst; // Daylight Saving Time flag
35+
};
36+
}];
37+
}
38+
39+
def TimeT: TypeDecl<"time_t"> {
40+
let Decl = [{
41+
typedef long time_t;
42+
}];
43+
}
44+
2345
def OffT : TypeDecl<"off_t"> {
2446
let Decl = [{
2547
#define __need_off_t
@@ -177,6 +199,17 @@ def StdIOAPI : PublicAPI<"stdio.h"> {
177199
def StdlibAPI : PublicAPI<"stdlib.h"> {
178200
}
179201

202+
def TimeAPI : PublicAPI<"time.h"> {
203+
let TypeDeclarations = [
204+
StructTm,
205+
TimeT,
206+
];
207+
208+
let Functions = [
209+
"mktime",
210+
];
211+
}
212+
180213
def ErrnoAPI : PublicAPI<"errno.h"> {
181214
let Macros = [
182215
ErrnoMacro,

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ set(TARGET_LIBC_ENTRYPOINTS
6767
libc.src.threads.thrd_create
6868
libc.src.threads.thrd_join
6969

70+
# time.h entrypoints
71+
libc.src.time.mktime
72+
7073
# unistd.h entrypoints
7174
libc.src.unistd.write
7275
)

libc/config/linux/x86_64/headers.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ set(TARGET_PUBLIC_HEADERS
99
libc.include.sys_mman
1010
libc.include.sys_syscall
1111
libc.include.threads
12+
libc.include.time
1213
libc.include.unistd
1314
)

libc/include/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ add_gen_header(
5050
.llvm_libc_common_h
5151
)
5252

53+
add_gen_header(
54+
time
55+
DEF_FILE time.h.def
56+
GEN_HDR time.h
57+
DEPENDS
58+
.llvm_libc_common_h
59+
)
60+
5361
add_gen_header(
5462
threads
5563
DEF_FILE threads.h.def

libc/include/time.h.def

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//===-- C standard library header time.h ----------------------------------===//
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_TIME_H
10+
#define LLVM_LIBC_TIME_H
11+
12+
#include <__llvm-libc-common.h>
13+
14+
%%public_api()
15+
16+
#endif // LLVM_LIBC_TIME_H

libc/spec/spec.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def DoublePtr : PtrType<DoubleType>;
7979

8080
def SigHandlerT : NamedType<"__sighandler_t">;
8181

82+
def TimeTType : NamedType<"time_t">;
83+
8284
//added because __assert_fail needs it.
8385
def UnsignedType : NamedType<"unsigned">;
8486

libc/spec/stdc.td

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ def StdC : StandardSpec<"stdc"> {
33
NamedType FILE = NamedType<"FILE">;
44
PtrType FILEPtr = PtrType<FILE>;
55
RestrictedPtrType FILERestrictedPtr = RestrictedPtrType<FILE>;
6+
NamedType StructTmType = NamedType<"struct tm">;
7+
PtrType StructTmPtr = PtrType<StructTmType>;
68

79
HeaderSpec Assert = HeaderSpec<
810
"assert.h",
@@ -467,6 +469,23 @@ def StdC : StandardSpec<"stdc"> {
467469
]
468470
>;
469471

472+
HeaderSpec Time = HeaderSpec<
473+
"time.h",
474+
[], // Macros
475+
[ // Types
476+
StructTmType,
477+
TimeTType,
478+
],
479+
[], // Enumerations
480+
[
481+
FunctionSpec<
482+
"mktime",
483+
RetValSpec<TimeTType>,
484+
[ArgSpec<StructTmPtr>]
485+
>,
486+
]
487+
>;
488+
470489
let Headers = [
471490
Assert,
472491
CType,
@@ -477,5 +496,6 @@ def StdC : StandardSpec<"stdc"> {
477496
StdLib,
478497
Signal,
479498
Threads,
499+
Time,
480500
];
481501
}

libc/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_subdirectory(string)
99
# TODO: Add this target conditional to the target OS.
1010
add_subdirectory(sys)
1111
add_subdirectory(threads)
12+
add_subdirectory(time)
1213
add_subdirectory(unistd)
1314

1415
add_subdirectory(__support)

libc/src/time/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
add_entrypoint_object(
2+
mktime
3+
SRCS
4+
mktime.cpp
5+
HDRS
6+
mktime.h
7+
DEPENDS
8+
libc.include.errno
9+
libc.include.time
10+
libc.src.errno.__errno_location
11+
)

libc/src/time/mktime.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 "include/errno.h"
10+
11+
#include "src/__support/common.h"
12+
#include "src/errno/llvmlibc_errno.h"
13+
#include "src/time/mktime.h"
14+
15+
namespace __llvm_libc {
16+
17+
constexpr int SecondsPerMin = 60;
18+
constexpr int MinutesPerHour = 60;
19+
constexpr int HoursPerDay = 24;
20+
constexpr int DaysPerWeek = 7;
21+
constexpr int MonthsPerYear = 12;
22+
constexpr int DaysPerNonLeapYear = 365;
23+
constexpr int TimeYearBase = 1900;
24+
constexpr int EpochYear = 1970;
25+
constexpr int EpochWeekDay = 4;
26+
// The latest time that can be represented in this form is 03:14:07 UTC on
27+
// Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the
28+
// start of the epoch). This means that systems using a 32-bit time_t type are
29+
// susceptible to the Year 2038 problem.
30+
constexpr int EndOf32BitEpochYear = 2038;
31+
32+
constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30,
33+
31, 31, 30, 31, 30, 31};
34+
35+
constexpr bool isLeapYear(const time_t year) {
36+
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
37+
}
38+
39+
// POSIX.1-2017 requires this.
40+
static inline time_t outOfRange() {
41+
llvmlibc_errno = EOVERFLOW;
42+
return static_cast<time_t>(-1);
43+
}
44+
45+
time_t LLVM_LIBC_ENTRYPOINT(mktime)(struct tm *t1) {
46+
// Unlike most C Library functions, mktime doesn't just die on bad input.
47+
// TODO(rtenneti); Handle leap seconds. Handle out of range time and date
48+
// values that don't overflow or underflow.
49+
// TODO (rtenneti): Implement the following suggestion Siva: "As we start
50+
// accumulating the seconds, we should be able to check if the next amount of
51+
// seconds to be added can lead to an overflow. If it does, return the
52+
// overflow value. If not keep accumulating. The benefit is that, we don't
53+
// have to validate every input, and also do not need the special cases for
54+
// sizeof(time_t) == 4".
55+
if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1))
56+
return outOfRange();
57+
if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1))
58+
return outOfRange();
59+
if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1))
60+
return outOfRange();
61+
time_t tmYearFromBase = t1->tm_year + TimeYearBase;
62+
63+
if (tmYearFromBase < EpochYear)
64+
return outOfRange();
65+
66+
// 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
67+
if (sizeof(time_t) == 4 && tmYearFromBase >= EndOf32BitEpochYear) {
68+
if (tmYearFromBase > EndOf32BitEpochYear)
69+
return outOfRange();
70+
if (t1->tm_mon > 0)
71+
return outOfRange();
72+
if (t1->tm_mday > 19)
73+
return outOfRange();
74+
if (t1->tm_hour > 3)
75+
return outOfRange();
76+
if (t1->tm_min > 14)
77+
return outOfRange();
78+
if (t1->tm_sec > 7)
79+
return outOfRange();
80+
}
81+
82+
// Years are ints. A 32-bit year will fit into a 64-bit time_t.
83+
// A 64-bit year will not.
84+
static_assert(sizeof(int) == 4,
85+
"ILP64 is unimplemented. This implementation requires "
86+
"32-bit integers.");
87+
88+
if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1))
89+
return outOfRange();
90+
bool tmYearIsLeap = isLeapYear(tmYearFromBase);
91+
time_t daysInMonth = NonLeapYearDaysInMonth[t1->tm_mon];
92+
// Add one day if it is a leap year and the month is February.
93+
if (tmYearIsLeap && t1->tm_mon == 1)
94+
++daysInMonth;
95+
if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth)
96+
return outOfRange();
97+
98+
time_t totalDays = t1->tm_mday - 1;
99+
for (int i = 0; i < t1->tm_mon; ++i)
100+
totalDays += NonLeapYearDaysInMonth[i];
101+
// Add one day if it is a leap year and the month is after February.
102+
if (tmYearIsLeap && t1->tm_mon > 1)
103+
totalDays++;
104+
t1->tm_yday = totalDays;
105+
totalDays += (tmYearFromBase - EpochYear) * DaysPerNonLeapYear;
106+
107+
// Add an extra day for each leap year, starting with 1972
108+
for (time_t year = EpochYear + 2; year < tmYearFromBase;) {
109+
if (isLeapYear(year)) {
110+
totalDays += 1;
111+
year += 4;
112+
} else {
113+
year++;
114+
}
115+
}
116+
117+
t1->tm_wday = (EpochWeekDay + totalDays) % DaysPerWeek;
118+
if (t1->tm_wday < 0)
119+
t1->tm_wday += DaysPerWeek;
120+
// TODO(rtenneti): Need to handle timezone and update of tm_isdst.
121+
return t1->tm_sec + t1->tm_min * SecondsPerMin +
122+
t1->tm_hour * MinutesPerHour * SecondsPerMin +
123+
totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin;
124+
}
125+
126+
} // namespace __llvm_libc

libc/src/time/mktime.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===-- Implementation header of mktime -------------------------*- 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_MKTIME_H
10+
#define LLVM_LIBC_SRC_TIME_MKTIME_H
11+
12+
#include "src/time/mktime.h"
13+
#include <time.h>
14+
15+
namespace __llvm_libc {
16+
17+
time_t mktime(struct tm *t1);
18+
19+
} // namespace __llvm_libc
20+
21+
#endif // LLVM_LIBC_SRC_TIME_MKTIME_H
22+
23+
#include "include/time.h"

libc/test/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_subdirectory(stdlib)
88
add_subdirectory(string)
99
add_subdirectory(sys)
1010
add_subdirectory(threads)
11+
add_subdirectory(time)
1112
add_subdirectory(unistd)
1213

1314
set(public_test ${CMAKE_CURRENT_BINARY_DIR}/public_integration_test.cpp)

libc/test/src/time/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
add_libc_testsuite(libc_time_unittests)
2+
3+
add_libc_unittest(
4+
mktime
5+
SUITE
6+
libc_time_unittests
7+
SRCS
8+
mktime_test.cpp
9+
DEPENDS
10+
libc.src.time.mktime
11+
)

0 commit comments

Comments
 (0)