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 @@ -249,6 +249,8 @@ ]; let Functions = [ + "asctime", + "asctime_r", "gmtime", "gmtime_r", "mktime", 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 @@ -181,6 +181,8 @@ libc.src.threads.thrd_join # time.h entrypoints + libc.src.time.asctime + libc.src.time.asctime_r libc.src.time.gmtime libc.src.time.gmtime_r libc.src.time.mktime diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -599,6 +599,19 @@ ], [], // Enumerations [ + FunctionSpec< + "asctime", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "asctime_r", + RetValSpec, + [ + ArgSpec, + ArgSpec, + ] + >, FunctionSpec< "gmtime", RetValSpec, diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -10,6 +10,28 @@ libc.src.errno.__errno_location ) +add_entrypoint_object( + asctime + SRCS + asctime.cpp + HDRS + asctime.h + DEPENDS + .time_utils + libc.include.time +) + +add_entrypoint_object( + asctime_r + SRCS + asctime_r.cpp + HDRS + asctime_r.h + DEPENDS + .time_utils + libc.include.time +) + add_entrypoint_object( gmtime SRCS diff --git a/libc/src/time/asctime.h b/libc/src/time/asctime.h new file mode 100644 --- /dev/null +++ b/libc/src/time/asctime.h @@ -0,0 +1,22 @@ +//===-- Implementation header of asctime ------------------------*- 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_ASCTIME_H +#define LLVM_LIBC_SRC_TIME_ASCTIME_H + +#include + +namespace __llvm_libc { + +char *asctime(const struct tm *timeptr); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_ASCTIME_H + +#include "include/time.h" diff --git a/libc/src/time/asctime.cpp b/libc/src/time/asctime.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/asctime.cpp @@ -0,0 +1,22 @@ +//===-- Implementation of asctime 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/asctime.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +namespace __llvm_libc { + +using __llvm_libc::time_utils::TimeConstants; + +LLVM_LIBC_FUNCTION(char *, asctime, (const struct tm *timeptr)) { + static char buffer[TimeConstants::AsctimeBufferSize]; + return time_utils::asctime(timeptr, buffer, TimeConstants::AsctimeMaxBytes); +} + +} // namespace __llvm_libc diff --git a/libc/src/time/asctime_r.h b/libc/src/time/asctime_r.h new file mode 100644 --- /dev/null +++ b/libc/src/time/asctime_r.h @@ -0,0 +1,22 @@ +//===-- Implementation header of asctime_r ----------------------*- 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_ASCTIME_R_H +#define LLVM_LIBC_SRC_TIME_ASCTIME_R_H + +#include + +namespace __llvm_libc { + +char *asctime_r(const struct tm *timeptr, char *buffer); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_ASCTIME_R_H + +#include "include/time.h" diff --git a/libc/src/time/asctime_r.cpp b/libc/src/time/asctime_r.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/asctime_r.cpp @@ -0,0 +1,22 @@ +//===-- Implementation of asctime_r 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/asctime_r.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +namespace __llvm_libc { + +using __llvm_libc::time_utils::TimeConstants; + +LLVM_LIBC_FUNCTION(char *, asctime_r, + (const struct tm *timeptr, char *buffer)) { + return time_utils::asctime(timeptr, buffer, TimeConstants::AsctimeMaxBytes); +} + +} // namespace __llvm_libc diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h --- a/libc/src/time/time_utils.h +++ b/libc/src/time/time_utils.h @@ -9,6 +9,8 @@ #ifndef LLVM_LIBC_SRC_TIME_TIME_UTILS_H #define LLVM_LIBC_SRC_TIME_TIME_UTILS_H +#include // For size_t. + #include "include/errno.h" #include "src/errno/llvmlibc_errno.h" @@ -33,6 +35,14 @@ static constexpr int NumberOfSecondsInLeapYear = (DaysPerNonLeapYear + 1) * SecondsPerDay; + // For asctime the behavior is undefined if struct tm's tm_wday or tm_mon are + // not within the normal ranges as defined in , or if struct tm's + // tm_year exceeds {INT_MAX}-1990, or if the below asctime_internal algorithm + // would attempt to generate more than 26 bytes of output (including the + // terminating null). + static constexpr int AsctimeBufferSize = 256; + static constexpr int AsctimeMaxBytes = 26; + /* 2000-03-01 (mod 400 year, immediately after feb29 */ static constexpr int64_t SecondsUntil2000MarchFirst = (946684800LL + SecondsPerDay * (31 + 29)); @@ -63,6 +73,46 @@ return static_cast(-1); } +static inline void InvalidValue() { llvmlibc_errno = EINVAL; } + +static inline char *asctime(const struct tm *timeptr, char *buffer, + size_t bufferLength) { + if (timeptr == nullptr || buffer == nullptr) { + InvalidValue(); + return nullptr; + } + if (timeptr->tm_wday < 0 || + timeptr->tm_wday > (TimeConstants::DaysPerWeek - 1)) { + InvalidValue(); + return nullptr; + } + if (timeptr->tm_mon < 0 || + timeptr->tm_mon > (TimeConstants::MonthsPerYear - 1)) { + InvalidValue(); + return nullptr; + } + + // TODO(rtenneti): i18n the following strings. + static const char *WeekDaysName[TimeConstants::DaysPerWeek] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + + static const char *MonthsName[TimeConstants::MonthsPerYear] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + int written_size = __builtin_snprintf( + buffer, bufferLength, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", + WeekDaysName[timeptr->tm_wday], MonthsName[timeptr->tm_mon], + timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, + TimeConstants::TimeYearBase + timeptr->tm_year); + if (written_size < 0) + return nullptr; + if (static_cast(written_size) >= bufferLength) { + OutOfRange(); + return nullptr; + } + return buffer; +} + static inline struct tm *gmtime_internal(const time_t *timer, struct tm *result) { int64_t seconds = *timer; diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -1,5 +1,31 @@ add_libc_testsuite(libc_time_unittests) +add_libc_unittest( + asctime + SUITE + libc_time_unittests + SRCS + asctime_test.cpp + HDRS + TmHelper.h + TmMatcher.h + DEPENDS + libc.src.time.asctime +) + +add_libc_unittest( + asctime_r + SUITE + libc_time_unittests + SRCS + asctime_r_test.cpp + HDRS + TmHelper.h + TmMatcher.h + DEPENDS + libc.src.time.asctime_r +) + add_libc_unittest( gmtime SUITE @@ -31,6 +57,7 @@ SRCS mktime_test.cpp HDRS + TmHelper.h TmMatcher.h DEPENDS libc.src.time.mktime diff --git a/libc/test/src/time/TmHelper.h b/libc/test/src/time/TmHelper.h new file mode 100644 --- /dev/null +++ b/libc/test/src/time/TmHelper.h @@ -0,0 +1,42 @@ +//===---- TmHelper.h ------------------------------------------*- 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_TEST_SRC_TIME_TM_HELPER_H +#define LLVM_LIBC_TEST_SRC_TIME_TM_HELPER_H + +#include + +#include "src/time/time_utils.h" + +using __llvm_libc::time_utils::TimeConstants; + +namespace __llvm_libc { +namespace tmhelper { +namespace testing { + +// A helper function to initialize tm data structure. +static inline void InitializeTmData(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec, + int wday, int yday) { + struct tm temp = {.tm_sec = sec, + .tm_min = min, + .tm_hour = hour, + .tm_mday = mday, + .tm_mon = month - 1, // tm_mon starts with 0 for Jan + // years since 1900 + .tm_year = year - TimeConstants::TimeYearBase, + .tm_wday = wday, + .tm_yday = yday}; + *tm_data = temp; +} + +} // namespace testing +} // namespace tmhelper +} // namespace __llvm_libc + +#endif // LLVM_LIBC_TEST_SRC_TIME_TM_HELPER_H diff --git a/libc/test/src/time/asctime_r_test.cpp b/libc/test/src/time/asctime_r_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/asctime_r_test.cpp @@ -0,0 +1,60 @@ +//===-- Unittests for asctime_r +//--------------------------------------------===// +// +// 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/asctime_r.h" +#include "src/time/time_utils.h" +#include "test/src/time/TmHelper.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::time_utils::TimeConstants; + +static inline char *call_asctime_r(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec, + int wday, int yday, char *buffer) { + __llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday, + hour, min, sec, wday, yday); + return __llvm_libc::asctime_r(tm_data, buffer); +} + +// asctime and asctime_r share the same code and thus didn't repeat all the +// tests from asctime. Added couple of validation tests. +TEST(LlvmLibcAsctimeR, Nullptr) { + char *result; + result = __llvm_libc::asctime_r(nullptr, nullptr); + ASSERT_EQ(EINVAL, llvmlibc_errno); + ASSERT_STREQ(nullptr, result); + + char buffer[TimeConstants::AsctimeBufferSize]; + result = __llvm_libc::asctime_r(nullptr, buffer); + ASSERT_EQ(EINVAL, llvmlibc_errno); + ASSERT_STREQ(nullptr, result); + + struct tm tm_data; + result = __llvm_libc::asctime_r(&tm_data, nullptr); + ASSERT_EQ(EINVAL, llvmlibc_errno); + ASSERT_STREQ(nullptr, result); +} + +TEST(LlvmLibcAsctimeR, ValidDate) { + char buffer[TimeConstants::AsctimeBufferSize]; + struct tm tm_data; + char *result; + // 1970-01-01 00:00:00. Test with a valid buffer size. + result = call_asctime_r(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0, // yday + buffer); + ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result); +} diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/asctime_test.cpp @@ -0,0 +1,215 @@ +//===-- Unittests for asctime ---------------------------------------------===// +// +// 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/asctime.h" +#include "test/src/time/TmHelper.h" +#include "utils/UnitTest/Test.h" + +static inline char *call_asctime(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec, int wday, + int yday) { + __llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday, + hour, min, sec, wday, yday); + return __llvm_libc::asctime(tm_data); +} + +TEST(LlvmLibcAsctime, Nullptr) { + char *result; + result = __llvm_libc::asctime(nullptr); + ASSERT_EQ(EINVAL, llvmlibc_errno); + ASSERT_STREQ(nullptr, result); +} + +// Weekdays are in the range 0 to 6. Test passing invalid value in wday. +TEST(LlvmLibcAsctime, InvalidWday) { + struct tm tm_data; + char *result; + + // Test with wday = -1. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + -1, // wday + 0); // yday + ASSERT_EQ(EINVAL, llvmlibc_errno); + + // Test with wday = 7. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 7, // wday + 0); // yday + ASSERT_EQ(EINVAL, llvmlibc_errno); +} + +// Months are from January to December. Test passing invalid value in month. +TEST(LlvmLibcAsctime, InvalidMonth) { + struct tm tm_data; + char *result; + + // Test with month = 0. + result = call_asctime(&tm_data, + 1970, // year + 0, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_EQ(EINVAL, llvmlibc_errno); + + // Test with month = 13. + result = call_asctime(&tm_data, + 1970, // year + 13, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_EQ(EINVAL, llvmlibc_errno); +} + +TEST(LlvmLibcAsctime, ValidWeekdays) { + struct tm tm_data; + char *result; + // 1970-01-01 00:00:00. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result); + + // 1970-01-03 00:00:00. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 3, // day + 0, // hr + 0, // min + 0, // sec + 6, // wday + 0); // yday + ASSERT_STREQ("Sat Jan 3 00:00:00 1970\n", result); + + // 1970-01-04 00:00:00. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 4, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0); // yday + ASSERT_STREQ("Sun Jan 4 00:00:00 1970\n", result); +} + +TEST(LlvmLibcAsctime, ValidMonths) { + struct tm tm_data; + char *result; + // 1970-01-01 00:00:00. + result = call_asctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result); + + // 1970-02-01 00:00:00. + result = call_asctime(&tm_data, + 1970, // year + 2, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 0, // wday + 0); // yday + ASSERT_STREQ("Sun Feb 1 00:00:00 1970\n", result); + + // 1970-12-31 23:59:59. + result = call_asctime(&tm_data, + 1970, // year + 12, // month + 31, // day + 23, // hr + 59, // min + 59, // sec + 4, // wday + 0); // yday + ASSERT_STREQ("Thu Dec 31 23:59:59 1970\n", result); +} + +TEST(LlvmLibcAsctime, EndOf32BitEpochYear) { + struct tm tm_data; + char *result; + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + result = call_asctime(&tm_data, + 2038, // year + 1, // month + 19, // day + 3, // hr + 14, // min + 7, // sec + 2, // wday + 7); // yday + ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result); +} + +TEST(LlvmLibcAsctime, Max64BitYear) { + if (sizeof(time_t) == 4) + return; + // Mon Jan 1 12:50:50 2170 (200 years from 1970), + struct tm tm_data; + char *result; + result = call_asctime(&tm_data, + 2170, // year + 1, // month + 1, // day + 12, // hr + 50, // min + 50, // sec + 1, // wday + 50); // yday + ASSERT_STREQ("Mon Jan 1 12:50:50 2170\n", result); + + // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. + // This test would cause buffer overflow and thus asctime returns nullptr. + result = call_asctime(&tm_data, + 2147483647, // year + 1, // month + 1, // day + 12, // hr + 50, // min + 50, // sec + 2, // wday + 50); // yday + ASSERT_EQ(EOVERFLOW, llvmlibc_errno); + ASSERT_STREQ(nullptr, result); +} diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp --- a/libc/test/src/time/mktime_test.cpp +++ b/libc/test/src/time/mktime_test.cpp @@ -9,6 +9,7 @@ #include "src/time/mktime.h" #include "src/time/time_utils.h" #include "test/ErrnoSetterMatcher.h" +#include "test/src/time/TmHelper.h" #include "test/src/time/TmMatcher.h" #include "utils/UnitTest/Test.h" @@ -20,33 +21,18 @@ using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; using __llvm_libc::time_utils::TimeConstants; -// 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, - int wday, int yday) { - struct tm temp = {.tm_sec = sec, - .tm_min = min, - .tm_hour = hour, - .tm_mday = mday, - .tm_mon = month - 1, // tm_mon starts with 0 for Jan - // years since 1900 - .tm_year = year - TimeConstants::TimeYearBase, - .tm_wday = wday, - .tm_yday = yday}; - *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, int wday, int yday) { - initialize_tm_data(tm_data, year, month, mday, hour, min, sec, wday, yday); + __llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday, + hour, min, sec, wday, yday); return __llvm_libc::mktime(tm_data); } TEST(LlvmLibcMkTime, FailureSetsErrno) { struct tm tm_data; - initialize_tm_data(&tm_data, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1, - 0, 0); + __llvm_libc::tmhelper::testing::InitializeTmData( + &tm_data, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1, 0, 0); EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW)); }