diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt index 637be4f19d5b7..f8cb2a3efa424 100644 --- a/libc/config/baremetal/aarch64/entrypoints.txt +++ b/libc/config/baremetal/aarch64/entrypoints.txt @@ -269,6 +269,8 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.time.difftime 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.strftime libc.src.time.strftime_l diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index c64db2cc3548f..27af97bbf0bdd 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -1304,6 +1304,8 @@ if(LLVM_LIBC_FULL_BUILD) 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.strftime diff --git a/libc/docs/headers/time.rst b/libc/docs/headers/time.rst index 9733a176fbb25..a30d489661b12 100644 --- a/libc/docs/headers/time.rst +++ b/libc/docs/headers/time.rst @@ -87,9 +87,9 @@ Implementation Status +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ | gmtime_r | |check| | |check| | | |check| | | | | | | | | | | +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| localtime | | | | | | | | | | | | | | +| localtime | |check| | |check| | | |check| | | | | | | | | | | +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| localtime_r | | | | | | | | | | | | | | +| localtime_r | |check| | |check| | | |check| | | | | | | | | | | +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ | mktime | |check| | |check| | | |check| | | | | | | | | | | +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ @@ -112,4 +112,4 @@ Implementation Status | timer_settime | | | | | | | | | | | | | | +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ | tzset | | | | | | | | | | | | | | -+---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ \ No newline at end of file ++---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ diff --git a/libc/hdr/localtime_overlay.h b/libc/hdr/localtime_overlay.h new file mode 100644 index 0000000000000..86282770ae170 --- /dev/null +++ b/libc/hdr/localtime_overlay.h @@ -0,0 +1,45 @@ +//===-- Including localtime.h in overlay mode -----------------------------===// +// +// 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_HDR_LOCALTIME_OVERLAY_H +#define LLVM_LIBC_HDR_LOCALTIME_OVERLAY_H + +#ifdef LIBC_FULL_BUILD +#error "This header should only be included in overlay mode" +#endif + +// Overlay mode + +// glibc header might provide extern inline definitions for few +// functions, causing external alias errors. They are guarded by +// `__USE_EXTERN_INLINES` macro. + +#ifdef __USE_EXTERN_INLINES +#define LIBC_OLD_USE_EXTERN_INLINES +#undef __USE_EXTERN_INLINES +#endif + +#ifndef __NO_INLINE__ +#define __NO_INLINE__ 1 +#define LIBC_SET_NO_INLINE +#endif + +#include +#include + +#ifdef LIBC_SET_NO_INLINE +#undef __NO_INLINE__ +#undef LIBC_SET_NO_INLINE +#endif + +#ifdef LIBC_OLD_USE_EXTERN_INLINES +#define __USE_EXTERN_INLINES +#undef LIBC_OLD_USE_EXTERN_INLINES +#endif + +#endif // LLVM_LIBC_HDR_LOCALTIME_OVERLAY_H diff --git a/libc/include/time.yaml b/libc/include/time.yaml index 3b9d77c0aaae2..2f8024298fad1 100644 --- a/libc/include/time.yaml +++ b/libc/include/time.yaml @@ -41,6 +41,19 @@ functions: arguments: - type: const time_t * - type: char * + - name: localtime + standard: + - stdc + return_type: struct tm * + arguments: + - type: const time_t * + - name: localtime_r + standard: + - stdc + return_type: struct tm * + arguments: + - type: const time_t * + - type: struct tm * - name: clock standard: - stdc diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt index 304b3f247a508..ec942e38d1af5 100644 --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -85,6 +85,30 @@ add_entrypoint_object( libc.include.time ) +add_entrypoint_object( + localtime + SRCS + localtime.cpp + HDRS + localtime.h + DEPENDS + .time_utils + libc.hdr.types.time_t + libc.hdr.types.struct_tm +) + +add_entrypoint_object( + localtime_r + SRCS + localtime_r.cpp + HDRS + localtime_r.h + DEPENDS + .time_utils + libc.hdr.types.time_t + libc.hdr.types.struct_tm +) + add_entrypoint_object( difftime SRCS diff --git a/libc/src/time/baremetal/CMakeLists.txt b/libc/src/time/baremetal/CMakeLists.txt index 3072c8b14959d..cbe9cf3db3e21 100644 --- a/libc/src/time/baremetal/CMakeLists.txt +++ b/libc/src/time/baremetal/CMakeLists.txt @@ -19,3 +19,29 @@ add_entrypoint_object( libc.hdr.time_macros libc.hdr.types.struct_timespec ) + +add_entrypoint_object( + localtime + SRCS + localtime.cpp + HDRS + ../localtime.h + time_utils.h + DEPENDS + .time_utils + libc.hdr.types.struct_tm + libc.hdr.types.time_t +) + +add_entrypoint_object( + localtime_r + SRCS + localtime_r.cpp + HDRS + ../localtime.h + time_utils.h + DEPENDS + .time_utils + libc.hdr.types.struct_tm + libc.hdr.types.time_t +) diff --git a/libc/src/time/baremetal/localtime.cpp b/libc/src/time/baremetal/localtime.cpp new file mode 100644 index 0000000000000..d39c2738e81a5 --- /dev/null +++ b/libc/src/time/baremetal/localtime.cpp @@ -0,0 +1,22 @@ +//===-- Implementation of localtime for baremetal -------------------------===// +// +// 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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/time/time_utils.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(struct tm *, localtime, (time_t *timer)) { + static struct tm tm_out; + + return time_utils::localtime_internal(timer, &tm_out); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/baremetal/localtime_r.cpp b/libc/src/time/baremetal/localtime_r.cpp new file mode 100644 index 0000000000000..3b57450ca4499 --- /dev/null +++ b/libc/src/time/baremetal/localtime_r.cpp @@ -0,0 +1,24 @@ +//===-- Implementation of localtime_r for baremetal -----------------------===// +// +// 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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/__support/macros/null_check.h" +#include "src/time/time_utils.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(struct tm *, localtime_r, + (const time_t *timer, struct tm *buf)) { + LIBC_CRASH_ON_NULLPTR(timer); + + return time_utils::localtime_internal(timer, buf); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp new file mode 100644 index 0000000000000..90a296178bae8 --- /dev/null +++ b/libc/src/time/localtime.cpp @@ -0,0 +1,24 @@ +//===-- Linux implementation of the 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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/__support/macros/null_check.h" +#include "src/time/time_utils.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(struct tm *, localtime, (const time_t *timer)) { + LIBC_CRASH_ON_NULLPTR(timer); + + static struct tm tm_out; + return time_utils::localtime_internal(timer, &tm_out); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/localtime.h b/libc/src/time/localtime.h new file mode 100644 index 0000000000000..663aa70a1321b --- /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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/__support/common.h" + +namespace LIBC_NAMESPACE_DECL { + +struct tm *localtime(const time_t *timer); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_H diff --git a/libc/src/time/localtime_r.cpp b/libc/src/time/localtime_r.cpp new file mode 100644 index 0000000000000..70bbbeed271ea --- /dev/null +++ b/libc/src/time/localtime_r.cpp @@ -0,0 +1,25 @@ +//===-- Linux 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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/__support/macros/null_check.h" +#include "src/time/time_utils.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(struct tm *, localtime_r, + (const time_t *timer, struct tm *buf)) { + LIBC_CRASH_ON_NULLPTR(timer); + LIBC_CRASH_ON_NULLPTR(buf); + + return time_utils::localtime_internal(timer, buf); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/localtime_r.h b/libc/src/time/localtime_r.h new file mode 100644 index 0000000000000..6fea40222a4fc --- /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 "hdr/types/struct_tm.h" +#include "hdr/types/time_t.h" +#include "src/__support/common.h" + +namespace LIBC_NAMESPACE_DECL { + +struct tm *localtime_r(const time_t *timer, struct tm *buf); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_R_H diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h index 84d412c1e846a..18dc28760594c 100644 --- a/libc/src/time/time_utils.h +++ b/libc/src/time/time_utils.h @@ -93,11 +93,22 @@ LIBC_INLINE tm *gmtime_internal(const time_t *timer, tm *result) { return result; } -// TODO: localtime is not yet implemented and a temporary solution is to -// use gmtime, https://github.com/llvm/llvm-project/issues/107597 +LIBC_INLINE tm *localtime_internal(const time_t *timer, tm *result) { + time_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; + } + + // TODO(zimirza): implement timezone database + + return result; +} + LIBC_INLINE tm *localtime(const time_t *t_ptr) { static tm result; - return time_utils::gmtime_internal(t_ptr, &result); + return time_utils::localtime_internal(t_ptr, &result); } // Returns number of years from (1, year). diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt index be7aa6f0f058a..66753b84f2328 100644 --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -72,6 +72,29 @@ add_libc_unittest( libc.hdr.types.struct_tm ) +add_libc_unittest( + localtime_test + SUITE + libc_time_unittests + SRCS + localtime_test.cpp + DEPENDS + libc.hdr.types.time_t + libc.src.time.localtime +) + +add_libc_unittest( + localtime_r_test + SUITE + libc_time_unittests + SRCS + localtime_r_test.cpp + DEPENDS + libc.hdr.types.struct_tm + libc.hdr.types.time_t + libc.src.time.localtime_r +) + add_libc_test( clock_gettime_test 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 index 0000000000000..8f7a79ef264a6 --- /dev/null +++ b/libc/test/src/time/localtime_r_test.cpp @@ -0,0 +1,93 @@ +//===-- 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 "test/UnitTest/Test.h" + +TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) { + struct tm input = {.tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 0, + .tm_mon = 0, + .tm_year = 0, + .tm_wday = 0, + .tm_yday = 0, + .tm_isdst = 0}; + const time_t timer = 0; + + struct tm *result = LIBC_NAMESPACE::localtime_r(&timer, &input); + + ASSERT_EQ(70, result->tm_year); + ASSERT_EQ(0, result->tm_mon); + ASSERT_EQ(1, result->tm_mday); + ASSERT_EQ(0, result->tm_hour); + ASSERT_EQ(0, result->tm_min); + ASSERT_EQ(0, result->tm_sec); + ASSERT_EQ(4, result->tm_wday); + ASSERT_EQ(0, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +} + +TEST(LlvmLibcLocaltime, NullPtr) { + EXPECT_DEATH([] { LIBC_NAMESPACE::localtime_r(nullptr, nullptr); }, + WITH_SIGNAL(4)); +} + +// TODO(zimirza): These tests does not expect the correct output of localtime as +// per specification. This is due to timezone functions removed from +// https://github.com/llvm/llvm-project/pull/110363. +// This will be resolved a new pull request. + +TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp) { + struct tm input = {.tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 0, + .tm_mon = 0, + .tm_year = 0, + .tm_wday = 0, + .tm_yday = 0, + .tm_isdst = 0}; + const time_t timer = 1756595338; + struct tm *result = LIBC_NAMESPACE::localtime_r(&timer, &input); + + ASSERT_EQ(125, result->tm_year); + ASSERT_EQ(7, result->tm_mon); + ASSERT_EQ(30, result->tm_mday); + ASSERT_EQ(23, result->tm_hour); + ASSERT_EQ(8, result->tm_min); + ASSERT_EQ(58, result->tm_sec); + ASSERT_EQ(6, result->tm_wday); + ASSERT_EQ(241, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +} + +TEST(LlvmLibcLocaltimeR, ValidUnixTimestampNegative) { + struct tm input = {.tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 0, + .tm_mon = 0, + .tm_year = 0, + .tm_wday = 0, + .tm_yday = 0, + .tm_isdst = 0}; + const time_t timer = -1756595338; + struct tm *result = LIBC_NAMESPACE::localtime_r(&timer, &input); + + ASSERT_EQ(14, result->tm_year); + ASSERT_EQ(4, result->tm_mon); + ASSERT_EQ(4, result->tm_mday); + ASSERT_EQ(0, result->tm_hour); + ASSERT_EQ(51, result->tm_min); + ASSERT_EQ(2, result->tm_sec); + ASSERT_EQ(1, result->tm_wday); + ASSERT_EQ(123, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +} diff --git a/libc/test/src/time/localtime_test.cpp b/libc/test/src/time/localtime_test.cpp new file mode 100644 index 0000000000000..144060c86cfc2 --- /dev/null +++ b/libc/test/src/time/localtime_test.cpp @@ -0,0 +1,64 @@ +//===-- 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 "test/UnitTest/Test.h" + +TEST(LlvmLibcLocaltime, ValidUnixTimestamp0) { + const time_t timer = 0; + struct tm *result = LIBC_NAMESPACE::localtime(&timer); + + ASSERT_EQ(70, result->tm_year); + ASSERT_EQ(0, result->tm_mon); + ASSERT_EQ(1, result->tm_mday); + ASSERT_EQ(0, result->tm_hour); + ASSERT_EQ(0, result->tm_min); + ASSERT_EQ(0, result->tm_sec); + ASSERT_EQ(4, result->tm_wday); + ASSERT_EQ(0, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +} + +TEST(LlvmLibcLocaltime, NullPtr) { + EXPECT_DEATH([] { LIBC_NAMESPACE::localtime(nullptr); }, WITH_SIGNAL(4)); +} + +// TODO(zimirza): These tests does not expect the correct output of localtime as +// per specification. This is due to timezone functions removed from +// https://github.com/llvm/llvm-project/pull/110363. +// This will be resolved a new pull request. + +TEST(LlvmLibcLocaltime, ValidUnixTimestamp) { + const time_t timer = 1756595338; + struct tm *result = LIBC_NAMESPACE::localtime(&timer); + + ASSERT_EQ(125, result->tm_year); + ASSERT_EQ(7, result->tm_mon); + ASSERT_EQ(30, result->tm_mday); + ASSERT_EQ(23, result->tm_hour); + ASSERT_EQ(8, result->tm_min); + ASSERT_EQ(58, result->tm_sec); + ASSERT_EQ(6, result->tm_wday); + ASSERT_EQ(241, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +} + +TEST(LlvmLibcLocaltime, ValidUnixTimestampNegative) { + const time_t timer = -1756595338; + struct tm *result = LIBC_NAMESPACE::localtime(&timer); + + ASSERT_EQ(14, result->tm_year); + ASSERT_EQ(4, result->tm_mon); + ASSERT_EQ(4, result->tm_mday); + ASSERT_EQ(0, result->tm_hour); + ASSERT_EQ(51, result->tm_min); + ASSERT_EQ(2, result->tm_sec); + ASSERT_EQ(1, result->tm_wday); + ASSERT_EQ(123, result->tm_yday); + ASSERT_EQ(0, result->tm_isdst); +}