Initial commit of mktime.

This introduces mktime to LLVM libc, based on C99/C2X/Single Unix Spec.

Co-authored-by: Jeff Bailey <jeffbailey@google.com>

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
This commit is contained in:
Raman Tenneti 2020-11-30 21:06:47 -08:00
parent 40659cd2c6
commit 6f0f844e9a
14 changed files with 413 additions and 0 deletions

View File

@ -20,6 +20,28 @@ def SSizeT : TypeDecl<"ssize_t"> {
}];
}
def StructTm: TypeDecl<"struct tm"> {
let Decl = [{
struct tm {
int tm_sec; // seconds after the minute
int tm_min; // minutes after the hour
int tm_hour; // hours since midnight
int tm_mday; // day of the month
int tm_mon; // months since January
int tm_year; // years since 1900
int tm_wday; // days since Sunday
int tm_yday; // days since January
int tm_isdst; // Daylight Saving Time flag
};
}];
}
def TimeT: TypeDecl<"time_t"> {
let Decl = [{
typedef long time_t;
}];
}
def OffT : TypeDecl<"off_t"> {
let Decl = [{
#define __need_off_t
@ -177,6 +199,17 @@ def StdIOAPI : PublicAPI<"stdio.h"> {
def StdlibAPI : PublicAPI<"stdlib.h"> {
}
def TimeAPI : PublicAPI<"time.h"> {
let TypeDeclarations = [
StructTm,
TimeT,
];
let Functions = [
"mktime",
];
}
def ErrnoAPI : PublicAPI<"errno.h"> {
let Macros = [
ErrnoMacro,

View File

@ -67,6 +67,9 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.threads.thrd_create
libc.src.threads.thrd_join
# time.h entrypoints
libc.src.time.mktime
# unistd.h entrypoints
libc.src.unistd.write
)

View File

@ -9,5 +9,6 @@ set(TARGET_PUBLIC_HEADERS
libc.include.sys_mman
libc.include.sys_syscall
libc.include.threads
libc.include.time
libc.include.unistd
)

View File

@ -50,6 +50,14 @@ add_gen_header(
.llvm_libc_common_h
)
add_gen_header(
time
DEF_FILE time.h.def
GEN_HDR time.h
DEPENDS
.llvm_libc_common_h
)
add_gen_header(
threads
DEF_FILE threads.h.def

16
libc/include/time.h.def Normal file
View File

@ -0,0 +1,16 @@
//===-- C standard library header time.h ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_TIME_H
#define LLVM_LIBC_TIME_H
#include <__llvm-libc-common.h>
%%public_api()
#endif // LLVM_LIBC_TIME_H

View File

@ -79,6 +79,8 @@ def DoublePtr : PtrType<DoubleType>;
def SigHandlerT : NamedType<"__sighandler_t">;
def TimeTType : NamedType<"time_t">;
//added because __assert_fail needs it.
def UnsignedType : NamedType<"unsigned">;

View File

@ -3,6 +3,8 @@ def StdC : StandardSpec<"stdc"> {
NamedType FILE = NamedType<"FILE">;
PtrType FILEPtr = PtrType<FILE>;
RestrictedPtrType FILERestrictedPtr = RestrictedPtrType<FILE>;
NamedType StructTmType = NamedType<"struct tm">;
PtrType StructTmPtr = PtrType<StructTmType>;
HeaderSpec Assert = HeaderSpec<
"assert.h",
@ -467,6 +469,23 @@ def StdC : StandardSpec<"stdc"> {
]
>;
HeaderSpec Time = HeaderSpec<
"time.h",
[], // Macros
[ // Types
StructTmType,
TimeTType,
],
[], // Enumerations
[
FunctionSpec<
"mktime",
RetValSpec<TimeTType>,
[ArgSpec<StructTmPtr>]
>,
]
>;
let Headers = [
Assert,
CType,
@ -477,5 +496,6 @@ def StdC : StandardSpec<"stdc"> {
StdLib,
Signal,
Threads,
Time,
];
}

View File

@ -9,6 +9,7 @@ add_subdirectory(string)
# TODO: Add this target conditional to the target OS.
add_subdirectory(sys)
add_subdirectory(threads)
add_subdirectory(time)
add_subdirectory(unistd)
add_subdirectory(__support)

View File

@ -0,0 +1,11 @@
add_entrypoint_object(
mktime
SRCS
mktime.cpp
HDRS
mktime.h
DEPENDS
libc.include.errno
libc.include.time
libc.src.errno.__errno_location
)

126
libc/src/time/mktime.cpp Normal file
View File

@ -0,0 +1,126 @@
//===-- Implementation of mktime function ---------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "include/errno.h"
#include "src/__support/common.h"
#include "src/errno/llvmlibc_errno.h"
#include "src/time/mktime.h"
namespace __llvm_libc {
constexpr int SecondsPerMin = 60;
constexpr int MinutesPerHour = 60;
constexpr int HoursPerDay = 24;
constexpr int DaysPerWeek = 7;
constexpr int MonthsPerYear = 12;
constexpr int DaysPerNonLeapYear = 365;
constexpr int TimeYearBase = 1900;
constexpr int EpochYear = 1970;
constexpr int EpochWeekDay = 4;
// The latest time that can be represented in this form is 03:14:07 UTC on
// Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the
// start of the epoch). This means that systems using a 32-bit time_t type are
// susceptible to the Year 2038 problem.
constexpr int EndOf32BitEpochYear = 2038;
constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
constexpr bool isLeapYear(const time_t year) {
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
}
// POSIX.1-2017 requires this.
static inline time_t outOfRange() {
llvmlibc_errno = EOVERFLOW;
return static_cast<time_t>(-1);
}
time_t LLVM_LIBC_ENTRYPOINT(mktime)(struct tm *t1) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds. Handle out of range time and date
// values that don't overflow or underflow.
// TODO (rtenneti): 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".
if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1))
return outOfRange();
if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1))
return outOfRange();
if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1))
return outOfRange();
time_t tmYearFromBase = t1->tm_year + TimeYearBase;
if (tmYearFromBase < EpochYear)
return outOfRange();
// 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
if (sizeof(time_t) == 4 && tmYearFromBase >= EndOf32BitEpochYear) {
if (tmYearFromBase > EndOf32BitEpochYear)
return outOfRange();
if (t1->tm_mon > 0)
return outOfRange();
if (t1->tm_mday > 19)
return outOfRange();
if (t1->tm_hour > 3)
return outOfRange();
if (t1->tm_min > 14)
return outOfRange();
if (t1->tm_sec > 7)
return outOfRange();
}
// Years are ints. A 32-bit year will fit into a 64-bit time_t.
// A 64-bit year will not.
static_assert(sizeof(int) == 4,
"ILP64 is unimplemented. This implementation requires "
"32-bit integers.");
if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1))
return outOfRange();
bool tmYearIsLeap = isLeapYear(tmYearFromBase);
time_t daysInMonth = NonLeapYearDaysInMonth[t1->tm_mon];
// Add one day if it is a leap year and the month is February.
if (tmYearIsLeap && t1->tm_mon == 1)
++daysInMonth;
if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth)
return outOfRange();
time_t totalDays = t1->tm_mday - 1;
for (int i = 0; i < t1->tm_mon; ++i)
totalDays += NonLeapYearDaysInMonth[i];
// Add one day if it is a leap year and the month is after February.
if (tmYearIsLeap && t1->tm_mon > 1)
totalDays++;
t1->tm_yday = totalDays;
totalDays += (tmYearFromBase - EpochYear) * DaysPerNonLeapYear;
// Add an extra day for each leap year, starting with 1972
for (time_t year = EpochYear + 2; year < tmYearFromBase;) {
if (isLeapYear(year)) {
totalDays += 1;
year += 4;
} else {
year++;
}
}
t1->tm_wday = (EpochWeekDay + totalDays) % DaysPerWeek;
if (t1->tm_wday < 0)
t1->tm_wday += DaysPerWeek;
// TODO(rtenneti): Need to handle timezone and update of tm_isdst.
return t1->tm_sec + t1->tm_min * SecondsPerMin +
t1->tm_hour * MinutesPerHour * SecondsPerMin +
totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin;
}
} // namespace __llvm_libc

23
libc/src/time/mktime.h Normal file
View File

@ -0,0 +1,23 @@
//===-- Implementation header of mktime -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_TIME_MKTIME_H
#define LLVM_LIBC_SRC_TIME_MKTIME_H
#include "src/time/mktime.h"
#include <time.h>
namespace __llvm_libc {
time_t mktime(struct tm *t1);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_TIME_MKTIME_H
#include "include/time.h"

View File

@ -8,6 +8,7 @@ add_subdirectory(stdlib)
add_subdirectory(string)
add_subdirectory(sys)
add_subdirectory(threads)
add_subdirectory(time)
add_subdirectory(unistd)
set(public_test ${CMAKE_CURRENT_BINARY_DIR}/public_integration_test.cpp)

View File

@ -0,0 +1,11 @@
add_libc_testsuite(libc_time_unittests)
add_libc_unittest(
mktime
SUITE
libc_time_unittests
SRCS
mktime_test.cpp
DEPENDS
libc.src.time.mktime
)

View File

@ -0,0 +1,157 @@
//===-- Unittests for mktime ----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/time/mktime.h"
#include "test/ErrnoSetterMatcher.h"
#include "utils/UnitTest/Test.h"
#include <errno.h>
#include <string.h>
using __llvm_libc::testing::ErrnoSetterMatcher::Fails;
static constexpr time_t OutOfRangeReturnValue = -1;
// A helper function to initialize tm data structure.
static inline void initialize_tm_data(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec) {
struct tm temp = {.tm_sec = sec,
.tm_min = min,
.tm_hour = hour,
.tm_mday = mday,
.tm_mon = month,
.tm_year = year - 1900};
*tm_data = temp;
}
static inline time_t call_mktime(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec) {
initialize_tm_data(tm_data, year, month, mday, hour, min, sec);
return __llvm_libc::mktime(tm_data);
}
TEST(MkTime, FailureSetsErrno) {
struct tm tm_data;
initialize_tm_data(&tm_data, 0, 0, 0, 0, 0, -1);
EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW));
}
TEST(MkTime, MktimeTestsInvalidSeconds) {
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, -1), OutOfRangeReturnValue);
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, 60), OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidMinutes) {
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, -1, 0), OutOfRangeReturnValue);
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 1, 0, 60, 0), OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidHours) {
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, -1, 0, 0), OutOfRangeReturnValue);
EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 24, 0, 0), OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidYear) {
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 1969, 0, 0, 0, 0, 0), OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidEndOf32BitEpochYear) {
if (sizeof(time_t) != 4)
return;
struct tm tm_data;
// 2038-01-19 03:14:08 tests overflow of the second in 2038.
EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 8),
OutOfRangeReturnValue);
// 2038-01-19 03:15:07 tests overflow of the minute in 2038.
EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 15, 7),
OutOfRangeReturnValue);
// 2038-01-19 04:14:07 tests overflow of the hour in 2038.
EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 4, 14, 7),
OutOfRangeReturnValue);
// 2038-01-20 03:14:07 tests overflow of the day in 2038.
EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 20, 3, 14, 7),
OutOfRangeReturnValue);
// 2038-02-19 03:14:07 tests overflow of the month in 2038.
EXPECT_EQ(call_mktime(&tm_data, 2038, 1, 19, 3, 14, 7),
OutOfRangeReturnValue);
// 2039-01-19 03:14:07 tests overflow of the year.
EXPECT_EQ(call_mktime(&tm_data, 2039, 0, 19, 3, 14, 7),
OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidMonths) {
struct tm tm_data;
// Before Jan of 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, -1, 15, 0, 0, 0),
OutOfRangeReturnValue);
// After Dec of 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, 12, 15, 0, 0, 0),
OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsInvalidDays) {
struct tm tm_data;
// -1 day of Jan, 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, 0, -1, 0, 0, 0), OutOfRangeReturnValue);
// 32 day of Jan, 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 32, 0, 0, 0), OutOfRangeReturnValue);
// 29 day of Feb, 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, 1, 29, 0, 0, 0), OutOfRangeReturnValue);
// 30 day of Feb, 1972
EXPECT_EQ(call_mktime(&tm_data, 1972, 1, 30, 0, 0, 0), OutOfRangeReturnValue);
// 31 day of Apr, 1970
EXPECT_EQ(call_mktime(&tm_data, 1970, 3, 31, 0, 0, 0), OutOfRangeReturnValue);
}
TEST(MkTime, MktimeTestsStartEpochYear) {
// Thu Jan 1 00:00:00 1970
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 0, 0, 0), static_cast<time_t>(0));
EXPECT_EQ(4, tm_data.tm_wday);
EXPECT_EQ(0, tm_data.tm_yday);
}
TEST(MkTime, MktimeTestsEpochYearRandomTime) {
// Thu Jan 1 12:50:50 1970
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 12, 50, 50),
static_cast<time_t>(46250));
EXPECT_EQ(4, tm_data.tm_wday);
EXPECT_EQ(0, tm_data.tm_yday);
}
TEST(MkTime, MktimeTestsEndOf32BitEpochYear) {
struct tm tm_data;
// Test for maximum value of a signed 32-bit integer.
// Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 7),
static_cast<time_t>(0x7FFFFFFF));
EXPECT_EQ(2, tm_data.tm_wday);
EXPECT_EQ(18, tm_data.tm_yday);
}
TEST(MkTime, MktimeTests64BitYear) {
if (sizeof(time_t) == 4)
return;
// Mon Jan 1 12:50:50 2170
struct tm tm_data;
EXPECT_EQ(call_mktime(&tm_data, 2170, 0, 1, 12, 50, 50),
static_cast<time_t>(6311479850));
EXPECT_EQ(1, tm_data.tm_wday);
EXPECT_EQ(0, tm_data.tm_yday);
// Test for Tue Jan 1 12:50:50 in 2,147,483,647th year.
EXPECT_EQ(call_mktime(&tm_data, 2147483647, 0, 1, 12, 50, 50),
static_cast<time_t>(67767976202043050));
EXPECT_EQ(2, tm_data.tm_wday);
EXPECT_EQ(0, tm_data.tm_yday);
}