diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -20,6 +20,28 @@ }]; } +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 StdlibAPI : PublicAPI<"stdlib.h"> { } +def TimeAPI : PublicAPI<"time.h"> { + let TypeDeclarations = [ + StructTm, + TimeT, + ]; + + let Functions = [ + "mktime", + ]; +} + def ErrnoAPI : PublicAPI<"errno.h"> { let Macros = [ ErrnoMacro, diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -67,6 +67,9 @@ libc.src.threads.thrd_create libc.src.threads.thrd_join + # time.h entrypoints + libc.src.time.mktime + # unistd.h entrypoints libc.src.unistd.write ) diff --git a/libc/config/linux/x86_64/headers.txt b/libc/config/linux/x86_64/headers.txt --- a/libc/config/linux/x86_64/headers.txt +++ b/libc/config/linux/x86_64/headers.txt @@ -9,5 +9,6 @@ libc.include.sys_mman libc.include.sys_syscall libc.include.threads + libc.include.time libc.include.unistd ) diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -50,6 +50,14 @@ .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 diff --git a/libc/include/time.h.def b/libc/include/time.h.def new file mode 100644 --- /dev/null +++ b/libc/include/time.h.def @@ -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 diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -79,6 +79,8 @@ def SigHandlerT : NamedType<"__sighandler_t">; +def TimeTType : NamedType<"time_t">; + //added because __assert_fail needs it. def UnsignedType : NamedType<"unsigned">; diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -3,6 +3,8 @@ NamedType FILE = NamedType<"FILE">; PtrType FILEPtr = PtrType; RestrictedPtrType FILERestrictedPtr = RestrictedPtrType; + NamedType StructTmType = NamedType<"struct tm">; + PtrType StructTmPtr = PtrType; HeaderSpec Assert = HeaderSpec< "assert.h", @@ -463,6 +465,23 @@ ] >; + HeaderSpec Time = HeaderSpec< + "time.h", + [], // Macros + [ // Types + StructTmType, + TimeTType, + ], + [], // Enumerations + [ + FunctionSpec< + "mktime", + RetValSpec, + [ArgSpec] + >, + ] + >; + let Headers = [ Assert, CType, @@ -473,5 +492,6 @@ StdLib, Signal, Threads, + Time, ]; } diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -9,6 +9,7 @@ # TODO: Add this target conditional to the target OS. add_subdirectory(sys) add_subdirectory(threads) +add_subdirectory(time) add_subdirectory(unistd) add_subdirectory(__support) diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/time/CMakeLists.txt @@ -0,0 +1,9 @@ +add_entrypoint_object( + mktime + SRCS + mktime.cpp + HDRS + mktime.h + DEPENDS + libc.include.time +) diff --git a/libc/src/time/mktime.h b/libc/src/time/mktime.h new file mode 100644 --- /dev/null +++ b/libc/src/time/mktime.h @@ -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 + +namespace __llvm_libc { + +time_t mktime(struct tm *t1); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_MKTIME_H + +#include "include/time.h" diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/mktime.cpp @@ -0,0 +1,114 @@ +//===-- 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 "src/time/mktime.h" +#include "src/__support/common.h" + +namespace __llvm_libc { + +namespace { +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 LeapYearDaysInMonth[] = {31 /* Jan */, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + +constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + +constexpr int LeapYearDaysByMonth[] = {0 /* Jan */, 31, 60, 91, 121, 152, + 182, 213, 244, 274, 305, 335}; + +constexpr int NonLeapYearDaysByMonth[] = {0 /* Jan */, 31, 59, 90, 120, 151, + 181, 212, 243, 273, 304, 334}; + +constexpr bool isLeapYear(const time_t year) { + return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); +} +} // namespace + +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. + if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1)) + return (time_t)(-1); + if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1)) + return (time_t)(-1); + if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1)) + return (time_t)(-1); + time_t tmYearFromBase = t1->tm_year + TimeYearBase; + + if (tmYearFromBase < EpochYear) + return (time_t)(-1); + + // 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 (time_t)(-1); + if (t1->tm_mon > 0) + return (time_t)(-1); + if (t1->tm_mday > 19) + return (time_t)(-1); + if (t1->tm_hour > 3) + return (time_t)(-1); + if (t1->tm_min > 14) + return (time_t)(-1); + if (t1->tm_sec > 7) + return (time_t)(-1); + } + + static_assert(sizeof(int) == 4, + "ILP64 is uimplemented. Ths implementation requires 32-bit " + "integers."); + + if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1)) + return (time_t)(-1); + bool tmYearIsLeap = isLeapYear(tmYearFromBase); + time_t daysInMonth = tmYearIsLeap ? LeapYearDaysInMonth[t1->tm_mon] + : NonLeapYearDaysInMonth[t1->tm_mon]; + if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth) + return (time_t)(-1); + + time_t totalDays = t1->tm_mday - 1; + totalDays += (tmYearIsLeap ? LeapYearDaysByMonth[t1->tm_mon] + : NonLeapYearDaysByMonth[t1->tm_mon]); + 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 + return t1->tm_sec + t1->tm_min * SecondsPerMin + + t1->tm_hour * MinutesPerHour * SecondsPerMin + + totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -8,6 +8,7 @@ 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) diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/time/CMakeLists.txt @@ -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 +) diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/mktime_test.cpp @@ -0,0 +1,204 @@ +//===-- 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 "utils/UnitTest/Test.h" + +#include + +// A helper function to initialize tm data structure. +void initialize_tm_data(struct tm *tm_data, time_t year, time_t month, + time_t mday, time_t hour, time_t min, time_t sec) { + memset(tm_data, 0, sizeof(struct tm)); + tm_data->tm_sec = sec; // seconds after the minute + tm_data->tm_min = min; // minutes after the hour + tm_data->tm_hour = hour; // hours since midnight + tm_data->tm_mday = mday; // day of the month + tm_data->tm_mon = month; // months since January + tm_data->tm_year = year - 1900; // years since 1900 + tm_data->tm_wday = 0; // days since Sunday + tm_data->tm_yday = 0; // days since January +} + +TEST(MkTime, MktimeTestsInvalidSeconds) { + struct tm temp; + initialize_tm_data(&temp, 0, 0, 0, 0, 0, -1); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 0, 0, 0, 0, 0, 60); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidMinutes) { + struct tm temp; + initialize_tm_data(&temp, 0, 0, 0, 0, -1, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 0, 0, 1, 0, 60, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidHours) { + struct tm temp; + initialize_tm_data(&temp, 0, 0, 0, -1, 0, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 0, 0, 0, 24, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidYear) { + struct tm temp; + initialize_tm_data(&temp, 1969, 0, 0, 0, 0, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidEndOf32BitEpochYear) { + if (sizeof(time_t) != 4) + return; + struct tm temp; + initialize_tm_data(&temp, 2038, 0, 19, 3, 14, 8); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 2038, 0, 19, 3, 15, 7); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 2038, 0, 19, 4, 14, 7); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 2038, 0, 20, 3, 14, 7); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 2038, 1, 19, 3, 14, 7); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 2039, 0, 19, 3, 14, 7); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidMonths) { + struct tm temp; + initialize_tm_data(&temp, 1970, -1, 0, 0, 0, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 1970, 12, 0, 0, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsInvalidDays) { + struct tm temp; + initialize_tm_data(&temp, 1970, 0, -1, 0, 0, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 1970, 0, 32, 0, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 1970, 1, 29, 0, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 1972, 1, 30, 0, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); + + initialize_tm_data(&temp, 1970, 3, 31, 0, 0, 0); + secs = __llvm_libc::mktime(&temp); + result = -1; + EXPECT_EQ(secs, result); +} + +TEST(MkTime, MktimeTestsStartEpochYear) { + struct tm temp; + // Thu Jan 1 00:00:00 1970 + initialize_tm_data(&temp, 1970, 0, 1, 0, 0, 0); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = 0; + EXPECT_EQ(secs, result); + EXPECT_EQ(4, temp.tm_wday); + EXPECT_EQ(0, temp.tm_yday); +} + +TEST(MkTime, MktimeTestsEpochYearRandomTime) { + struct tm temp; + // Thu Jan 1 12:50:50 1970 + initialize_tm_data(&temp, 1970, 0, 1, 12, 50, 50); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = 46250; + EXPECT_EQ(secs, result); + EXPECT_EQ(4, temp.tm_wday); + EXPECT_EQ(0, temp.tm_yday); +} + +TEST(MkTime, MktimeTestsEndOf32BitEpochYear) { + struct tm temp; + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for 03:14:07 UTC on 19 January 2038. + initialize_tm_data(&temp, 2038, 0, 19, 3, 14, 7); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = 0x7FFFFFFF; + EXPECT_EQ(secs, result); + EXPECT_EQ(2, temp.tm_wday); + EXPECT_EQ(18, temp.tm_yday); +} + +TEST(MkTime, MktimeTests64BitYear) { + if (sizeof(time_t) == 4) + return; + struct tm temp; + // Thu Jan 1 12:50:50 2170 + initialize_tm_data(&temp, 2170, 0, 1, 12, 50, 50); + time_t secs = __llvm_libc::mktime(&temp); + time_t result = 6311479850; + EXPECT_EQ(secs, result); + EXPECT_EQ(1, temp.tm_wday); + EXPECT_EQ(0, temp.tm_yday); + + // Test for 2,147,483,647 year. + initialize_tm_data(&temp, (time_t)2147483647, 0, 1, 12, 50, 50); + secs = __llvm_libc::mktime(&temp); + result = 67767976202043050; + EXPECT_EQ(secs, result); + EXPECT_EQ(2, temp.tm_wday); + EXPECT_EQ(0, temp.tm_yday); +}