diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -429,6 +429,8 @@ libc.src.time.gettimeofday libc.src.time.gmtime libc.src.time.gmtime_r + libc.src.time.localtime + libc.src.time.localtime_r libc.src.time.mktime libc.src.time.nanosleep libc.src.time.time 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 @@ -466,6 +466,8 @@ libc.src.time.gettimeofday libc.src.time.gmtime libc.src.time.gmtime_r + libc.src.time.localtime + libc.src.time.localtime_r libc.src.time.mktime libc.src.time.nanosleep libc.src.time.time diff --git a/libc/docs/date_and_time.rst b/libc/docs/date_and_time.rst --- a/libc/docs/date_and_time.rst +++ b/libc/docs/date_and_time.rst @@ -23,7 +23,7 @@ asctime |check| ctime gmtime |check| -localtime +localtime |check| strftime ============= ======= @@ -45,8 +45,8 @@ gettimeofday |check| gmtime |check| gmtime_r |check| -localtime -localtime_r +localtime |check| +localtime_r |check| mktime |check| nanosleep |check| strftime diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -1025,6 +1025,19 @@ ArgSpec, ] >, + FunctionSpec< + "localtime", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "localtime_r", + RetValSpec, + [ + ArgSpec, + ArgSpec, + ] + >, FunctionSpec< "mktime", 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 @@ -96,6 +96,28 @@ libc.include.time ) +add_entrypoint_object( + localtime + SRCS + localtime.cpp + HDRS + localtime.h + DEPENDS + .time_utils + libc.include.time +) + +add_entrypoint_object( + localtime_r + SRCS + localtime_r.cpp + HDRS + localtime_r.h + DEPENDS + .time_utils + libc.include.time +) + add_entrypoint_object( mktime SRCS diff --git a/libc/src/time/localtime.h b/libc/src/time/localtime.h new file mode 100644 --- /dev/null +++ b/libc/src/time/localtime.h @@ -0,0 +1,22 @@ +//===-- Implementation header of localtime ----------------------*- 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_LOCALTIME_H +#define LLVM_LIBC_SRC_TIME_LOCALTIME_H + +#include + +namespace __llvm_libc { + +struct tm *localtime(const time_t *timer); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_H + +#include "include/time.h" diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/localtime.cpp @@ -0,0 +1,22 @@ +//===-- Implementation of localtime 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/localtime.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(struct tm *, localtime, (const time_t *timer)) { + static struct tm tm_out; + return time_utils::localtime_internal(timer, &tm_out); +} + +} // namespace __llvm_libc diff --git a/libc/src/time/localtime_r.h b/libc/src/time/localtime_r.h new file mode 100644 --- /dev/null +++ b/libc/src/time/localtime_r.h @@ -0,0 +1,22 @@ +//===-- Implementation header of localtime_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_LOCALTIME_R_H +#define LLVM_LIBC_SRC_TIME_LOCALTIME_R_H + +#include + +namespace __llvm_libc { + +struct tm *localtime_r(const time_t *timer, struct tm *result); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_R_H + +#include "include/time.h" diff --git a/libc/src/time/localtime_r.cpp b/libc/src/time/localtime_r.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/localtime_r.cpp @@ -0,0 +1,22 @@ +//===-- Implementation of localtime_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/localtime_r.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(struct tm *, localtime_r, + (const time_t *timer, struct tm *result)) { + return time_utils::localtime_internal(timer, result); +} + +} // 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 @@ -10,11 +10,11 @@ #define LLVM_LIBC_SRC_TIME_TIME_UTILS_H #include // For size_t. +#include #include "include/errno.h" #include "src/errno/llvmlibc_errno.h" -#include "src/time/mktime.h" #include @@ -125,6 +125,20 @@ return result; } +static inline struct tm *localtime_internal(const time_t *timer, + struct tm *result) { + // Unlike most C Library functions, localtime doesn't just die on bad input. + // TODO(rtenneti); Handle leap seconds, timezone and update of tm_isdst. + int64_t seconds = *timer; + // Update the tm structure's year, month, day, etc. from seconds. + if (update_from_seconds(seconds, result) < 0) { + out_of_range(); + return nullptr; + } + + return result; +} + } // namespace time_utils } // namespace __llvm_libc 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 @@ -92,6 +92,30 @@ libc.src.time.gmtime_r ) +add_libc_unittest( + localtime + SUITE + libc_time_unittests + SRCS + localtime_test.cpp + HDRS + TmMatcher.h + DEPENDS + libc.src.time.localtime +) + +add_libc_unittest( + localtime_r + SUITE + libc_time_unittests + SRCS + localtime_r_test.cpp + HDRS + TmMatcher.h + DEPENDS + libc.src.time.localtime_r +) + add_libc_unittest( mktime SUITE diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/localtime_r_test.cpp @@ -0,0 +1,62 @@ +//===-- Unittests for localtime_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/localtime_r.h" +#include "src/time/time_utils.h" +#include "test/src/time/TmMatcher.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::time_utils::TimeConstants; + +// localtime_r shares the same code as gmtime_r and thus didn't repeat all the +// tests from gmtime. Added couple of validation tests (which are same as +// gmtime_r's). +// +// TODO(rtenneti): Add tests for timezone and update the following tests for +// timezone testing. + +TEST(LlvmLibcLocalTimeR, EndOf32BitEpochYear) { + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + time_t seconds = 0x7FFFFFFF; + struct tm tm_data; + struct tm *tm_data_ptr; + tm_data_ptr = __llvm_libc::localtime_r(&seconds, &tm_data); + EXPECT_TM_EQ((tm{7, // sec + 14, // min + 3, // hr + 19, // day + 0, // tm_mon starts with 0 for Jan + 2038 - TimeConstants::TIME_YEAR_BASE, // year + 2, // wday + 7, // yday + 0}), + *tm_data_ptr); + EXPECT_TM_EQ(*tm_data_ptr, tm_data); +} + +TEST(LlvmLibcLocalTimeR, Max64BitYear) { + if (sizeof(time_t) == 4) + return; + // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. + time_t seconds = 67767976202043050; + struct tm tm_data; + struct tm *tm_data_ptr; + tm_data_ptr = __llvm_libc::localtime_r(&seconds, &tm_data); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2147483647 - TimeConstants::TIME_YEAR_BASE, // year + 2, // wday + 50, // yday + 0}), + *tm_data_ptr); + EXPECT_TM_EQ(*tm_data_ptr, tm_data); +} diff --git a/libc/test/src/time/localtime_test.cpp b/libc/test/src/time/localtime_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/localtime_test.cpp @@ -0,0 +1,69 @@ +//===-- Unittests for localtime -------------------------------------------===// +// +// 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/localtime.h" +#include "src/time/time_utils.h" +#include "test/src/time/TmMatcher.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::time_utils::TimeConstants; + +// localtime shares the same code as gmtime and thus didn't repeat all the tests +// from gmtime. Added couple of validation tests (which are same as gmtime's). +// +// TODO(rtenneti): Add tests for timezone and update the following tests for +// timezone testing. + +TEST(LlvmLibcLocalTime, EndOf32BitEpochYear) { + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + time_t seconds = 0x7FFFFFFF; + struct tm *tm_data = __llvm_libc::localtime(&seconds); + EXPECT_TM_EQ((tm{7, // sec + 14, // min + 3, // hr + 19, // day + 0, // tm_mon starts with 0 for Jan + 2038 - TimeConstants::TIME_YEAR_BASE, // year + 2, // wday + 7, // yday + 0}), + *tm_data); +} + +TEST(LlvmLibcLocalTime, Max64BitYear) { + if (sizeof(time_t) == 4) + return; + // Mon Jan 1 12:50:50 2170 (200 years from 1970), + time_t seconds = 6311479850; + struct tm *tm_data = __llvm_libc::localtime(&seconds); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2170 - TimeConstants::TIME_YEAR_BASE, // year + 1, // wday + 50, // yday + 0}), + *tm_data); + + // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. + seconds = 67767976202043050; + tm_data = __llvm_libc::localtime(&seconds); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2147483647 - TimeConstants::TIME_YEAR_BASE, // year + 2, // wday + 50, // yday + 0}), + *tm_data); +}