[libc] implement template functions for localtime (#110363)

This is an implementation for template functions of localtime.

Update for this pull request: Implementation as been removed from this
pull request and will be added to a new one. This is because this pull
request is getting big. This pull request will only contain template
functions in order to implement localtime.

Update: The implementation is available in
https://github.com/zimirza/llvm-project/tree/localtime_implementation.

---------

Co-authored-by: Зишан Мирза <zmirza@tutanota.de>
Co-authored-by: Zishan Mirza <zmirza@posteo.de>
GitOrigin-RevId: 17bddd12245324311d10a681d606961914174c88
diff --git a/config/baremetal/aarch64/entrypoints.txt b/config/baremetal/aarch64/entrypoints.txt
index 782769e..04bf636 100644
--- a/config/baremetal/aarch64/entrypoints.txt
+++ b/config/baremetal/aarch64/entrypoints.txt
@@ -269,6 +269,8 @@
     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/config/linux/x86_64/entrypoints.txt b/config/linux/x86_64/entrypoints.txt
index 9a73e18..35425eb 100644
--- a/config/linux/x86_64/entrypoints.txt
+++ b/config/linux/x86_64/entrypoints.txt
@@ -1307,6 +1307,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.strftime
diff --git a/docs/headers/time.rst b/docs/headers/time.rst
index 9733a17..a30d489 100644
--- a/docs/headers/time.rst
+++ b/docs/headers/time.rst
@@ -87,9 +87,9 @@
 +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
 | 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 @@
 | timer_settime       |         |         |         |                 |         |         |         |         |         |         |         |         |         |
 +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
 | tzset               |         |         |         |                 |         |         |         |         |         |         |         |         |         |
-+---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
\ No newline at end of file
++---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
diff --git a/hdr/localtime_overlay.h b/hdr/localtime_overlay.h
new file mode 100644
index 0000000..8628277
--- /dev/null
+++ b/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 <unistd.h> 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 <localtime.h>
+#include <localtime_r.h>
+
+#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/include/time.yaml b/include/time.yaml
index 3b9d77c..2f80242 100644
--- a/include/time.yaml
+++ b/include/time.yaml
@@ -41,6 +41,19 @@
     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/src/time/CMakeLists.txt b/src/time/CMakeLists.txt
index 304b3f2..ec942e3 100644
--- a/src/time/CMakeLists.txt
+++ b/src/time/CMakeLists.txt
@@ -86,6 +86,30 @@
 )
 
 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
     difftime.cpp
diff --git a/src/time/baremetal/CMakeLists.txt b/src/time/baremetal/CMakeLists.txt
index 3072c8b..cbe9cf3 100644
--- a/src/time/baremetal/CMakeLists.txt
+++ b/src/time/baremetal/CMakeLists.txt
@@ -19,3 +19,29 @@
     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/src/time/baremetal/localtime.cpp b/src/time/baremetal/localtime.cpp
new file mode 100644
index 0000000..d39c273
--- /dev/null
+++ b/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/src/time/baremetal/localtime_r.cpp b/src/time/baremetal/localtime_r.cpp
new file mode 100644
index 0000000..3b57450
--- /dev/null
+++ b/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/src/time/localtime.cpp b/src/time/localtime.cpp
new file mode 100644
index 0000000..90a2961
--- /dev/null
+++ b/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/src/time/localtime.h b/src/time/localtime.h
new file mode 100644
index 0000000..663aa70
--- /dev/null
+++ b/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/src/time/localtime_r.cpp b/src/time/localtime_r.cpp
new file mode 100644
index 0000000..70bbbee
--- /dev/null
+++ b/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/src/time/localtime_r.h b/src/time/localtime_r.h
new file mode 100644
index 0000000..6fea402
--- /dev/null
+++ b/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/src/time/time_utils.h b/src/time/time_utils.h
index 84d412c..18dc287 100644
--- a/src/time/time_utils.h
+++ b/src/time/time_utils.h
@@ -93,11 +93,22 @@
   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/test/src/time/CMakeLists.txt b/test/src/time/CMakeLists.txt
index be7aa6f..66753b8 100644
--- a/test/src/time/CMakeLists.txt
+++ b/test/src/time/CMakeLists.txt
@@ -72,6 +72,29 @@
     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/test/src/time/localtime_r_test.cpp b/test/src/time/localtime_r_test.cpp
new file mode 100644
index 0000000..8f7a79e
--- /dev/null
+++ b/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/test/src/time/localtime_test.cpp b/test/src/time/localtime_test.cpp
new file mode 100644
index 0000000..144060c
--- /dev/null
+++ b/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);
+}