[libc-commits] [libc] [libc] implement localtime (PR #110363)

Зишан Мирза via libc-commits libc-commits at lists.llvm.org
Sun Oct 6 10:42:14 PDT 2024


https://github.com/zimirza updated https://github.com/llvm/llvm-project/pull/110363

>From 41b22383d62bccc5621435c6b117f7446b0cac99 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 15:50:29 +0200
Subject: [PATCH 01/12] [libc] implement localtime

This is an implementation of localtime.

Closes #107597 and #109892
---
 libc/config/baremetal/arm/entrypoints.txt   |  2 +
 libc/config/baremetal/riscv/entrypoints.txt |  2 +
 libc/config/linux/aarch64/entrypoints.txt   |  2 +
 libc/config/linux/riscv/entrypoints.txt     |  2 +
 libc/config/linux/x86_64/entrypoints.txt    |  2 +
 libc/newhdrgen/yaml/time.yaml               | 13 +++
 libc/spec/stdc.td                           | 13 +++
 libc/src/time/CMakeLists.txt                | 24 ++++++
 libc/src/time/localtime.cpp                 | 25 ++++++
 libc/src/time/localtime.h                   | 21 +++++
 libc/src/time/localtime_r.cpp               | 25 ++++++
 libc/src/time/localtime_r.h                 | 21 +++++
 libc/src/time/time_utils.cpp                | 45 ++++++++++-
 libc/src/time/time_utils.h                  | 35 ++++++++-
 libc/test/src/time/CMakeLists.txt           | 36 +++++++++
 libc/test/src/time/ctime_r_test.cpp         |  4 +-
 libc/test/src/time/ctime_test.cpp           |  4 +-
 libc/test/src/time/localtime_r_test.cpp     | 87 +++++++++++++++++++++
 libc/test/src/time/localtime_test.cpp       | 54 +++++++++++++
 19 files changed, 408 insertions(+), 9 deletions(-)
 create mode 100644 libc/src/time/localtime.cpp
 create mode 100644 libc/src/time/localtime.h
 create mode 100644 libc/src/time/localtime_r.cpp
 create mode 100644 libc/src/time/localtime_r.h
 create mode 100644 libc/test/src/time/localtime_r_test.cpp
 create mode 100644 libc/test/src/time/localtime_test.cpp

diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index 68030f7f1775b5..7577fad2fde869 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -205,6 +205,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.time.asctime_r
     libc.src.time.ctime
     libc.src.time.ctime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.difftime
     libc.src.time.gmtime
     libc.src.time.gmtime_r
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index 5894b591072ef0..b93aa334296ffb 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -201,6 +201,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.time.asctime_r
     libc.src.time.ctime
     libc.src.time.ctime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.difftime
     libc.src.time.gmtime
     libc.src.time.gmtime_r
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 64fbe1a250c0ba..d287bb0510a1fb 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -950,6 +950,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.asctime_r
     libc.src.time.ctime
     libc.src.time.ctime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index ff3d821c664c5b..40a417bcf09b49 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -885,6 +885,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.asctime_r
     libc.src.time.ctime
     libc.src.time.ctime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index dd658af3bfb674..3b63ce82417f4b 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1005,6 +1005,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.asctime_r
     libc.src.time.ctime
     libc.src.time.ctime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/newhdrgen/yaml/time.yaml b/libc/newhdrgen/yaml/time.yaml
index 69b40bef3160dd..7198c19fce2bf0 100644
--- a/libc/newhdrgen/yaml/time.yaml
+++ b/libc/newhdrgen/yaml/time.yaml
@@ -37,6 +37,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/spec/stdc.td b/libc/spec/stdc.td
index 7caf543748151a..9fd8d175f36be3 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -1615,6 +1615,19 @@ def StdC : StandardSpec<"stdc"> {
                   ArgSpec<CharPtr>,
               ]
           >,
+          FunctionSpec<
+              "localtime",
+              RetValSpec<StructTmPtr>,
+              [ArgSpec<TimeTTypePtr>]
+          >,
+          FunctionSpec<
+              "localtime_r",
+              RetValSpec<StructTmPtr>,
+              [
+                  ArgSpec<TimeTTypePtr>,
+                  ArgSpec<StructTmPtr>,
+              ]
+          >,
           FunctionSpec<
               "clock",
               RetValSpec<ClockT>,
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index b3318e7ca87fa5..de2e128dd310c4 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -60,6 +60,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.include.time
+)
+
+add_entrypoint_object(
+  localtime_r
+  SRCS
+    localtime_r.cpp
+  HDRS
+    localtime_r.h
+  DEPENDS
+    .time_utils
+    libc.hdr.types.time_t
+    libc.include.time
+)
+
 add_entrypoint_object(
   difftime
   SRCS
diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp
new file mode 100644
index 00000000000000..c08af2d8b26b78
--- /dev/null
+++ b/libc/src/time/localtime.cpp
@@ -0,0 +1,25 @@
+//===-- 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/CPP/limits.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct tm *, localtime, (const time_t *t_ptr)) {
+  if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
+      return nullptr;
+  }
+
+  return time_utils::localtime(t_ptr);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/localtime.h b/libc/src/time/localtime.h
new file mode 100644
index 00000000000000..648c8b755ddee9
--- /dev/null
+++ b/libc/src/time/localtime.h
@@ -0,0 +1,21 @@
+//===-- 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 "src/__support/macros/config.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct tm *localtime(const time_t *t_ptr);
+
+} // 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 00000000000000..6ff3fb3d4faa8c
--- /dev/null
+++ b/libc/src/time/localtime_r.cpp
@@ -0,0 +1,25 @@
+//===-- 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/CPP/limits.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct tm *, localtime_r, (const time_t *t_ptr, struct tm *tm)) {
+  if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
+      return nullptr;
+  }
+
+  return time_utils::localtime_internal(t_ptr, tm);
+}
+
+} // 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 00000000000000..d98b52180fa789
--- /dev/null
+++ b/libc/src/time/localtime_r.h
@@ -0,0 +1,21 @@
+//===-- 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 "src/__support/macros/config.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct tm *localtime_r(const time_t *t_ptr, struct tm *tm);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_R_H
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 509cad8146df87..d00cc139471c8f 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <stdio.h>
 #include "src/time/time_utils.h"
 #include "src/__support/CPP/limits.h" // INT_MIN, INT_MAX
 #include "src/__support/common.h"
@@ -129,6 +130,25 @@ int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
   if (years > INT_MAX || years < INT_MIN)
     return time_utils::out_of_range();
 
+  FILE *fp;
+  fp = fopen("/etc/timezone", "rb");
+  if (fp == NULL) {
+    return time_utils::out_of_range();
+  }
+
+  char timezone[128];
+  if (fgets(timezone, sizeof(timezone), fp) == NULL) {
+    return time_utils::out_of_range();
+  }
+
+  int offset;
+  if (internal::same_string(timezone, "UTC") == 0) {
+      offset = 0;
+  }
+  if (internal::same_string(timezone, "Europe/Berlin") == 0) {
+      offset = 2;
+  }
+
   // All the data (years, month and remaining days) was calculated from
   // March, 2000. Thus adjust the data to be from January, 1900.
   tm->tm_year = static_cast<int>(years + 2000 - TimeConstants::TIME_YEAR_BASE);
@@ -144,11 +164,32 @@ int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
                        TimeConstants::SECONDS_PER_MIN);
   tm->tm_sec =
       static_cast<int>(remainingSeconds % TimeConstants::SECONDS_PER_MIN);
-  // TODO(rtenneti): Need to handle timezone and update of tm_isdst.
-  tm->tm_isdst = 0;
+
+  if (offset == 0) {
+    tm->tm_isdst = 1;
+  } else {
+    tm->tm_isdst = 0;
+    tm->tm_hour += offset;
+  }
+
+  fclose(fp);
 
   return 0;
 }
 
+int calculate_dst(struct tm *tm) {
+  int sunday = tm->tm_mday - tm->tm_wday;
+
+  if (tm->tm_mon < 3 || tm->tm_mon > 11) {
+    return 0;
+  } else if (tm->tm_mon > 3 && tm->tm_mon < 11) {
+    return 1;
+  } else if (tm->tm_mon == 3) {
+      return sunday >= 8;
+  }
+
+  return sunday <= 0;
+}
+
 } // namespace time_utils
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 552ea925c1c7dc..97374ca5b17289 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -15,6 +15,7 @@
 #include "src/__support/macros/config.h"
 #include "src/errno/libc_errno.h"
 #include "src/time/mktime.h"
+#include "src/__support/CPP/limits.h"
 
 #include <stdint.h>
 
@@ -85,6 +86,7 @@ struct TimeConstants {
 // Update the "tm" structure's year, month, etc. members from seconds.
 // "total_seconds" is the number of seconds since January 1st, 1970.
 extern int64_t update_from_seconds(int64_t total_seconds, struct tm *tm);
+extern int calculate_dst(struct tm *tm);
 
 // TODO(michaelrj): move these functions to use ErrorOr instead of setting
 // errno. They always accompany a specific return value so we only need the one
@@ -156,11 +158,38 @@ LIBC_INLINE struct tm *gmtime_internal(const time_t *timer, struct 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 struct tm *localtime(const time_t *t_ptr) {
   static struct tm result;
-  return time_utils::gmtime_internal(t_ptr, &result);
+  int64_t time = *t_ptr;
+
+  // Update the tm structure's year, month, day, etc. from seconds.
+  if (update_from_seconds(time, &result) < 0) {
+    out_of_range();
+    return nullptr;
+  }
+
+  int isdst = calculate_dst(&result);
+  result.tm_hour += isdst;
+  result.tm_isdst = isdst;
+
+  return &result;
+}
+
+LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr, struct tm *result) {
+  //time_t time = *t;
+  int64_t t = *t_ptr;
+
+  // Update the tm structure's year, month, day, etc. from seconds.
+  if (update_from_seconds(t, result) < 0) {
+    out_of_range();
+    return nullptr;
+  }
+
+  int isdst = calculate_dst(result);
+  result->tm_hour += isdst;
+  result->tm_isdst = isdst;
+
+  return result;
 }
 
 } // namespace time_utils
diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt
index bba01f063fed27..e4f8944f6f3c62 100644
--- a/libc/test/src/time/CMakeLists.txt
+++ b/libc/test/src/time/CMakeLists.txt
@@ -66,6 +66,42 @@ add_libc_unittest(
     libc.src.time.time_utils
 )
 
+add_libc_unittest(
+  localtime_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    localtime_test.cpp
+  HDRS
+    TmHelper.h
+    TmMatcher.h
+  CXX_STANDARD
+    20
+  DEPENDS
+    libc.include.time
+    libc.hdr.types.time_t
+    libc.src.time.localtime
+    libc.src.time.time_utils
+)
+
+add_libc_unittest(
+  localtime_r_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    localtime_r_test.cpp
+  HDRS
+    TmHelper.h
+    TmMatcher.h
+  CXX_STANDARD
+    20
+  DEPENDS
+    libc.include.time
+    libc.hdr.types.time_t
+    libc.src.time.localtime_r
+    libc.src.time.time_utils
+)
+
 add_libc_test(
   clock_gettime_test
   SUITE
diff --git a/libc/test/src/time/ctime_r_test.cpp b/libc/test/src/time/ctime_r_test.cpp
index 9ce6f75f754849..e0aa32e28178dc 100644
--- a/libc/test/src/time/ctime_r_test.cpp
+++ b/libc/test/src/time/ctime_r_test.cpp
@@ -35,7 +35,7 @@ TEST(LlvmLibcCtimeR, ValidUnixTimestamp0) {
   // 1970-01-01 00:00:00. Test with a valid buffer size.
   t = 0;
   result = LIBC_NAMESPACE::ctime_r(&t, buffer);
-  ASSERT_STREQ("Thu Jan  1 00:00:00 1970\n", result);
+  ASSERT_STREQ("Thu Jan  1 01:00:00 1970\n", result);
 }
 
 TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
@@ -45,7 +45,7 @@ TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
   // 2038-01-19 03:14:07. Test with a valid buffer size.
   t = 2147483647;
   result = LIBC_NAMESPACE::ctime_r(&t, buffer);
-  ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result);
+  ASSERT_STREQ("Tue Jan 19 04:14:07 2038\n", result);
 }
 
 TEST(LlvmLibcCtimeR, InvalidArgument) {
diff --git a/libc/test/src/time/ctime_test.cpp b/libc/test/src/time/ctime_test.cpp
index 7ec71bb1e4ed1e..554b72d23fd4de 100644
--- a/libc/test/src/time/ctime_test.cpp
+++ b/libc/test/src/time/ctime_test.cpp
@@ -22,7 +22,7 @@ TEST(LlvmLibcCtime, ValidUnixTimestamp0) {
   char *result;
   t = 0;
   result = LIBC_NAMESPACE::ctime(&t);
-  ASSERT_STREQ("Thu Jan  1 00:00:00 1970\n", result);
+  ASSERT_STREQ("Thu Jan  1 01:00:00 1970\n", result);
 }
 
 TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
@@ -30,7 +30,7 @@ TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
   char *result;
   t = 2147483647;
   result = LIBC_NAMESPACE::ctime(&t);
-  ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result);
+  ASSERT_STREQ("Tue Jan 19 04:14:07 2038\n", result);
 }
 
 TEST(LlvmLibcCtime, InvalidArgument) {
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 00000000000000..83848b53fc7d54
--- /dev/null
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -0,0 +1,87 @@
+//===-- 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/errno/libc_errno.h"
+#include "src/time/localtime_r.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/time/TmHelper.h"
+
+TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
+  const time_t t_ptr = 1;
+  static struct tm input = (struct tm) {
+      .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
+  };
+  struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+  ASSERT_EQ(70, result->tm_year);
+  ASSERT_EQ(0, result->tm_mon);
+  ASSERT_EQ(1, result->tm_mday);
+  ASSERT_EQ(2, result->tm_hour);
+  ASSERT_EQ(0, result->tm_min);
+  ASSERT_EQ(1, result->tm_sec);
+  ASSERT_EQ(4, result->tm_wday);
+  ASSERT_EQ(0, result->tm_yday);
+  ASSERT_EQ(0, result->tm_isdst);
+}
+
+TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
+  time_t t_ptr = 2147483647;
+  static struct tm input = (struct tm) {
+      .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
+  };
+  struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+  ASSERT_EQ(138, result->tm_year);
+  ASSERT_EQ(0, result->tm_mon);
+  ASSERT_EQ(19, result->tm_mday);
+  ASSERT_EQ(5, result->tm_hour);
+  ASSERT_EQ(14, result->tm_min);
+  ASSERT_EQ(7, result->tm_sec);
+  ASSERT_EQ(2, result->tm_wday);
+  ASSERT_EQ(18, result->tm_yday);
+  ASSERT_EQ(0, result->tm_isdst);
+}
+
+TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32IntDst) {
+  time_t t_ptr = 1627225465;
+  static struct tm input = (struct tm) {
+      .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
+  };
+  struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+  ASSERT_EQ(121, result->tm_year);
+  ASSERT_EQ(6, result->tm_mon);
+  ASSERT_EQ(25, result->tm_mday);
+  ASSERT_EQ(18, result->tm_hour);
+  ASSERT_EQ(4, result->tm_min);
+  ASSERT_EQ(25, result->tm_sec);
+  ASSERT_EQ(0, result->tm_wday);
+  ASSERT_EQ(205, result->tm_yday);
+  ASSERT_EQ(1, 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 00000000000000..ba068dbf632d02
--- /dev/null
+++ b/libc/test/src/time/localtime_test.cpp
@@ -0,0 +1,54 @@
+//===-- 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/errno/libc_errno.h"
+#include "src/time/localtime.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/time/TmHelper.h"
+
+TEST(LlvmLibcLocaltime, ValidUnixTimestamp0) {
+  const time_t t_ptr = 0;
+  struct tm *result = LIBC_NAMESPACE::localtime(&t_ptr);
+  ASSERT_EQ(70, result->tm_year);
+  ASSERT_EQ(0, result->tm_mon);
+  ASSERT_EQ(1, result->tm_mday);
+  ASSERT_EQ(2, 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, ValidUnixTimestamp32Int) {
+  time_t t_ptr = 2147483647;
+  struct tm *result = LIBC_NAMESPACE::localtime(&t_ptr);
+  ASSERT_EQ(138, result->tm_year);
+  ASSERT_EQ(0, result->tm_mon);
+  ASSERT_EQ(19, result->tm_mday);
+  ASSERT_EQ(5, result->tm_hour);
+  ASSERT_EQ(14, result->tm_min);
+  ASSERT_EQ(7, result->tm_sec);
+  ASSERT_EQ(2, result->tm_wday);
+  ASSERT_EQ(18, result->tm_yday);
+  ASSERT_EQ(0, result->tm_isdst);
+}
+
+TEST(LlvmLibcLocaltime, ValidUnixTimestamp32IntDst) {
+  time_t t_ptr = 1627225465;
+  struct tm *result = LIBC_NAMESPACE::localtime(&t_ptr);
+  ASSERT_EQ(121, result->tm_year);
+  ASSERT_EQ(6, result->tm_mon);
+  ASSERT_EQ(25, result->tm_mday);
+  ASSERT_EQ(18, result->tm_hour);
+  ASSERT_EQ(4, result->tm_min);
+  ASSERT_EQ(25, result->tm_sec);
+  ASSERT_EQ(0, result->tm_wday);
+  ASSERT_EQ(205, result->tm_yday);
+  ASSERT_EQ(1, result->tm_isdst);
+}

>From 4280c136c7fa56643a83b6184be894bfca259588 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:02:12 +0200
Subject: [PATCH 02/12] [libc] implement localtime

format code with clang-format
---
 libc/src/time/localtime.cpp             |  2 +-
 libc/src/time/localtime_r.cpp           |  5 ++-
 libc/src/time/time_utils.cpp            |  8 ++--
 libc/src/time/time_utils.h              |  7 +--
 libc/test/src/time/localtime_r_test.cpp | 60 +++++++++++--------------
 5 files changed, 39 insertions(+), 43 deletions(-)

diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp
index c08af2d8b26b78..48c8d4a6c6b3f4 100644
--- a/libc/src/time/localtime.cpp
+++ b/libc/src/time/localtime.cpp
@@ -16,7 +16,7 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(struct tm *, localtime, (const time_t *t_ptr)) {
   if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
-      return nullptr;
+    return nullptr;
   }
 
   return time_utils::localtime(t_ptr);
diff --git a/libc/src/time/localtime_r.cpp b/libc/src/time/localtime_r.cpp
index 6ff3fb3d4faa8c..64a107a729132f 100644
--- a/libc/src/time/localtime_r.cpp
+++ b/libc/src/time/localtime_r.cpp
@@ -14,9 +14,10 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(struct tm *, localtime_r, (const time_t *t_ptr, struct tm *tm)) {
+LLVM_LIBC_FUNCTION(struct tm *, localtime_r,
+                   (const time_t *t_ptr, struct tm *tm)) {
   if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
-      return nullptr;
+    return nullptr;
   }
 
   return time_utils::localtime_internal(t_ptr, tm);
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index d00cc139471c8f..9a4aacb8f86f85 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -6,11 +6,11 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <stdio.h>
 #include "src/time/time_utils.h"
 #include "src/__support/CPP/limits.h" // INT_MIN, INT_MAX
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include <stdio.h>
 
 namespace LIBC_NAMESPACE_DECL {
 namespace time_utils {
@@ -143,10 +143,10 @@ int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
 
   int offset;
   if (internal::same_string(timezone, "UTC") == 0) {
-      offset = 0;
+    offset = 0;
   }
   if (internal::same_string(timezone, "Europe/Berlin") == 0) {
-      offset = 2;
+    offset = 2;
   }
 
   // All the data (years, month and remaining days) was calculated from
@@ -185,7 +185,7 @@ int calculate_dst(struct tm *tm) {
   } else if (tm->tm_mon > 3 && tm->tm_mon < 11) {
     return 1;
   } else if (tm->tm_mon == 3) {
-      return sunday >= 8;
+    return sunday >= 8;
   }
 
   return sunday <= 0;
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 97374ca5b17289..87e0e0bae767c8 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -11,11 +11,11 @@
 
 #include <stddef.h> // For size_t.
 
+#include "src/__support/CPP/limits.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/errno/libc_errno.h"
 #include "src/time/mktime.h"
-#include "src/__support/CPP/limits.h"
 
 #include <stdint.h>
 
@@ -175,8 +175,9 @@ LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
   return &result;
 }
 
-LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr, struct tm *result) {
-  //time_t time = *t;
+LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr,
+                                          struct tm *result) {
+  // time_t time = *t;
   int64_t t = *t_ptr;
 
   // Update the tm structure's year, month, day, etc. from seconds.
diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index 83848b53fc7d54..edc7fed030aa9d 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -13,17 +13,15 @@
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
   const time_t t_ptr = 1;
-  static struct tm input = (struct tm) {
-      .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
-  };
+  static struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(70, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
@@ -38,17 +36,15 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
   time_t t_ptr = 2147483647;
-  static struct tm input = (struct tm) {
-      .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
-  };
+  static struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(138, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
@@ -63,17 +59,15 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32IntDst) {
   time_t t_ptr = 1627225465;
-  static struct tm input = (struct tm) {
-      .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
-  };
+  static struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(121, result->tm_year);
   ASSERT_EQ(6, result->tm_mon);

>From 15a5bef567cd7535a4ace2dcee0da4bddb97b954 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:05:42 +0200
Subject: [PATCH 03/12] [libc] implement localtime

fix: dst
---
 libc/src/time/time_utils.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 9a4aacb8f86f85..5cc735734111a7 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -166,9 +166,9 @@ int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
       static_cast<int>(remainingSeconds % TimeConstants::SECONDS_PER_MIN);
 
   if (offset == 0) {
-    tm->tm_isdst = 1;
-  } else {
     tm->tm_isdst = 0;
+  } else {
+    tm->tm_isdst = 1;
     tm->tm_hour += offset;
   }
 

>From 9970dfefba4073a55d5bc73be43c03a5a12d6c7b 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:16:06 +0200
Subject: [PATCH 04/12] [libc] implement localtime

fix: dst
---
 libc/src/time/time_utils.h              | 1 -
 libc/test/src/time/localtime_r_test.cpp | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 87e0e0bae767c8..0be7f114d370f9 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -187,7 +187,6 @@ LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr,
   }
 
   int isdst = calculate_dst(result);
-  result->tm_hour += isdst;
   result->tm_isdst = isdst;
 
   return result;
diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index edc7fed030aa9d..3c5d3c64e6bd9f 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -72,7 +72,7 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32IntDst) {
   ASSERT_EQ(121, result->tm_year);
   ASSERT_EQ(6, result->tm_mon);
   ASSERT_EQ(25, result->tm_mday);
-  ASSERT_EQ(18, result->tm_hour);
+  ASSERT_EQ(17, result->tm_hour);
   ASSERT_EQ(4, result->tm_min);
   ASSERT_EQ(25, result->tm_sec);
   ASSERT_EQ(0, result->tm_wday);

>From 6dee10b36c82cc3483e281c717c749a624479f3c 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:20:15 +0200
Subject: [PATCH 05/12] [libc] implement localtime

update test for `localtime_r`
---
 libc/test/src/time/localtime_r_test.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index 3c5d3c64e6bd9f..805db50751b777 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -12,7 +12,7 @@
 #include "test/src/time/TmHelper.h"
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
-  const time_t t_ptr = 1;
+  const time_t t_ptr = 0;
   static struct tm input = (struct tm){.tm_sec = 0,
                                        .tm_min = 0,
                                        .tm_hour = 0,
@@ -28,7 +28,7 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
   ASSERT_EQ(1, result->tm_mday);
   ASSERT_EQ(2, result->tm_hour);
   ASSERT_EQ(0, result->tm_min);
-  ASSERT_EQ(1, result->tm_sec);
+  ASSERT_EQ(0, result->tm_sec);
   ASSERT_EQ(4, result->tm_wday);
   ASSERT_EQ(0, result->tm_yday);
   ASSERT_EQ(0, result->tm_isdst);

>From 429d13aa01a6d9efbf23e16cbf31b5d89009dfd1 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:22:35 +0200
Subject: [PATCH 06/12] [libc] implement localtime

remove unnecessary code
---
 libc/src/time/time_utils.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 0be7f114d370f9..494ebc0bedf276 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -177,7 +177,6 @@ LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
 
 LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr,
                                           struct tm *result) {
-  // time_t time = *t;
   int64_t t = *t_ptr;
 
   // Update the tm structure's year, month, day, etc. from seconds.

>From 6dc9f4c6b22cde2393742e215d4c725d89907843 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?= <zmirza at tutanota.de>
Date: Sat, 28 Sep 2024 16:34:52 +0200
Subject: [PATCH 07/12] [libc] implement localtime

fix: dst
---
 libc/src/time/time_utils.h            | 1 -
 libc/test/src/time/localtime_test.cpp | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 494ebc0bedf276..6faf8f37172f6c 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -169,7 +169,6 @@ LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
   }
 
   int isdst = calculate_dst(&result);
-  result.tm_hour += isdst;
   result.tm_isdst = isdst;
 
   return &result;
diff --git a/libc/test/src/time/localtime_test.cpp b/libc/test/src/time/localtime_test.cpp
index ba068dbf632d02..4143e7065596ff 100644
--- a/libc/test/src/time/localtime_test.cpp
+++ b/libc/test/src/time/localtime_test.cpp
@@ -45,7 +45,7 @@ TEST(LlvmLibcLocaltime, ValidUnixTimestamp32IntDst) {
   ASSERT_EQ(121, result->tm_year);
   ASSERT_EQ(6, result->tm_mon);
   ASSERT_EQ(25, result->tm_mday);
-  ASSERT_EQ(18, result->tm_hour);
+  ASSERT_EQ(17, result->tm_hour);
   ASSERT_EQ(4, result->tm_min);
   ASSERT_EQ(25, result->tm_sec);
   ASSERT_EQ(0, result->tm_wday);

>From 054d070f0cf4e11bc649c420200894cb15d49ed7 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?= <zmirza at tutanota.de>
Date: Sun, 6 Oct 2024 16:30:21 +0200
Subject: [PATCH 08/12] localtime_s is for windows only, implement localtime_s
 on gnu/linux for compatibility reasons

---
 libc/config/baremetal/arm/entrypoints.txt   |  1 +
 libc/config/baremetal/riscv/entrypoints.txt |  1 +
 libc/config/linux/aarch64/entrypoints.txt   |  1 +
 libc/config/linux/riscv/entrypoints.txt     |  1 +
 libc/config/linux/x86_64/entrypoints.txt    |  1 +
 libc/docs/c23.rst                           |  1 +
 libc/docs/date_and_time.rst                 |  2 +
 libc/newhdrgen/yaml/time.yaml               |  7 +++
 libc/spec/stdc.td                           |  8 ++++
 libc/src/time/CMakeLists.txt                | 12 +++++
 libc/src/time/localtime.cpp                 |  8 ++--
 libc/src/time/localtime_r.cpp               |  9 +---
 libc/src/time/localtime_r.h                 |  2 +-
 libc/src/time/localtime_s.cpp               | 21 +++++++++
 libc/src/time/localtime_s.h                 | 22 +++++++++
 libc/src/time/time_utils.h                  | 37 +++++++++++++--
 libc/test/src/time/CMakeLists.txt           | 19 ++++++++
 libc/test/src/time/ctime_r_test.cpp         |  4 +-
 libc/test/src/time/localtime_r_test.cpp     | 50 +++++++++-----------
 libc/test/src/time/localtime_s_test.cpp     | 52 +++++++++++++++++++++
 20 files changed, 213 insertions(+), 46 deletions(-)
 create mode 100644 libc/src/time/localtime_s.cpp
 create mode 100644 libc/src/time/localtime_s.h
 create mode 100644 libc/test/src/time/localtime_s_test.cpp

diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index 7577fad2fde869..0a905d50f8ba25 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -207,6 +207,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.time.ctime_r
     libc.src.time.localtime
     libc.src.time.localtime_r
+    libc.src.time.localtime_s
     libc.src.time.difftime
     libc.src.time.gmtime
     libc.src.time.gmtime_r
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index b93aa334296ffb..b9ea143c770262 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -203,6 +203,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.time.ctime_r
     libc.src.time.localtime
     libc.src.time.localtime_r
+    libc.src.time.localtime_s
     libc.src.time.difftime
     libc.src.time.gmtime
     libc.src.time.gmtime_r
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index d287bb0510a1fb..a4b430f1e84ea4 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -952,6 +952,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.ctime_r
     libc.src.time.localtime
     libc.src.time.localtime_r
+    libc.src.time.localtime_s
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 40a417bcf09b49..18f6e6418a7af9 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -887,6 +887,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.ctime_r
     libc.src.time.localtime
     libc.src.time.localtime_r
+    libc.src.time.localtime_s
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 3b63ce82417f4b..5b9b0dea1b31ae 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1007,6 +1007,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.ctime_r
     libc.src.time.localtime
     libc.src.time.localtime_r
+    libc.src.time.localtime_s
     libc.src.time.clock
     libc.src.time.clock_gettime
     libc.src.time.difftime
diff --git a/libc/docs/c23.rst b/libc/docs/c23.rst
index b9a2424307f84b..9f0515bda5d581 100644
--- a/libc/docs/c23.rst
+++ b/libc/docs/c23.rst
@@ -152,6 +152,7 @@ Additions:
 
   * gmtime_r
   * localtime_r
+  * localtime_s
   * timegm
   * timespec_getres
   * strftime conversion specifiers
diff --git a/libc/docs/date_and_time.rst b/libc/docs/date_and_time.rst
index b745a3b416f802..ab7c7c3de68677 100644
--- a/libc/docs/date_and_time.rst
+++ b/libc/docs/date_and_time.rst
@@ -89,6 +89,8 @@ Implementation Status
 +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+
 | localtime_r         |         |         |         |                 |         |         |         |         |         |         |         |         |
 +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+
+| localtime_s         |         |         |         |                 |         |         |         |         |         |         |         |         |
++---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+
 | mktime              | |check| | |check| |         |     |check|     |         |         |         |         |         |         |         |         |
 +---------------------+---------+---------+---------+-----------------+---------+---------+---------+---------+---------+---------+---------+---------+
 | nanosleep           | |check| | |check| |         |     |check|     |         |         |         |         |         |         |         |         |
diff --git a/libc/newhdrgen/yaml/time.yaml b/libc/newhdrgen/yaml/time.yaml
index 7198c19fce2bf0..b0311b64165472 100644
--- a/libc/newhdrgen/yaml/time.yaml
+++ b/libc/newhdrgen/yaml/time.yaml
@@ -50,6 +50,13 @@ functions:
     arguments:
       - type: const time_t *
       - type: struct tm *
+  - name: localtime_s
+    standard:
+      - stdc
+    return_type: struct tm *
+    arguments:
+      - type: const time_t *
+      - type: struct tm *
   - name: clock
     standard:
       - stdc
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 9fd8d175f36be3..5546a3afcab9f7 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -1628,6 +1628,14 @@ def StdC : StandardSpec<"stdc"> {
                   ArgSpec<StructTmPtr>,
               ]
           >,
+          FunctionSpec<
+              "localtime_s",
+              RetValSpec<StructTmPtr>,
+              [
+                  ArgSpec<TimeTTypePtr>,
+                  ArgSpec<StructTmPtr>,
+              ]
+          >,
           FunctionSpec<
               "clock",
               RetValSpec<ClockT>,
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index de2e128dd310c4..57b229d029c92e 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -84,6 +84,18 @@ add_entrypoint_object(
     libc.include.time
 )
 
+add_entrypoint_object(
+  localtime_s
+  SRCS
+    localtime_s.cpp
+  HDRS
+    localtime_s.h
+  DEPENDS
+    .time_utils
+    libc.hdr.types.time_t
+    libc.include.time
+)
+
 add_entrypoint_object(
   difftime
   SRCS
diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp
index 48c8d4a6c6b3f4..42ba562e0a561c 100644
--- a/libc/src/time/localtime.cpp
+++ b/libc/src/time/localtime.cpp
@@ -6,16 +6,18 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "src/time/localtime.h"
+#include "localtime.h"
 #include "src/__support/CPP/limits.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
-#include "src/time/time_utils.h"
+#include "time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
+using LIBC_NAMESPACE::time_utils::TimeConstants;
+
 LLVM_LIBC_FUNCTION(struct tm *, localtime, (const time_t *t_ptr)) {
-  if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
+  if (t_ptr == nullptr) {
     return nullptr;
   }
 
diff --git a/libc/src/time/localtime_r.cpp b/libc/src/time/localtime_r.cpp
index 64a107a729132f..0af5414930327b 100644
--- a/libc/src/time/localtime_r.cpp
+++ b/libc/src/time/localtime_r.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/time/localtime_r.h"
-#include "src/__support/CPP/limits.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/time/time_utils.h"
@@ -15,12 +14,8 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(struct tm *, localtime_r,
-                   (const time_t *t_ptr, struct tm *tm)) {
-  if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
-    return nullptr;
-  }
-
-  return time_utils::localtime_internal(t_ptr, tm);
+                   (const time_t *t_ptr, struct tm *input)) {
+  return time_utils::localtime_internal(t_ptr, input);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/localtime_r.h b/libc/src/time/localtime_r.h
index d98b52180fa789..553811d6ffdc2b 100644
--- a/libc/src/time/localtime_r.h
+++ b/libc/src/time/localtime_r.h
@@ -14,7 +14,7 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-struct tm *localtime_r(const time_t *t_ptr, struct tm *tm);
+struct tm *localtime_r(const time_t *t_ptr, struct tm *input);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/time/localtime_s.cpp b/libc/src/time/localtime_s.cpp
new file mode 100644
index 00000000000000..e49ff49810819f
--- /dev/null
+++ b/libc/src/time/localtime_s.cpp
@@ -0,0 +1,21 @@
+//===-- Implementation of localtime_s 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_s.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// windows only, implemented in gnu/linux for compatibility reasons
+LLVM_LIBC_FUNCTION(int, localtime_s, (const time_t *t_ptr, struct tm *input)) {
+  return time_utils::localtime_s(t_ptr, input);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/localtime_s.h b/libc/src/time/localtime_s.h
new file mode 100644
index 00000000000000..4f69cb0014d507
--- /dev/null
+++ b/libc/src/time/localtime_s.h
@@ -0,0 +1,22 @@
+//===-- Implementation header of localtime_s --------------------*- 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_S_H
+#define LLVM_LIBC_SRC_TIME_LOCALTIME_S_H
+
+#include "src/__support/macros/config.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+// windows only, implemented in gnu/linux for compatibility reasons
+int localtime_s(const time_t *t_ptr, struct tm *input);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_TIME_LOCALTIME_S_H
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 6faf8f37172f6c..78bfa069471231 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -175,19 +175,46 @@ LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
 }
 
 LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr,
-                                          struct tm *result) {
+                                          struct tm *input) {
+  static struct tm result;
   int64_t t = *t_ptr;
 
+  result.tm_sec = input->tm_sec;
+  result.tm_min = input->tm_min;
+  result.tm_hour = input->tm_hour;
+  result.tm_mday = input->tm_mday;
+  result.tm_mon = input->tm_mon;
+  result.tm_year = input->tm_year;
+  result.tm_wday = input->tm_wday;
+  result.tm_yday = input->tm_yday;
+  result.tm_isdst = input->tm_isdst;
+
   // Update the tm structure's year, month, day, etc. from seconds.
-  if (update_from_seconds(t, result) < 0) {
+  if (update_from_seconds(t, &result) < 0) {
     out_of_range();
     return nullptr;
   }
 
-  int isdst = calculate_dst(result);
-  result->tm_isdst = isdst;
+  int isdst = calculate_dst(&result);
+  result.tm_isdst = isdst;
 
-  return result;
+  return &result;
+}
+
+// for windows only, implemented on gnu/linux for compatibility reasons
+LIBC_INLINE int localtime_s(const time_t *t_ptr, struct tm *input) {
+  static struct tm *result = localtime_internal(t_ptr, input);
+  time_t t = LIBC_NAMESPACE::mktime(result);
+
+  if (*t_ptr < t) {
+    return -1;
+  }
+
+  if (*t_ptr > t) {
+    return 1;
+  }
+
+  return 0;
 }
 
 } // namespace time_utils
diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt
index e4f8944f6f3c62..54e078cff83c15 100644
--- a/libc/test/src/time/CMakeLists.txt
+++ b/libc/test/src/time/CMakeLists.txt
@@ -102,6 +102,25 @@ add_libc_unittest(
     libc.src.time.time_utils
 )
 
+add_libc_unittest(
+  localtime_s_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    localtime_s_test.cpp
+  HDRS
+    TmHelper.h
+    TmMatcher.h
+  CXX_STANDARD
+    20
+  DEPENDS
+    libc.include.time
+    libc.hdr.types.time_t
+    libc.src.time.mktime
+    libc.src.time.localtime_s
+    libc.src.time.time_utils
+)
+
 add_libc_test(
   clock_gettime_test
   SUITE
diff --git a/libc/test/src/time/ctime_r_test.cpp b/libc/test/src/time/ctime_r_test.cpp
index e0aa32e28178dc..b03e8cecd2e9df 100644
--- a/libc/test/src/time/ctime_r_test.cpp
+++ b/libc/test/src/time/ctime_r_test.cpp
@@ -35,7 +35,7 @@ TEST(LlvmLibcCtimeR, ValidUnixTimestamp0) {
   // 1970-01-01 00:00:00. Test with a valid buffer size.
   t = 0;
   result = LIBC_NAMESPACE::ctime_r(&t, buffer);
-  ASSERT_STREQ("Thu Jan  1 01:00:00 1970\n", result);
+  ASSERT_STREQ("Thu Jan  1 02:00:00 1970\n", result);
 }
 
 TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
@@ -45,7 +45,7 @@ TEST(LlvmLibcCtime, ValidUnixTimestamp32Int) {
   // 2038-01-19 03:14:07. Test with a valid buffer size.
   t = 2147483647;
   result = LIBC_NAMESPACE::ctime_r(&t, buffer);
-  ASSERT_STREQ("Tue Jan 19 04:14:07 2038\n", result);
+  ASSERT_STREQ("Tue Jan 19 05:14:07 2038\n", result);
 }
 
 TEST(LlvmLibcCtimeR, InvalidArgument) {
diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index 805db50751b777..ba46835e81f77a 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -6,22 +6,16 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "src/errno/libc_errno.h"
 #include "src/time/localtime_r.h"
+#include "src/time/time_utils.h"
 #include "test/UnitTest/Test.h"
 #include "test/src/time/TmHelper.h"
 
+using LIBC_NAMESPACE::time_utils::TimeConstants;
+
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
+  struct tm input;
   const time_t t_ptr = 0;
-  static struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(70, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
@@ -36,15 +30,15 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
   time_t t_ptr = 2147483647;
-  static struct tm input = (struct tm){.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};
+  struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(138, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
@@ -59,15 +53,15 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32IntDst) {
   time_t t_ptr = 1627225465;
-  static struct tm input = (struct tm){.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};
+  struct tm input = (struct tm){.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};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
   ASSERT_EQ(121, result->tm_year);
   ASSERT_EQ(6, result->tm_mon);
diff --git a/libc/test/src/time/localtime_s_test.cpp b/libc/test/src/time/localtime_s_test.cpp
new file mode 100644
index 00000000000000..04d5d79c810792
--- /dev/null
+++ b/libc/test/src/time/localtime_s_test.cpp
@@ -0,0 +1,52 @@
+//===-- Unittests for localtime_s -----------------------------------------===//
+//
+// 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_s.h"
+#include "src/time/mktime.h"
+#include "src/time/time_utils.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/time/TmHelper.h"
+
+using LIBC_NAMESPACE::time_utils::TimeConstants;
+
+TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp0) {
+  struct tm input;
+  const time_t t_ptr = 0;
+  int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
+  ASSERT_EQ(-1, result);
+}
+
+TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32Int) {
+  time_t t_ptr = 2147483647;
+  static struct tm input = (struct tm){.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};
+  int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
+  ASSERT_EQ(1, result);
+}
+
+TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32IntDst) {
+  time_t t_ptr = 1627225465;
+  static struct tm input = (struct tm){.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};
+  int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
+  ASSERT_EQ(1, result);
+}

>From a09d3af60719db6bdebb9eb20c5ce01fe36ab8f7 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?= <zmirza at tutanota.de>
Date: Sun, 6 Oct 2024 16:37:58 +0200
Subject: [PATCH 09/12] fix: localtime_s

---
 libc/newhdrgen/yaml/time.yaml | 2 +-
 libc/spec/stdc.td             | 2 +-
 libc/src/time/CMakeLists.txt  | 1 +
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/libc/newhdrgen/yaml/time.yaml b/libc/newhdrgen/yaml/time.yaml
index b0311b64165472..9c249e21fc8d82 100644
--- a/libc/newhdrgen/yaml/time.yaml
+++ b/libc/newhdrgen/yaml/time.yaml
@@ -53,7 +53,7 @@ functions:
   - name: localtime_s
     standard:
       - stdc
-    return_type: struct tm *
+    return_type: int
     arguments:
       - type: const time_t *
       - type: struct tm *
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 5546a3afcab9f7..d6186281b76a3f 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -1630,7 +1630,7 @@ def StdC : StandardSpec<"stdc"> {
           >,
           FunctionSpec<
               "localtime_s",
-              RetValSpec<StructTmPtr>,
+              RetValSpec<IntType>,
               [
                   ArgSpec<TimeTTypePtr>,
                   ArgSpec<StructTmPtr>,
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index 57b229d029c92e..e983ccf6359e93 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -94,6 +94,7 @@ add_entrypoint_object(
     .time_utils
     libc.hdr.types.time_t
     libc.include.time
+    libc.time.mktime
 )
 
 add_entrypoint_object(

>From 897c8209c85e30567244703e861376ac2d504cc9 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?= <zmirza at tutanota.de>
Date: Sun, 6 Oct 2024 17:58:41 +0200
Subject: [PATCH 10/12] fix: path for mktime

---
 libc/src/time/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index e983ccf6359e93..096d6bcc7dd8ef 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -94,7 +94,7 @@ add_entrypoint_object(
     .time_utils
     libc.hdr.types.time_t
     libc.include.time
-    libc.time.mktime
+    libc.src.time.mktime
 )
 
 add_entrypoint_object(

>From 31b3f2483f11e68659f043fdae2dd12003bba00d 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?= <zmirza at tutanota.de>
Date: Sun, 6 Oct 2024 19:38:42 +0200
Subject: [PATCH 11/12] fix: localtime_s and dst function

---
 libc/src/time/time_utils.cpp            | 18 ++++--
 libc/src/time/time_utils.h              | 45 +++++++--------
 libc/test/src/time/localtime_s_test.cpp | 77 ++++++++++++++++++++++---
 3 files changed, 103 insertions(+), 37 deletions(-)

diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 5cc735734111a7..35fbc02c17e679 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -177,18 +177,24 @@ int64_t update_from_seconds(int64_t total_seconds, struct tm *tm) {
   return 0;
 }
 
-int calculate_dst(struct tm *tm) {
-  int sunday = tm->tm_mday - tm->tm_wday;
+void set_dst(struct tm *tm) {
+  int dst;
+  int sunday;
+
+  dst = 0;
+  sunday = tm->tm_mday - tm->tm_wday;
 
   if (tm->tm_mon < 3 || tm->tm_mon > 11) {
-    return 0;
+    dst = 0;
   } else if (tm->tm_mon > 3 && tm->tm_mon < 11) {
-    return 1;
+    dst = 1;
   } else if (tm->tm_mon == 3) {
-    return sunday >= 8;
+    dst = sunday >= 8;
+  } else {
+    dst = sunday <= 0;
   }
 
-  return sunday <= 0;
+  tm->tm_isdst = dst;
 }
 
 } // namespace time_utils
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 78bfa069471231..51442493e19d87 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -86,7 +86,7 @@ struct TimeConstants {
 // Update the "tm" structure's year, month, etc. members from seconds.
 // "total_seconds" is the number of seconds since January 1st, 1970.
 extern int64_t update_from_seconds(int64_t total_seconds, struct tm *tm);
-extern int calculate_dst(struct tm *tm);
+extern void set_dst(struct tm *tm);
 
 // TODO(michaelrj): move these functions to use ErrorOr instead of setting
 // errno. They always accompany a specific return value so we only need the one
@@ -168,51 +168,48 @@ LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
     return nullptr;
   }
 
-  int isdst = calculate_dst(&result);
-  result.tm_isdst = isdst;
+  set_dst(&result);
 
   return &result;
 }
 
 LIBC_INLINE struct tm *localtime_internal(const time_t *t_ptr,
                                           struct tm *input) {
-  static struct tm result;
   int64_t t = *t_ptr;
 
-  result.tm_sec = input->tm_sec;
-  result.tm_min = input->tm_min;
-  result.tm_hour = input->tm_hour;
-  result.tm_mday = input->tm_mday;
-  result.tm_mon = input->tm_mon;
-  result.tm_year = input->tm_year;
-  result.tm_wday = input->tm_wday;
-  result.tm_yday = input->tm_yday;
-  result.tm_isdst = input->tm_isdst;
-
   // Update the tm structure's year, month, day, etc. from seconds.
-  if (update_from_seconds(t, &result) < 0) {
+  if (update_from_seconds(t, input) < 0) {
     out_of_range();
     return nullptr;
   }
 
-  int isdst = calculate_dst(&result);
-  result.tm_isdst = isdst;
+  set_dst(input);
 
-  return &result;
+  return input;
 }
 
 // for windows only, implemented on gnu/linux for compatibility reasons
 LIBC_INLINE int localtime_s(const time_t *t_ptr, struct tm *input) {
-  static struct tm *result = localtime_internal(t_ptr, input);
-  time_t t = LIBC_NAMESPACE::mktime(result);
+  if (input == NULL)
+    return -1;
+
+  if ((*t_ptr < 0 || *t_ptr > cpp::numeric_limits<int64_t>::max()) && input != NULL) {
+    // setting values to -1 for compatibility reasons
+    // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s
+    input->tm_sec = -1;
+    input->tm_min = -1;
+    input->tm_hour = -1;
+    input->tm_mday = -1;
+    input->tm_mon = -1;
+    input->tm_year = -1;
+    input->tm_wday = -1;
+    input->tm_yday = -1;
+    input->tm_isdst = -1;
 
-  if (*t_ptr < t) {
     return -1;
   }
 
-  if (*t_ptr > t) {
-    return 1;
-  }
+  localtime_internal(t_ptr, input);
 
   return 0;
 }
diff --git a/libc/test/src/time/localtime_s_test.cpp b/libc/test/src/time/localtime_s_test.cpp
index 04d5d79c810792..4a9d1d5dba5414 100644
--- a/libc/test/src/time/localtime_s_test.cpp
+++ b/libc/test/src/time/localtime_s_test.cpp
@@ -15,15 +15,33 @@
 using LIBC_NAMESPACE::time_utils::TimeConstants;
 
 TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp0) {
-  struct tm input;
-  const time_t t_ptr = 0;
+  struct tm input = (struct tm){.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};
+  time_t t_ptr = 0;
   int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
-  ASSERT_EQ(-1, result);
+  ASSERT_EQ(0, result);
+
+  ASSERT_EQ(70, input.tm_year);
+  ASSERT_EQ(0, input.tm_mon);
+  ASSERT_EQ(1, input.tm_mday);
+  ASSERT_EQ(2, input.tm_hour);
+  ASSERT_EQ(0, input.tm_min);
+  ASSERT_EQ(0, input.tm_sec);
+  ASSERT_EQ(4, input.tm_wday);
+  ASSERT_EQ(0, input.tm_yday);
+  ASSERT_EQ(0, input.tm_isdst);
 }
 
 TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32Int) {
   time_t t_ptr = 2147483647;
-  static struct tm input = (struct tm){.tm_sec = 0,
+  struct tm input = (struct tm){.tm_sec = 0,
                                        .tm_min = 0,
                                        .tm_hour = 0,
                                        .tm_mday = 0,
@@ -33,12 +51,22 @@ TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32Int) {
                                        .tm_yday = 0,
                                        .tm_isdst = 0};
   int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
-  ASSERT_EQ(1, result);
+  ASSERT_EQ(0, result);
+
+  ASSERT_EQ(138, input.tm_year);
+  ASSERT_EQ(0, input.tm_mon);
+  ASSERT_EQ(19, input.tm_mday);
+  ASSERT_EQ(5, input.tm_hour);
+  ASSERT_EQ(14, input.tm_min);
+  ASSERT_EQ(7, input.tm_sec);
+  ASSERT_EQ(2, input.tm_wday);
+  ASSERT_EQ(18, input.tm_yday);
+  ASSERT_EQ(0, input.tm_isdst);
 }
 
 TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32IntDst) {
   time_t t_ptr = 1627225465;
-  static struct tm input = (struct tm){.tm_sec = 0,
+  struct tm input = (struct tm){.tm_sec = 0,
                                        .tm_min = 0,
                                        .tm_hour = 0,
                                        .tm_mday = 0,
@@ -48,5 +76,40 @@ TEST(LlvmLibcLocaltimeS, ValidUnixTimestamp32IntDst) {
                                        .tm_yday = 0,
                                        .tm_isdst = 0};
   int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
-  ASSERT_EQ(1, result);
+  ASSERT_EQ(0, result);
+
+  ASSERT_EQ(121, input.tm_year);
+  ASSERT_EQ(6, input.tm_mon);
+  ASSERT_EQ(25, input.tm_mday);
+  ASSERT_EQ(17, input.tm_hour);
+  ASSERT_EQ(4, input.tm_min);
+  ASSERT_EQ(25, input.tm_sec);
+  ASSERT_EQ(0, input.tm_wday);
+  ASSERT_EQ(205, input.tm_yday);
+  ASSERT_EQ(1, input.tm_isdst);
+}
+
+TEST(LlvmLibcLocaltimeS, InvalidUnixTimestamp) {
+  time_t t_ptr = -1;
+  struct tm input = (struct tm){.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};
+  int result = LIBC_NAMESPACE::localtime_s(&t_ptr, &input);
+  ASSERT_EQ(-1, result);
+
+  ASSERT_EQ(-1, input.tm_year);
+  ASSERT_EQ(-1, input.tm_mon);
+  ASSERT_EQ(-1, input.tm_mday);
+  ASSERT_EQ(-1, input.tm_hour);
+  ASSERT_EQ(-1, input.tm_min);
+  ASSERT_EQ(-1, input.tm_sec);
+  ASSERT_EQ(-1, input.tm_wday);
+  ASSERT_EQ(-1, input.tm_yday);
+  ASSERT_EQ(-1, input.tm_isdst);
 }

>From 0dbcfd3a8387dd69d83e2ea5a7701bcb4cf1af74 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?= <zmirza at tutanota.de>
Date: Sun, 6 Oct 2024 19:42:14 +0200
Subject: [PATCH 12/12] added tests for localtime_r

---
 libc/test/src/time/localtime_r_test.cpp | 35 ++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index ba46835e81f77a..1a3e4be11e6c36 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -15,8 +15,19 @@ using LIBC_NAMESPACE::time_utils::TimeConstants;
 
 TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
   struct tm input;
-  const time_t t_ptr = 0;
+  time_t t_ptr = 0;
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+
+  ASSERT_EQ(70, input.tm_year);
+  ASSERT_EQ(0, input.tm_mon);
+  ASSERT_EQ(1, input.tm_mday);
+  ASSERT_EQ(2, input.tm_hour);
+  ASSERT_EQ(0, input.tm_min);
+  ASSERT_EQ(0, input.tm_sec);
+  ASSERT_EQ(4, input.tm_wday);
+  ASSERT_EQ(0, input.tm_yday);
+  ASSERT_EQ(0, input.tm_isdst);
+
   ASSERT_EQ(70, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
   ASSERT_EQ(1, result->tm_mday);
@@ -40,6 +51,17 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32Int) {
                                 .tm_yday = 0,
                                 .tm_isdst = 0};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+
+  ASSERT_EQ(138, input.tm_year);
+  ASSERT_EQ(0, input.tm_mon);
+  ASSERT_EQ(19, input.tm_mday);
+  ASSERT_EQ(5, input.tm_hour);
+  ASSERT_EQ(14, input.tm_min);
+  ASSERT_EQ(7, input.tm_sec);
+  ASSERT_EQ(2, input.tm_wday);
+  ASSERT_EQ(18, input.tm_yday);
+  ASSERT_EQ(0, input.tm_isdst);
+
   ASSERT_EQ(138, result->tm_year);
   ASSERT_EQ(0, result->tm_mon);
   ASSERT_EQ(19, result->tm_mday);
@@ -63,6 +85,17 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp32IntDst) {
                                 .tm_yday = 0,
                                 .tm_isdst = 0};
   struct tm *result = LIBC_NAMESPACE::localtime_r(&t_ptr, &input);
+
+  ASSERT_EQ(121, input.tm_year);
+  ASSERT_EQ(6, input.tm_mon);
+  ASSERT_EQ(25, input.tm_mday);
+  ASSERT_EQ(17, input.tm_hour);
+  ASSERT_EQ(4, input.tm_min);
+  ASSERT_EQ(25, input.tm_sec);
+  ASSERT_EQ(0, input.tm_wday);
+  ASSERT_EQ(205, input.tm_yday);
+  ASSERT_EQ(1, input.tm_isdst);
+
   ASSERT_EQ(121, result->tm_year);
   ASSERT_EQ(6, result->tm_mon);
   ASSERT_EQ(25, result->tm_mday);



More information about the libc-commits mailing list