From e84118c6469a5f0e4fa103ca3619b062b37c1c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B8=D1=88=D0=B0=D0=BD=20=D0=9C=D0=B8=D1=80=D0=B7?= =?UTF-8?q?=D0=B0?= <149377404+zimirza@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:47:30 +0200 Subject: [PATCH] [libc][c11] implement ctime (#86567) --- libc/src/time/CMakeLists.txt | 11 ++ libc/src/time/ctime.cpp | 23 ++++ libc/src/time/ctime.h | 21 +++ libc/src/time/time_utils.h | 46 +++++++ libc/test/src/time/ctime_test.cpp | 215 ++++++++++++++++++++++++++++++ 5 files changed, 316 insertions(+) create mode 100644 libc/src/time/ctime.cpp create mode 100644 libc/src/time/ctime.h create mode 100644 libc/test/src/time/ctime_test.cpp diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt index 5680718715974e8..befe67677f3ec7d 100644 --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -36,6 +36,17 @@ add_entrypoint_object( libc.include.time ) +add_entrypoint_object( + ctime + SRCS + ctime.cpp + HDRS + ctime.h + DEPENDS + .time_utils + libc.include.time +) + add_entrypoint_object( difftime SRCS diff --git a/libc/src/time/ctime.cpp b/libc/src/time/ctime.cpp new file mode 100644 index 000000000000000..f3181816ad9ab15 --- /dev/null +++ b/libc/src/time/ctime.cpp @@ -0,0 +1,23 @@ +//===-- 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/ctime.h" +#include "src/__support/common.h" +#include "src/__support/macros/config.h" +#include "src/time/time_utils.h" + +namespace LIBC_NAMESPACE_DECL { + +using LIBC_NAMESPACE::time_utils::TimeConstants; + +LLVM_LIBC_FUNCTION(char *, ctime, (const struct tm *timeptr)) { + static char buffer[TimeConstants::CTIME_BUFFER_SIZE]; + return time_utils::ctime(timeptr, buffer, TimeConstants::CTIME_MAX_BYTES); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/ctime.h b/libc/src/time/ctime.h new file mode 100644 index 000000000000000..ec5530ffb5bc715 --- /dev/null +++ b/libc/src/time/ctime.h @@ -0,0 +1,21 @@ +//===-- Implementation header of ctime ------------------------*- 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_CTIME_H +#define LLVM_LIBC_SRC_TIME_CTIME_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +char *ctime(const struct tm *timeptr); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_TIME_CTIME_H diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h index 47f55f7d3891227..6fa590fefac8d02 100644 --- a/libc/src/time/time_utils.h +++ b/libc/src/time/time_utils.h @@ -62,6 +62,9 @@ struct TimeConstants { static constexpr int ASCTIME_BUFFER_SIZE = 256; static constexpr int ASCTIME_MAX_BYTES = 26; + static constexpr int CTIME_BUFFER_SIZE = 256; + static constexpr int CTIME_MAX_BYTES = 26; + /* 2000-03-01 (mod 400 year, immediately after feb29 */ static constexpr int64_t SECONDS_UNTIL2000_MARCH_FIRST = (946684800LL + SECONDS_PER_DAY * (31 + 29)); @@ -145,6 +148,49 @@ LIBC_INLINE char *asctime(const struct tm *timeptr, char *buffer, return buffer; } +LIBC_INLINE char *ctime(const struct tm *timeptr, char *buffer, + size_t bufferLength) { + if (timeptr == nullptr || buffer == nullptr) { + invalid_value(); + return nullptr; + } + if (timeptr->tm_wday < 0 || + timeptr->tm_wday > (TimeConstants::DAYS_PER_WEEK - 1)) { + invalid_value(); + return nullptr; + } + if (timeptr->tm_mon < 0 || + timeptr->tm_mon > (TimeConstants::MONTHS_PER_YEAR - 1)) { + invalid_value(); + return nullptr; + } + + // TODO(rtenneti): i18n the following strings. + static const char *week_days_name[TimeConstants::DAYS_PER_WEEK] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + + static const char *months_name[TimeConstants::MONTHS_PER_YEAR] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + // TODO(michaelr): look into removing this call to __builtin_snprintf that may + // be emitted as a call to snprintf. Alternatively, look into using our + // internal printf machinery. + int written_size = __builtin_snprintf( + buffer, bufferLength, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", + week_days_name[timeptr->tm_wday], months_name[timeptr->tm_mon], + timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, + TimeConstants::TIME_YEAR_BASE + timeptr->tm_year); + if (written_size < 0) + return nullptr; + if (static_cast(written_size) >= bufferLength) { + out_of_range(); + return nullptr; + } + return buffer; +} + + LIBC_INLINE struct tm *gmtime_internal(const time_t *timer, struct tm *result) { int64_t seconds = *timer; // Update the tm structure's year, month, day, etc. from seconds. diff --git a/libc/test/src/time/ctime_test.cpp b/libc/test/src/time/ctime_test.cpp new file mode 100644 index 000000000000000..bbf915188eacee7 --- /dev/null +++ b/libc/test/src/time/ctime_test.cpp @@ -0,0 +1,215 @@ +//===-- Unittests for ctime ---------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// make check-libc LIBC_TEST_TARGET=call_ctime VERBOSE=1 +#include "src/errno/libc_errno.h" +#include "src/time/ctime.h" +#include "test/UnitTest/Test.h" +#include "test/src/time/TmHelper.h" + +static inline char *call_ctime(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec, int wday, + int yday) { + LIBC_NAMESPACE::tmhelper::testing::initialize_tm_data( + tm_data, year, month, mday, hour, min, sec, wday, yday); + return LIBC_NAMESPACE::ctime(tm_data); +} + +TEST(LlvmLibcCtime, Nullptr) { + char *result; + result = LIBC_NAMESPACE::ctime(nullptr); + ASSERT_ERRNO_EQ(EINVAL); + ASSERT_STREQ(nullptr, result); +} + +// Weekdays are in the range 0 to 6. Test passing invalid value in wday. +TEST(LlvmLibcCtime, InvalidWday) { + struct tm tm_data; + + // Test with wday = -1. + call_ctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + -1, // wday + 0); // yday + ASSERT_ERRNO_EQ(EINVAL); + + // Test with wday = 7. + call_ctime(&tm_data, + 1970, // year + 1, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 7, // wday + 0); // yday + ASSERT_ERRNO_EQ(EINVAL); +} + +// Months are from January to December. Test passing invalid value in month. +TEST(LlvmLibcCtime, InvalidMonth) { + struct tm tm_data; + + // Test with month = 0. + call_ctime(&tm_data, + 1970, // year + 0, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_ERRNO_EQ(EINVAL); + + // Test with month = 13. + call_ctime(&tm_data, + 1970, // year + 13, // month + 1, // day + 0, // hr + 0, // min + 0, // sec + 4, // wday + 0); // yday + ASSERT_ERRNO_EQ(EINVAL); +} + +TEST(LlvmLibcCtime, ValidWeekdays) { + struct tm tm_data; + char *result; + // 1970-01-01 00:00:00. + result = call_ctime(&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_ctime(&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_ctime(&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(LlvmLibcCtime, ValidMonths) { + struct tm tm_data; + char *result; + // 1970-01-01 00:00:00. + result = call_ctime(&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_ctime(&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_ctime(&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(LlvmLibcCtime, 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_ctime(&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(LlvmLibcCtime, 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_ctime(&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_ctime(&tm_data, + 2147483647, // year + 1, // month + 1, // day + 12, // hr + 50, // min + 50, // sec + 2, // wday + 50); // yday + ASSERT_ERRNO_EQ(EOVERFLOW); + ASSERT_STREQ(nullptr, result); +}