[libc-commits] [libc] [libc] Complete hardening of time functions and remove Y2038 limit (PR #203298)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Thu Jun 11 08:15:41 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/203298

>From 54c0883e354490b7db00d1d228ea68900c70ee69 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 11 Jun 2026 15:42:22 +0100
Subject: [PATCH] [libc] Complete hardening of time functions and remove Y2038
 limit

Completed safety and hardening refactoring for time functions, and
removed Y2038 limitation:

* Hardening: Refactored update_from_seconds to return ErrorOr<int>
  (instead of int64_t status, as it only returns status 0 or error)
  and propagated it to entrypoints. Added LIBC_CRASH_ON_NULLPTR to public
  boundaries and converted tests to death tests using WITH_SIGNAL(-1)
  for portability.
* Y2038: Removed the artificial int32_t max check from ctime and
  ctime_r, allowing them to support timestamps beyond 2038 (up to Year
  9999).
* Tests: Updated ctime/ctime_r tests to include Year 2039 test cases
  and updated overflow test cases to Year 10000.
* Style: Fixed header blocks and added Doxygen comments in
  converter.h, str_converter.h, and time_utils.h.
* Conventions: Fixed relative includes in converter.h and time_utils.h.

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/time/CMakeLists.txt                  |  19 +++
 libc/src/time/asctime.cpp                     |  12 +-
 libc/src/time/asctime_r.cpp                   |  14 ++-
 libc/src/time/ctime.cpp                       |  19 ++-
 libc/src/time/ctime_r.cpp                     |  20 +++-
 libc/src/time/gmtime.cpp                      |  11 +-
 libc/src/time/gmtime_r.cpp                    |  12 +-
 libc/src/time/localtime.cpp                   |   8 +-
 libc/src/time/localtime_r.cpp                 |   8 +-
 libc/src/time/mktime.cpp                      |  17 ++-
 libc/src/time/strftime.cpp                    |   6 +
 libc/src/time/strftime_core/CMakeLists.txt    |   1 +
 .../time/strftime_core/composite_converter.h  | 102 ++++++++++++++---
 libc/src/time/strftime_core/converter.h       |  29 +++--
 libc/src/time/strftime_core/num_converter.h   |  19 ++-
 libc/src/time/strftime_core/str_converter.h   |  21 +++-
 libc/src/time/strftime_l.cpp                  |   6 +
 libc/src/time/time_utils.cpp                  |   6 +-
 libc/src/time/time_utils.h                    | 108 +++++++++---------
 libc/test/src/time/asctime_r_test.cpp         |  19 ++-
 libc/test/src/time/asctime_test.cpp           |   6 +-
 libc/test/src/time/ctime_r_test.cpp           |  28 +++--
 libc/test/src/time/ctime_test.cpp             |  16 ++-
 libc/test/src/time/gmtime_r_test.cpp          |  12 ++
 libc/test/src/time/gmtime_test.cpp            |   5 +
 libc/test/src/time/localtime_r_test.cpp       |  11 +-
 libc/test/src/time/localtime_test.cpp         |   3 +-
 27 files changed, 387 insertions(+), 151 deletions(-)

diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index 4d647c22c3239..f42b9b3723ca0 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -44,6 +44,8 @@ add_entrypoint_object(
     .time_constants
     libc.include.time
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -57,6 +59,8 @@ add_entrypoint_object(
     .time_constants
     libc.include.time
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -70,6 +74,8 @@ add_entrypoint_object(
     .time_constants
     libc.hdr.types.time_t
     libc.include.time
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -83,6 +89,8 @@ add_entrypoint_object(
     .time_constants
     libc.hdr.types.time_t
     libc.include.time
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -95,6 +103,8 @@ add_entrypoint_object(
     .time_utils
     libc.hdr.types.time_t
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -107,6 +117,8 @@ add_entrypoint_object(
     .time_utils
     libc.hdr.types.time_t
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -131,6 +143,8 @@ add_entrypoint_object(
     libc.include.time
     libc.hdr.types.time_t
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -144,6 +158,8 @@ add_entrypoint_object(
     libc.include.time
     libc.hdr.types.time_t
     libc.hdr.types.struct_tm
+    libc.src.errno.errno
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -159,6 +175,7 @@ add_entrypoint_object(
     libc.src.errno.errno
     libc.hdr.types.time_t
     libc.hdr.types.struct_tm
+    libc.src.__support.macros.null_check
 )
 
 add_subdirectory(strftime_core) #TODO: Move to top
@@ -174,6 +191,7 @@ add_entrypoint_object(
     libc.hdr.types.struct_tm
     libc.src.stdio.printf_core.writer
     libc.src.time.strftime_core.strftime_main
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -188,6 +206,7 @@ add_entrypoint_object(
     libc.hdr.types.struct_tm
     libc.src.stdio.printf_core.writer
     libc.src.time.strftime_core.strftime_main
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
diff --git a/libc/src/time/asctime.cpp b/libc/src/time/asctime.cpp
index 2b00c4136f906..8baeeb88b5698 100644
--- a/libc/src/time/asctime.cpp
+++ b/libc/src/time/asctime.cpp
@@ -8,16 +8,24 @@
 
 #include "src/time/asctime.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_constants.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(char *, asctime, (const struct tm *timeptr)) {
+  LIBC_CRASH_ON_NULLPTR(timeptr);
   static char buffer[time_constants::ASCTIME_BUFFER_SIZE];
-  return time_utils::asctime(timeptr, buffer,
-                             time_constants::ASCTIME_MAX_BYTES);
+  auto res =
+      time_utils::asctime(timeptr, buffer, time_constants::ASCTIME_MAX_BYTES);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/asctime_r.cpp b/libc/src/time/asctime_r.cpp
index bf53bfdf2f8c2..1002e4139adf7 100644
--- a/libc/src/time/asctime_r.cpp
+++ b/libc/src/time/asctime_r.cpp
@@ -8,7 +8,9 @@
 
 #include "src/time/asctime_r.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_constants.h"
 #include "src/time/time_utils.h"
 
@@ -16,8 +18,16 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(char *, asctime_r,
                    (const struct tm *timeptr, char *buffer)) {
-  return time_utils::asctime(timeptr, buffer,
-                             time_constants::ASCTIME_MAX_BYTES);
+  LIBC_CRASH_ON_NULLPTR(timeptr);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+
+  auto res =
+      time_utils::asctime(timeptr, buffer, time_constants::ASCTIME_MAX_BYTES);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/ctime.cpp b/libc/src/time/ctime.cpp
index ac0ffe5b32ae5..c7efcbffc5123 100644
--- a/libc/src/time/ctime.cpp
+++ b/libc/src/time/ctime.cpp
@@ -7,21 +7,32 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/time/ctime.h"
-#include "src/__support/CPP/limits.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_constants.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(char *, ctime, (const time_t *t_ptr)) {
-  if (t_ptr == nullptr || *t_ptr > cpp::numeric_limits<int32_t>::max()) {
+  LIBC_CRASH_ON_NULLPTR(t_ptr);
+
+  auto lt_res = time_utils::localtime(t_ptr);
+  if (!lt_res) {
+    libc_errno = lt_res.error();
     return nullptr;
   }
+
   static char buffer[time_constants::ASCTIME_BUFFER_SIZE];
-  return time_utils::asctime(time_utils::localtime(t_ptr), buffer,
-                             time_constants::ASCTIME_MAX_BYTES);
+  auto res = time_utils::asctime(lt_res.value(), buffer,
+                                 time_constants::ASCTIME_MAX_BYTES);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/ctime_r.cpp b/libc/src/time/ctime_r.cpp
index 7224f7742f139..0c8260b04df2a 100644
--- a/libc/src/time/ctime_r.cpp
+++ b/libc/src/time/ctime_r.cpp
@@ -7,22 +7,32 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/time/ctime_r.h"
-#include "src/__support/CPP/limits.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_constants.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(char *, ctime_r, (const time_t *t_ptr, char *buffer)) {
-  if (t_ptr == nullptr || buffer == nullptr ||
-      *t_ptr > cpp::numeric_limits<int32_t>::max()) {
+  LIBC_CRASH_ON_NULLPTR(t_ptr);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+
+  auto lt_res = time_utils::localtime(t_ptr);
+  if (!lt_res) {
+    libc_errno = lt_res.error();
     return nullptr;
   }
 
-  return time_utils::asctime(time_utils::localtime(t_ptr), buffer,
-                             time_constants::ASCTIME_MAX_BYTES);
+  auto res = time_utils::asctime(lt_res.value(), buffer,
+                                 time_constants::ASCTIME_MAX_BYTES);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/gmtime.cpp b/libc/src/time/gmtime.cpp
index 6785d18d43e36..32a14b4d53c66 100644
--- a/libc/src/time/gmtime.cpp
+++ b/libc/src/time/gmtime.cpp
@@ -8,14 +8,23 @@
 
 #include "src/time/gmtime.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(struct tm *, gmtime, (const time_t *timer)) {
+  LIBC_CRASH_ON_NULLPTR(timer);
+
   static struct tm tm_out;
-  return time_utils::gmtime_internal(timer, &tm_out);
+  auto result = time_utils::gmtime_internal(timer, &tm_out);
+  if (!result) {
+    libc_errno = result.error();
+    return nullptr;
+  }
+  return result.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/gmtime_r.cpp b/libc/src/time/gmtime_r.cpp
index d506b526edfb0..6b37d4e3f0990 100644
--- a/libc/src/time/gmtime_r.cpp
+++ b/libc/src/time/gmtime_r.cpp
@@ -8,14 +8,24 @@
 
 #include "src/time/gmtime_r.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(struct tm *, gmtime_r,
                    (const time_t *timer, struct tm *result)) {
-  return time_utils::gmtime_internal(timer, result);
+  LIBC_CRASH_ON_NULLPTR(timer);
+  LIBC_CRASH_ON_NULLPTR(result);
+
+  auto res = time_utils::gmtime_internal(timer, result);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/localtime.cpp b/libc/src/time/localtime.cpp
index 90a296178bae8..84b70e90a9384 100644
--- a/libc/src/time/localtime.cpp
+++ b/libc/src/time/localtime.cpp
@@ -9,6 +9,7 @@
 #include "src/time/localtime.h"
 #include "hdr/types/struct_tm.h"
 #include "hdr/types/time_t.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/null_check.h"
 #include "src/time/time_utils.h"
 
@@ -18,7 +19,12 @@ 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);
+  auto res = time_utils::localtime_internal(timer, &tm_out);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/localtime_r.cpp b/libc/src/time/localtime_r.cpp
index 70bbbeed271ea..298996b2a21ea 100644
--- a/libc/src/time/localtime_r.cpp
+++ b/libc/src/time/localtime_r.cpp
@@ -9,6 +9,7 @@
 #include "src/time/localtime_r.h"
 #include "hdr/types/struct_tm.h"
 #include "hdr/types/time_t.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/null_check.h"
 #include "src/time/time_utils.h"
 
@@ -19,7 +20,12 @@ LLVM_LIBC_FUNCTION(struct tm *, localtime_r,
   LIBC_CRASH_ON_NULLPTR(timer);
   LIBC_CRASH_ON_NULLPTR(buf);
 
-  return time_utils::localtime_internal(timer, buf);
+  auto res = time_utils::localtime_internal(timer, buf);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp
index 81652396e7fc2..a20fba4fb0aa1 100644
--- a/libc/src/time/mktime.cpp
+++ b/libc/src/time/mktime.cpp
@@ -8,22 +8,31 @@
 
 #include "src/time/mktime.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/time/time_constants.h"
 #include "src/time/time_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
+  LIBC_CRASH_ON_NULLPTR(tm_out);
+
   auto mktime_result = time_utils::mktime_internal(tm_out);
-  if (!mktime_result)
-    return time_utils::out_of_range();
+  if (!mktime_result) {
+    libc_errno = EOVERFLOW;
+    return time_constants::OUT_OF_RANGE_RETURN_VALUE;
+  }
 
   time_t seconds = *mktime_result;
 
   // Update the tm structure's year, month, day, etc. from seconds.
-  if (time_utils::update_from_seconds(seconds, tm_out) < 0)
-    return time_utils::out_of_range();
+  auto status = time_utils::update_from_seconds(seconds, tm_out);
+  if (!status) {
+    libc_errno = status.error();
+    return time_constants::OUT_OF_RANGE_RETURN_VALUE;
+  }
 
   return seconds;
 }
diff --git a/libc/src/time/strftime.cpp b/libc/src/time/strftime.cpp
index af0f438d22cab..71e1e99080e56 100644
--- a/libc/src/time/strftime.cpp
+++ b/libc/src/time/strftime.cpp
@@ -11,6 +11,7 @@
 #include "hdr/types/struct_tm.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/stdio/printf_core/writer.h"
 #include "src/time/strftime_core/strftime_main.h"
 
@@ -19,6 +20,11 @@ namespace LIBC_NAMESPACE_DECL {
 LLVM_LIBC_FUNCTION(size_t, strftime,
                    (char *__restrict buffer, size_t buffsz,
                     const char *__restrict format, const tm *timeptr)) {
+  if (buffsz > 0)
+    LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(format);
+  LIBC_CRASH_ON_NULLPTR(timeptr);
+
   printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
   printf_core::Writer writer(wb);
   auto ret = strftime_core::strftime_main(&writer, format, timeptr);
diff --git a/libc/src/time/strftime_core/CMakeLists.txt b/libc/src/time/strftime_core/CMakeLists.txt
index a9aa573cc9a63..31eb41aa1647f 100644
--- a/libc/src/time/strftime_core/CMakeLists.txt
+++ b/libc/src/time/strftime_core/CMakeLists.txt
@@ -33,6 +33,7 @@ add_header_library(
     libc.src.stdio.printf_core.writer
     libc.src.__support.CPP.string_view
     libc.src.__support.integer_to_string
+    libc.src.__support.error_or
 )
 
 add_header_library(
diff --git a/libc/src/time/strftime_core/composite_converter.h b/libc/src/time/strftime_core/composite_converter.h
index 53cb7e536a0e5..0417d8edf1e36 100644
--- a/libc/src/time/strftime_core/composite_converter.h
+++ b/libc/src/time/strftime_core/composite_converter.h
@@ -22,7 +22,7 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
-LIBC_INLINE IntFormatSection
+LIBC_INLINE ErrorOr<IntFormatSection>
 get_specific_int_format(const tm *timeptr, const FormatSection &base_to_conv,
                         char new_conv_name, int TRAILING_CONV_LEN = -1) {
   // a negative padding will be treated as the default
@@ -32,7 +32,10 @@ get_specific_int_format(const tm *timeptr, const FormatSection &base_to_conv,
   new_conv.conv_name = new_conv_name;
   new_conv.min_width = NEW_MIN_WIDTH;
 
-  IntFormatSection result = get_int_format(new_conv, timeptr);
+  auto result_or = get_int_format(new_conv, timeptr);
+  if (!result_or)
+    return cpp::unexpected(result_or.error());
+  IntFormatSection result = result_or.value();
 
   // If the user set the padding, but it's below the width of the trailing
   // conversions, then there should be no padding.
@@ -54,9 +57,21 @@ LIBC_INLINE int convert_date_us(printf_core::Writer<write_mode> *writer,
   IntFormatSection mon_conv;
   IntFormatSection mday_conv;
 
-  mon_conv = get_specific_int_format(timeptr, to_conv, 'm', TRAILING_CONV_LEN);
-  mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
-  year_conv = get_specific_int_format(timeptr, to_conv, 'y');
+  auto mon_conv_or =
+      get_specific_int_format(timeptr, to_conv, 'm', TRAILING_CONV_LEN);
+  if (!mon_conv_or)
+    return -mon_conv_or.error();
+  mon_conv = mon_conv_or.value();
+
+  auto mday_conv_or = get_specific_int_format(timeptr, to_conv, 'd');
+  if (!mday_conv_or)
+    return -mday_conv_or.error();
+  mday_conv = mday_conv_or.value();
+
+  auto year_conv_or = get_specific_int_format(timeptr, to_conv, 'y');
+  if (!year_conv_or)
+    return -year_conv_or.error();
+  year_conv = year_conv_or.value();
 
   RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mon_conv));
   RET_IF_RESULT_NEGATIVE(writer->write('/'));
@@ -79,9 +94,21 @@ LIBC_INLINE int convert_date_iso(printf_core::Writer<write_mode> *writer,
   IntFormatSection mon_conv;
   IntFormatSection mday_conv;
 
-  year_conv = get_specific_int_format(timeptr, to_conv, 'Y', TRAILING_CONV_LEN);
-  mon_conv = get_specific_int_format(timeptr, to_conv, 'm');
-  mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
+  auto year_conv_or =
+      get_specific_int_format(timeptr, to_conv, 'Y', TRAILING_CONV_LEN);
+  if (!year_conv_or)
+    return -year_conv_or.error();
+  year_conv = year_conv_or.value();
+
+  auto mon_conv_or = get_specific_int_format(timeptr, to_conv, 'm');
+  if (!mon_conv_or)
+    return -mon_conv_or.error();
+  mon_conv = mon_conv_or.value();
+
+  auto mday_conv_or = get_specific_int_format(timeptr, to_conv, 'd');
+  if (!mday_conv_or)
+    return -mday_conv_or.error();
+  mday_conv = mday_conv_or.value();
 
   RET_IF_RESULT_NEGATIVE(write_padded_int(writer, year_conv));
   RET_IF_RESULT_NEGATIVE(writer->write('-'));
@@ -107,9 +134,21 @@ LIBC_INLINE int convert_time_am_pm(printf_core::Writer<write_mode> *writer,
 
   const time_utils::TMReader time_reader(timeptr);
 
-  hour_conv = get_specific_int_format(timeptr, to_conv, 'I', TRAILING_CONV_LEN);
-  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
-  sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
+  auto hour_conv_or =
+      get_specific_int_format(timeptr, to_conv, 'I', TRAILING_CONV_LEN);
+  if (!hour_conv_or)
+    return -hour_conv_or.error();
+  hour_conv = hour_conv_or.value();
+
+  auto min_conv_or = get_specific_int_format(timeptr, to_conv, 'M');
+  if (!min_conv_or)
+    return -min_conv_or.error();
+  min_conv = min_conv_or.value();
+
+  auto sec_conv_or = get_specific_int_format(timeptr, to_conv, 'S');
+  if (!sec_conv_or)
+    return -sec_conv_or.error();
+  sec_conv = sec_conv_or.value();
 
   RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
   RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -133,8 +172,16 @@ LIBC_INLINE int convert_time_minute(printf_core::Writer<write_mode> *writer,
   IntFormatSection hour_conv;
   IntFormatSection min_conv;
 
-  hour_conv = get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
-  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+  auto hour_conv_or =
+      get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
+  if (!hour_conv_or)
+    return -hour_conv_or.error();
+  hour_conv = hour_conv_or.value();
+
+  auto min_conv_or = get_specific_int_format(timeptr, to_conv, 'M');
+  if (!min_conv_or)
+    return -min_conv_or.error();
+  min_conv = min_conv_or.value();
 
   RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
   RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -155,9 +202,21 @@ LIBC_INLINE int convert_time_second(printf_core::Writer<write_mode> *writer,
   IntFormatSection min_conv;
   IntFormatSection sec_conv;
 
-  hour_conv = get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
-  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
-  sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
+  auto hour_conv_or =
+      get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
+  if (!hour_conv_or)
+    return -hour_conv_or.error();
+  hour_conv = hour_conv_or.value();
+
+  auto min_conv_or = get_specific_int_format(timeptr, to_conv, 'M');
+  if (!min_conv_or)
+    return -min_conv_or.error();
+  min_conv = min_conv_or.value();
+
+  auto sec_conv_or = get_specific_int_format(timeptr, to_conv, 'S');
+  if (!sec_conv_or)
+    return -sec_conv_or.error();
+  sec_conv = sec_conv_or.value();
 
   RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
   RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -188,8 +247,15 @@ LIBC_INLINE int convert_full_date_time(printf_core::Writer<write_mode> *writer,
   IntFormatSection mday_conv;
   IntFormatSection year_conv;
 
-  mday_conv = get_specific_int_format(timeptr, to_conv, 'e');
-  year_conv = get_specific_int_format(timeptr, to_conv, 'Y');
+  auto mday_conv_or = get_specific_int_format(timeptr, to_conv, 'e');
+  if (!mday_conv_or)
+    return -mday_conv_or.error();
+  mday_conv = mday_conv_or.value();
+
+  auto year_conv_or = get_specific_int_format(timeptr, to_conv, 'Y');
+  if (!year_conv_or)
+    return -year_conv_or.error();
+  year_conv = year_conv_or.value();
 
   FormatSection raw_time_conv = to_conv;
   raw_time_conv.conv_name = 'T';
diff --git a/libc/src/time/strftime_core/converter.h b/libc/src/time/strftime_core/converter.h
index ff0faf35bbd90..7fc4b95eb51d5 100644
--- a/libc/src/time/strftime_core/converter.h
+++ b/libc/src/time/strftime_core/converter.h
@@ -1,10 +1,15 @@
-//===-- Format specifier converter for strftime -----------------*- 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Format specifier converter for strftime.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
 #define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
@@ -12,18 +17,22 @@
 #include "hdr/types/struct_tm.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/composite_converter.h"
 #include "src/time/strftime_core/core_structs.h"
-
-#include "composite_converter.h"
-#include "num_converter.h"
-#include "str_converter.h"
+#include "src/time/strftime_core/num_converter.h"
+#include "src/time/strftime_core/str_converter.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
-// convert will call a conversion function to convert the FormatSection into
-// its string representation, and then that will write the result to the
-// writer.
+/// Converts a format section and writes it to the writer.
+///
+/// \tparam write_mode The write mode for the writer.
+/// \param writer The writer to write the output to.
+/// \param to_conv The format section to convert.
+/// \param timeptr Pointer to the tm structure.
+/// \return Number of characters written on success, or a negative error code on
+/// failure.
 template <printf_core::WriteMode write_mode>
 int convert(printf_core::Writer<write_mode> *writer,
             const FormatSection &to_conv, const tm *timeptr) {
@@ -66,7 +75,6 @@ int convert(printf_core::Writer<write_mode> *writer,
   case 'y': // Year of the Century [00-99]
   case 'Y': // Full year
     return convert_int(writer, to_conv, timeptr);
-
     // string conversions
   case 'a': // Abbreviated weekday name
   case 'A': // Full weekday name
@@ -75,7 +83,6 @@ int convert(printf_core::Writer<write_mode> *writer,
   case 'h': // same as %b
   case 'p': // AM/PM designation
     return convert_str(writer, to_conv, timeptr);
-
     // composite conversions
   case 'c': // locale specified date and time
   case 'D': // %m/%d/%y (month/day/year)
@@ -93,10 +100,10 @@ int convert(printf_core::Writer<write_mode> *writer,
     // the standard says if no time zone is determinable, write no characters.
     // Leave this here until time zones are implemented.
     return 0;
+
   default:
     return writer->write(to_conv.raw_string);
   }
-  return 0;
 }
 
 } // namespace strftime_core
diff --git a/libc/src/time/strftime_core/num_converter.h b/libc/src/time/strftime_core/num_converter.h
index 7da9195aa4885..c44e2058b6653 100644
--- a/libc/src/time/strftime_core/num_converter.h
+++ b/libc/src/time/strftime_core/num_converter.h
@@ -11,6 +11,7 @@
 
 #include "hdr/types/struct_tm.h"
 #include "src/__support/CPP/string_view.h"
+#include "src/__support/error_or.h"
 #include "src/__support/integer_to_string.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/writer.h"
@@ -53,8 +54,8 @@ LIBC_INLINE int write_padded_int(printf_core::Writer<write_mode> *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
-                                            const tm *timeptr) {
+LIBC_INLINE ErrorOr<IntFormatSection>
+get_int_format(const FormatSection &to_conv, const tm *timeptr) {
   const time_utils::TMReader time_reader(timeptr);
 
   intmax_t raw_num;
@@ -111,10 +112,14 @@ LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
     raw_num = time_reader.get_min();
     result.pad_to_len = 2;
     break;
-  case 's': // Seconds since the epoch
-    raw_num = time_reader.get_epoch();
+  case 's': { // Seconds since the epoch
+    auto epoch_or = time_reader.get_epoch();
+    if (!epoch_or)
+      return cpp::unexpected(epoch_or.error());
+    raw_num = epoch_or.value();
     result.pad_to_len = 0;
     break;
+  }
   case 'S': // Second of the minute [00-60]
     raw_num = time_reader.get_sec();
     result.pad_to_len = 2;
@@ -191,8 +196,10 @@ LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
 template <printf_core::WriteMode write_mode>
 LIBC_INLINE int convert_int(printf_core::Writer<write_mode> *writer,
                             const FormatSection &to_conv, const tm *timeptr) {
-
-  return write_padded_int(writer, get_int_format(to_conv, timeptr));
+  auto num_info_or = get_int_format(to_conv, timeptr);
+  if (!num_info_or)
+    return -num_info_or.error();
+  return write_padded_int(writer, num_info_or.value());
 }
 
 } // namespace strftime_core
diff --git a/libc/src/time/strftime_core/str_converter.h b/libc/src/time/strftime_core/str_converter.h
index 13eccd3979ea6..3273dcf9a6b86 100644
--- a/libc/src/time/strftime_core/str_converter.h
+++ b/libc/src/time/strftime_core/str_converter.h
@@ -1,10 +1,15 @@
-//===-- String converter for strftime ---------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See htto_conv.times://llvm.org/LICENSE.txt for license information.
+// See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// String converter for strftime.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STR_CONVERTER_H
 #define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STR_CONVERTER_H
@@ -22,11 +27,19 @@ namespace strftime_core {
 
 static constexpr cpp::string_view OUT_OF_BOUNDS_STR = "?";
 
+/// Helper to unwrap optional string view, returning "?" if empty.
 LIBC_INLINE cpp::string_view
 unwrap_opt(cpp::optional<cpp::string_view> str_opt) {
   return str_opt.has_value() ? *str_opt : OUT_OF_BOUNDS_STR;
 }
 
+/// Converts string-based format specifiers (like %a, %Z) and writes to writer.
+///
+/// \tparam write_mode The write mode for the writer.
+/// \param writer The writer to write the output to.
+/// \param to_conv The format section to convert.
+/// \param timeptr Pointer to the tm structure.
+/// \return WRITE_OK on success, or negative value on error.
 template <printf_core::WriteMode write_mode>
 LIBC_INLINE int convert_str(printf_core::Writer<write_mode> *writer,
                             const FormatSection &to_conv, const tm *timeptr) {
@@ -56,9 +69,7 @@ LIBC_INLINE int convert_str(printf_core::Writer<write_mode> *writer,
     str = time_reader.get_am_pm();
     break;
   case 'Z': // Timezone name
-    // the standard says if no time zone is determinable, write no characters.
-    return WRITE_OK;
-    // str = time_reader.get_timezone_name();
+    str = time_reader.get_timezone_name();
     break;
   default:
     __builtin_trap(); // this should be unreachable, but trap if you hit it.
diff --git a/libc/src/time/strftime_l.cpp b/libc/src/time/strftime_l.cpp
index 7a70d2c5418ee..8a8950fee91cc 100644
--- a/libc/src/time/strftime_l.cpp
+++ b/libc/src/time/strftime_l.cpp
@@ -12,6 +12,7 @@
 #include "hdr/types/struct_tm.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/stdio/printf_core/writer.h"
 #include "src/time/strftime_core/strftime_main.h"
 
@@ -22,6 +23,11 @@ LLVM_LIBC_FUNCTION(size_t, strftime_l,
                    (char *__restrict buffer, size_t buffsz,
                     const char *__restrict format, const tm *timeptr,
                     locale_t)) {
+  if (buffsz > 0)
+    LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(format);
+  LIBC_CRASH_ON_NULLPTR(timeptr);
+
   printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
   printf_core::Writer writer(wb);
   auto ret = strftime_core::strftime_main(&writer, format, timeptr);
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index d749ff83019c5..dceaf751dde73 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -112,7 +112,7 @@ static int64_t computeRemainingYears(int64_t daysPerYears,
 //
 // Compute the number of months from the remaining days. Finally, adjust years
 // to be 1900 and months to be from January.
-int64_t update_from_seconds(time_t total_seconds, tm *tm) {
+ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm) {
   // Days in month starting from March in the year 2000.
   static const char daysInMonth[] = {31 /* Mar */, 30, 31, 30, 31, 31,
                                      30,           31, 30, 31, 31, 29};
@@ -125,7 +125,7 @@ int64_t update_from_seconds(time_t total_seconds, tm *tm) {
       static_cast<int64_t>(time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
 
   if (total_seconds < time_min || total_seconds > time_max)
-    return time_utils::out_of_range();
+    return cpp::unexpected(EOVERFLOW);
 
   int64_t seconds =
       total_seconds - time_constants::SECONDS_UNTIL2000_MARCH_FIRST;
@@ -191,7 +191,7 @@ int64_t update_from_seconds(time_t total_seconds, tm *tm) {
   }
 
   if (years > INT_MAX || years < INT_MIN)
-    return time_utils::out_of_range();
+    return cpp::unexpected(EOVERFLOW);
 
   // All the data (years, month and remaining days) was calculated from
   // March, 2000. Thus adjust the data to be from January, 1900.
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 18dc28760594c..358453f9551a2 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -1,10 +1,15 @@
-//===-- Collection of utils for mktime and friends --------------*- 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Collection of utils for mktime and friends.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_TIME_TIME_UTILS_H
 #define LLVM_LIBC_SRC_TIME_TIME_UTILS_H
@@ -16,53 +21,47 @@
 #include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/common.h"
+#include "src/__support/error_or.h"
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "time_constants.h"
+#include "src/time/time_constants.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace time_utils {
 
-// calculates the seconds from the epoch for tm_in. Does not update the struct,
-// you must call update_from_seconds for that.
+/// Calculates the seconds from the epoch for tm_in.
+/// Does not update the struct, call update_from_seconds for that.
+///
+/// \param tm_out Pointer to the tm structure to read.
+/// \return The epoch time on success, or nullopt on failure.
 cpp::optional<time_t> mktime_internal(const tm *tm_out);
 
-// Update the "tm" structure's year, month, etc. members from seconds.
-// "total_seconds" is the number of seconds since January 1st, 1970.
-int64_t update_from_seconds(time_t total_seconds, 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
-// variable.
-
-// POSIX.1-2017 requires this.
-LIBC_INLINE time_t out_of_range() {
-#ifdef EOVERFLOW
-  // For non-POSIX uses of the standard C time functions, where EOVERFLOW is
-  // not defined, it's OK not to set errno at all. The plain C standard doesn't
-  // require it.
-  libc_errno = EOVERFLOW;
+// For non-POSIX targets where EOVERFLOW is not defined, we map it to ERANGE.
+// The C standard does not require setting errno on overflow for these
+// functions, but doing so is compliant and provides consistent error reporting.
+#ifndef EOVERFLOW
+#define EOVERFLOW ERANGE
 #endif
-  return time_constants::OUT_OF_RANGE_RETURN_VALUE;
-}
 
-LIBC_INLINE void invalid_value() { libc_errno = EINVAL; }
+/// Update the "tm" structure's year, month, etc. members from seconds.
+///
+/// \param total_seconds The number of seconds since January 1st, 1970.
+/// \param tm Pointer to the tm structure to update.
+/// \return 0 on success, or error code on failure.
+ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm);
 
-LIBC_INLINE char *asctime(const tm *timeptr, char *buffer,
-                          size_t bufferLength) {
+LIBC_INLINE ErrorOr<char *> asctime(const tm *timeptr, char *buffer,
+                                    size_t bufferLength) {
   if (timeptr == nullptr || buffer == nullptr) {
-    invalid_value();
-    return nullptr;
+    return cpp::unexpected(EINVAL);
   }
   if (timeptr->tm_wday < 0 ||
       timeptr->tm_wday > (time_constants::DAYS_PER_WEEK - 1)) {
-    invalid_value();
-    return nullptr;
+    return cpp::unexpected(EINVAL);
   }
   if (timeptr->tm_mon < 0 ||
       timeptr->tm_mon > (time_constants::MONTHS_PER_YEAR - 1)) {
-    invalid_value();
-    return nullptr;
+    return cpp::unexpected(EINVAL);
   }
 
   // TODO(michaelr): move this to use the strftime machinery
@@ -74,39 +73,37 @@ LIBC_INLINE char *asctime(const tm *timeptr, char *buffer,
       timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec,
       time_constants::TIME_YEAR_BASE + timeptr->tm_year);
   if (written_size < 0)
-    return nullptr;
+    return cpp::unexpected(EINVAL);
   if (static_cast<size_t>(written_size) >= bufferLength) {
-    out_of_range();
-    return nullptr;
+    return cpp::unexpected(EOVERFLOW);
   }
   return buffer;
 }
 
-LIBC_INLINE tm *gmtime_internal(const time_t *timer, tm *result) {
+LIBC_INLINE ErrorOr<tm *> gmtime_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;
+  auto status = update_from_seconds(seconds, result);
+  if (!status) {
+    return cpp::unexpected(status.error());
   }
 
   return result;
 }
 
-LIBC_INLINE tm *localtime_internal(const time_t *timer, tm *result) {
+LIBC_INLINE ErrorOr<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;
+  auto status = update_from_seconds(seconds, result);
+  if (!status) {
+    return cpp::unexpected(status.error());
   }
 
   // TODO(zimirza): implement timezone database
-
   return result;
 }
 
-LIBC_INLINE tm *localtime(const time_t *t_ptr) {
+LIBC_INLINE ErrorOr<tm *> localtime(const time_t *t_ptr) {
+  if (t_ptr == nullptr)
+    return cpp::unexpected(EINVAL);
   static tm result;
   return time_utils::localtime_internal(t_ptr, &result);
 }
@@ -128,9 +125,11 @@ LIBC_INLINE constexpr int get_days_in_year(const int year) {
 
 // This is a helper class that takes a struct tm and lets you inspect its
 // values. Where relevant, results are bounds checked and returned as optionals.
-// This class does not, however, do data normalization except where necessary.
-// It will faithfully return a date of 9999-99-99, even though that makes no
-// sense.
+/// Class to read fields from tm structure.
+///
+/// This class does not, however, do data normalization except where necessary.
+/// It will faithfully return a date of 9999-99-99, even though that makes no
+/// sense.
 class TMReader final {
   const tm *timeptr;
 
@@ -338,15 +337,16 @@ class TMReader final {
     return BASE_YEAR + IS_NEXT_YEAR;
   }
 
-  LIBC_INLINE time_t get_epoch() const {
+  /// Get epoch time.
+  ///
+  /// \return Epoch time in seconds on success, or EOVERFLOW on failure.
+  LIBC_INLINE ErrorOr<time_t> get_epoch() const {
     auto seconds = mktime_internal(timeptr);
-    return seconds ? *seconds : time_utils::out_of_range();
+    if (!seconds)
+      return cpp::unexpected(EOVERFLOW);
+    return *seconds;
   }
 
-  // returns the timezone offset in microwave time:
-  // return (hours * 100) + minutes;
-  // This means that a shift of -4:30 is returned as -430, simplifying
-  // conversion.
   LIBC_INLINE constexpr int get_timezone_offset() const {
     // TODO: timezone support
     return 0;
diff --git a/libc/test/src/time/asctime_r_test.cpp b/libc/test/src/time/asctime_r_test.cpp
index 89634176e9236..0183c2aab9687 100644
--- a/libc/test/src/time/asctime_r_test.cpp
+++ b/libc/test/src/time/asctime_r_test.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
 #include "src/time/asctime_r.h"
 #include "src/time/time_constants.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
@@ -26,20 +27,14 @@ static inline char *call_asctime_r(struct tm *tm_data, int year, int month,
 // asctime and asctime_r share the same code and thus didn't repeat all the
 // tests from asctime. Added couple of validation tests.
 TEST_F(LlvmLibcAsctimeR, Nullptr) {
-  char *result;
-  result = LIBC_NAMESPACE::asctime_r(nullptr, nullptr);
-  ASSERT_ERRNO_EQ(EINVAL);
-  ASSERT_STREQ(nullptr, result);
-
   char buffer[LIBC_NAMESPACE::time_constants::ASCTIME_BUFFER_SIZE];
-  result = LIBC_NAMESPACE::asctime_r(nullptr, buffer);
-  ASSERT_ERRNO_EQ(EINVAL);
-  ASSERT_STREQ(nullptr, result);
-
   struct tm tm_data;
-  result = LIBC_NAMESPACE::asctime_r(&tm_data, nullptr);
-  ASSERT_ERRNO_EQ(EINVAL);
-  ASSERT_STREQ(nullptr, result);
+  EXPECT_DEATH([] { LIBC_NAMESPACE::asctime_r(nullptr, nullptr); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::asctime_r(nullptr, buffer); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::asctime_r(&tm_data, nullptr); },
+               WITH_SIGNAL(-1));
 }
 
 TEST_F(LlvmLibcAsctimeR, ValidDate) {
diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp
index 7635fbb8dc62e..3e089766aab81 100644
--- a/libc/test/src/time/asctime_test.cpp
+++ b/libc/test/src/time/asctime_test.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
 #include "src/time/asctime.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/Test.h"
@@ -23,10 +24,7 @@ static inline char *call_asctime(struct tm *tm_data, int year, int month,
 }
 
 TEST_F(LlvmLibcAsctime, Nullptr) {
-  char *result;
-  result = LIBC_NAMESPACE::asctime(nullptr);
-  ASSERT_ERRNO_EQ(EINVAL);
-  ASSERT_STREQ(nullptr, result);
+  EXPECT_DEATH([] { LIBC_NAMESPACE::asctime(nullptr); }, WITH_SIGNAL(SIGILL));
 }
 
 // Weekdays are in the range 0 to 6. Test passing invalid value in wday.
diff --git a/libc/test/src/time/ctime_r_test.cpp b/libc/test/src/time/ctime_r_test.cpp
index ee06c706734fb..f23d4a3464bc1 100644
--- a/libc/test/src/time/ctime_r_test.cpp
+++ b/libc/test/src/time/ctime_r_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/time/ctime_r.h"
 #include "src/time/time_constants.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
@@ -15,17 +16,13 @@
 using LlvmLibcCtimeR = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
 TEST_F(LlvmLibcCtimeR, Nullptr) {
-  char *result;
-  result = LIBC_NAMESPACE::ctime_r(nullptr, nullptr);
-  ASSERT_STREQ(nullptr, result);
-
   char buffer[LIBC_NAMESPACE::time_constants::ASCTIME_BUFFER_SIZE];
-  result = LIBC_NAMESPACE::ctime_r(nullptr, buffer);
-  ASSERT_STREQ(nullptr, result);
-
   time_t t;
-  result = LIBC_NAMESPACE::ctime_r(&t, nullptr);
-  ASSERT_STREQ(nullptr, result);
+  EXPECT_DEATH([] { LIBC_NAMESPACE::ctime_r(nullptr, nullptr); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::ctime_r(nullptr, buffer); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::ctime_r(&t, nullptr); }, WITH_SIGNAL(-1));
 }
 
 TEST_F(LlvmLibcCtimeR, ValidUnixTimestamp0) {
@@ -48,11 +45,22 @@ TEST_F(LlvmLibcCtimeR, ValidUnixTimestamp32Int) {
   ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result);
 }
 
+TEST_F(LlvmLibcCtimeR, ValidUnixTimestamp2039) {
+  char buffer[LIBC_NAMESPACE::time_constants::ASCTIME_BUFFER_SIZE];
+  time_t t;
+  char *result;
+  // 2039-01-01 00:00:00 UTC. Test with a valid buffer size.
+  t = 2177452800;
+  result = LIBC_NAMESPACE::ctime_r(&t, buffer);
+  ASSERT_STREQ("Sat Jan  1 00:00:00 2039\n", result);
+}
+
 TEST_F(LlvmLibcCtimeR, InvalidArgument) {
   char buffer[LIBC_NAMESPACE::time_constants::ASCTIME_BUFFER_SIZE];
   time_t t;
   char *result;
-  t = 2147483648;
+  t = 253402300800; // 10000-01-01 00:00:00 UTC (overflows 26-byte buffer)
   result = LIBC_NAMESPACE::ctime_r(&t, buffer);
+  ASSERT_ERRNO_EQ(EOVERFLOW);
   ASSERT_STREQ(nullptr, result);
 }
diff --git a/libc/test/src/time/ctime_test.cpp b/libc/test/src/time/ctime_test.cpp
index 34a645ff41c10..e96baad635f85 100644
--- a/libc/test/src/time/ctime_test.cpp
+++ b/libc/test/src/time/ctime_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/time/ctime.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/Test.h"
@@ -14,9 +15,7 @@
 using LlvmLibcCtime = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
 TEST_F(LlvmLibcCtime, nullptr) {
-  char *result;
-  result = LIBC_NAMESPACE::ctime(nullptr);
-  ASSERT_STREQ(nullptr, result);
+  EXPECT_DEATH([] { LIBC_NAMESPACE::ctime(nullptr); }, WITH_SIGNAL(-1));
 }
 
 TEST_F(LlvmLibcCtime, ValidUnixTimestamp0) {
@@ -35,10 +34,19 @@ TEST_F(LlvmLibcCtime, ValidUnixTimestamp32Int) {
   ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result);
 }
 
+TEST_F(LlvmLibcCtime, ValidUnixTimestamp2039) {
+  time_t t;
+  char *result;
+  t = 2177452800; // 2039-01-01 00:00:00 UTC
+  result = LIBC_NAMESPACE::ctime(&t);
+  ASSERT_STREQ("Sat Jan  1 00:00:00 2039\n", result);
+}
+
 TEST_F(LlvmLibcCtime, InvalidArgument) {
   time_t t;
   char *result;
-  t = 2147483648;
+  t = 253402300800; // 10000-01-01 00:00:00 UTC (overflows 26-byte buffer)
   result = LIBC_NAMESPACE::ctime(&t);
+  ASSERT_ERRNO_EQ(EOVERFLOW);
   ASSERT_STREQ(nullptr, result);
 }
diff --git a/libc/test/src/time/gmtime_r_test.cpp b/libc/test/src/time/gmtime_r_test.cpp
index 8026d37618868..1c5058b6654db 100644
--- a/libc/test/src/time/gmtime_r_test.cpp
+++ b/libc/test/src/time/gmtime_r_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/time/gmtime_r.h"
 #include "src/time/time_constants.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
@@ -56,3 +57,14 @@ TEST_F(LlvmLibcGmTimeR, Max64BitYear) {
       *tm_data_ptr);
   EXPECT_TM_EQ(*tm_data_ptr, tm_data);
 }
+
+TEST_F(LlvmLibcGmTimeR, NullPtr) {
+  struct tm tm_data;
+  time_t seconds = 0;
+  EXPECT_DEATH([] { LIBC_NAMESPACE::gmtime_r(nullptr, nullptr); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::gmtime_r(nullptr, &tm_data); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::gmtime_r(&seconds, nullptr); },
+               WITH_SIGNAL(-1));
+}
diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp
index 6e28e5987fde5..b5880ceba9ed6 100644
--- a/libc/test/src/time/gmtime_test.cpp
+++ b/libc/test/src/time/gmtime_test.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
 #include "hdr/types/struct_tm.h"
 #include "src/__support/CPP/limits.h" // INT_MAX, INT_MIN
 #include "src/time/gmtime.h"
@@ -37,6 +38,10 @@ TEST_F(LlvmLibcGmTime, OutOfRange) {
   ASSERT_ERRNO_EQ(EOVERFLOW);
 }
 
+TEST_F(LlvmLibcGmTime, NullPtr) {
+  EXPECT_DEATH([] { LIBC_NAMESPACE::gmtime(nullptr); }, WITH_SIGNAL(-1));
+}
+
 TEST_F(LlvmLibcGmTime, InvalidSeconds) {
   time_t seconds = 0;
   struct tm *tm_data = nullptr;
diff --git a/libc/test/src/time/localtime_r_test.cpp b/libc/test/src/time/localtime_r_test.cpp
index 8f7a79ef264a6..9ad73c8a0add4 100644
--- a/libc/test/src/time/localtime_r_test.cpp
+++ b/libc/test/src/time/localtime_r_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/time/localtime_r.h"
 #include "test/UnitTest/Test.h"
 
@@ -34,9 +35,15 @@ TEST(LlvmLibcLocaltimeR, ValidUnixTimestamp0) {
   ASSERT_EQ(0, result->tm_isdst);
 }
 
-TEST(LlvmLibcLocaltime, NullPtr) {
+TEST(LlvmLibcLocaltimeR, NullPtr) {
+  struct tm input;
+  time_t timer = 0;
   EXPECT_DEATH([] { LIBC_NAMESPACE::localtime_r(nullptr, nullptr); },
-               WITH_SIGNAL(4));
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::localtime_r(nullptr, &input); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([&] { LIBC_NAMESPACE::localtime_r(&timer, nullptr); },
+               WITH_SIGNAL(-1));
 }
 
 // TODO(zimirza): These tests does not expect the correct output of localtime as
diff --git a/libc/test/src/time/localtime_test.cpp b/libc/test/src/time/localtime_test.cpp
index 144060c86cfc2..37974d27771dc 100644
--- a/libc/test/src/time/localtime_test.cpp
+++ b/libc/test/src/time/localtime_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/time/localtime.h"
 #include "test/UnitTest/Test.h"
 
@@ -25,7 +26,7 @@ TEST(LlvmLibcLocaltime, ValidUnixTimestamp0) {
 }
 
 TEST(LlvmLibcLocaltime, NullPtr) {
-  EXPECT_DEATH([] { LIBC_NAMESPACE::localtime(nullptr); }, WITH_SIGNAL(4));
+  EXPECT_DEATH([] { LIBC_NAMESPACE::localtime(nullptr); }, WITH_SIGNAL(-1));
 }
 
 // TODO(zimirza): These tests does not expect the correct output of localtime as



More information about the libc-commits mailing list