[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 11:59:42 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 1/3] [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
>From 018310847fc3fa9d30cb346abc6a00bfdf949eb9 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 11 Jun 2026 17:14:45 +0100
Subject: [PATCH 2/3] [libc] Use constexpr for EOVERFLOW and simplify strftime
Replaced target-specific #define EOVERFLOW fallback with constexpr
TIME_OVERFLOW in time_utils.h.
Simplified composite_converter.h by removing error-checking boilerplate,
as composite formats do not use %s (the only format that can fail).
Fixed a potential signed overflow UB in mktime_internal by casting
tm_year to int64_t before adding TIME_YEAR_BASE.
Updated unit tests to use LIBC_NAMESPACE::time_utils::TIME_OVERFLOW.
Assisted-by: Automated tooling, human reviewed.
---
libc/src/time/mktime.cpp | 2 +-
.../time/strftime_core/composite_converter.h | 115 +++---------------
libc/src/time/time_utils.cpp | 7 +-
libc/src/time/time_utils.h | 21 ++--
libc/test/src/time/asctime_test.cpp | 4 +-
libc/test/src/time/ctime_r_test.cpp | 3 +-
libc/test/src/time/ctime_test.cpp | 3 +-
libc/test/src/time/gmtime_test.cpp | 6 +-
libc/test/src/time/mktime_test.cpp | 6 +-
9 files changed, 50 insertions(+), 117 deletions(-)
diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp
index a20fba4fb0aa1..f9323d1dec946 100644
--- a/libc/src/time/mktime.cpp
+++ b/libc/src/time/mktime.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
auto mktime_result = time_utils::mktime_internal(tm_out);
if (!mktime_result) {
- libc_errno = EOVERFLOW;
+ libc_errno = time_utils::TIME_OVERFLOW;
return time_constants::OUT_OF_RANGE_RETURN_VALUE;
}
diff --git a/libc/src/time/strftime_core/composite_converter.h b/libc/src/time/strftime_core/composite_converter.h
index 0417d8edf1e36..26300c0daeefb 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 ErrorOr<IntFormatSection>
+LIBC_INLINE 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,10 +32,8 @@ 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;
- auto result_or = get_int_format(new_conv, timeptr);
- if (!result_or)
- return cpp::unexpected(result_or.error());
- IntFormatSection result = result_or.value();
+ // We only call this for formats that cannot fail (not %s).
+ IntFormatSection result = get_int_format(new_conv, timeptr).value();
// If the user set the padding, but it's below the width of the trailing
// conversions, then there should be no padding.
@@ -53,25 +51,11 @@ LIBC_INLINE int convert_date_us(printf_core::Writer<write_mode> *writer,
// we only pad the first conversion, and we assume all the other values are in
// their valid ranges.
constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof("/01/02")
- IntFormatSection year_conv;
- IntFormatSection mon_conv;
- IntFormatSection mday_conv;
- auto mon_conv_or =
+ IntFormatSection mon_conv =
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();
+ IntFormatSection mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
+ IntFormatSection year_conv = get_specific_int_format(timeptr, to_conv, 'y');
RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mon_conv));
RET_IF_RESULT_NEGATIVE(writer->write('/'));
@@ -90,25 +74,11 @@ LIBC_INLINE int convert_date_iso(printf_core::Writer<write_mode> *writer,
// we only pad the first conversion, and we assume all the other values are in
// their valid ranges.
constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof("-01-02")
- IntFormatSection year_conv;
- IntFormatSection mon_conv;
- IntFormatSection mday_conv;
- auto year_conv_or =
+ IntFormatSection year_conv =
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();
+ IntFormatSection mon_conv = get_specific_int_format(timeptr, to_conv, 'm');
+ IntFormatSection mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
RET_IF_RESULT_NEGATIVE(write_padded_int(writer, year_conv));
RET_IF_RESULT_NEGATIVE(writer->write('-'));
@@ -128,27 +98,13 @@ LIBC_INLINE int convert_time_am_pm(printf_core::Writer<write_mode> *writer,
// their valid ranges.
constexpr int TRAILING_CONV_LEN =
1 + 2 + 1 + 2 + 1 + 2; // sizeof(":01:02 AM")
- IntFormatSection hour_conv;
- IntFormatSection min_conv;
- IntFormatSection sec_conv;
const time_utils::TMReader time_reader(timeptr);
- auto hour_conv_or =
+ IntFormatSection hour_conv =
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();
+ IntFormatSection min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+ IntFormatSection sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -169,19 +125,10 @@ LIBC_INLINE int convert_time_minute(printf_core::Writer<write_mode> *writer,
// we only pad the first conversion, and we assume all the other values are in
// their valid ranges.
constexpr int TRAILING_CONV_LEN = 1 + 2; // sizeof(":01")
- IntFormatSection hour_conv;
- IntFormatSection min_conv;
- auto hour_conv_or =
+ IntFormatSection hour_conv =
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();
+ IntFormatSection min_conv = get_specific_int_format(timeptr, to_conv, 'M');
RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -198,25 +145,11 @@ LIBC_INLINE int convert_time_second(printf_core::Writer<write_mode> *writer,
// we only pad the first conversion, and we assume all the other values are in
// their valid ranges.
constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof(":01:02")
- IntFormatSection hour_conv;
- IntFormatSection min_conv;
- IntFormatSection sec_conv;
- auto hour_conv_or =
+ IntFormatSection hour_conv =
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();
+ IntFormatSection min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+ IntFormatSection sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
RET_IF_RESULT_NEGATIVE(writer->write(':'));
@@ -244,18 +177,8 @@ LIBC_INLINE int convert_full_date_time(printf_core::Writer<write_mode> *writer,
cpp::string_view wday_str = unwrap_opt(time_reader.get_weekday_short_name());
cpp::string_view month_str = unwrap_opt(time_reader.get_month_short_name());
- IntFormatSection mday_conv;
- IntFormatSection year_conv;
-
- 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();
+ IntFormatSection mday_conv = get_specific_int_format(timeptr, to_conv, 'e');
+ IntFormatSection year_conv = get_specific_int_format(timeptr, to_conv, 'Y');
FormatSection raw_time_conv = to_conv;
raw_time_conv.conv_name = 'T';
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index dceaf751dde73..5ef562a246b85 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -20,7 +20,8 @@ namespace time_utils {
cpp::optional<time_t> mktime_internal(const tm *tm_out) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds.
- int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;
+ int64_t tm_year_from_base = static_cast<int64_t>(tm_out->tm_year) +
+ time_constants::TIME_YEAR_BASE;
// Years are ints. A 32-bit year will fit into a 64-bit time_t.
// A 64-bit year will not.
@@ -125,7 +126,7 @@ ErrorOr<int> 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 cpp::unexpected(EOVERFLOW);
+ return cpp::unexpected(TIME_OVERFLOW);
int64_t seconds =
total_seconds - time_constants::SECONDS_UNTIL2000_MARCH_FIRST;
@@ -191,7 +192,7 @@ ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm) {
}
if (years > INT_MAX || years < INT_MIN)
- return cpp::unexpected(EOVERFLOW);
+ return cpp::unexpected(TIME_OVERFLOW);
// 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 358453f9551a2..531068e0eb76d 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -36,13 +36,18 @@ namespace time_utils {
/// \return The epoch time on success, or nullopt on failure.
cpp::optional<time_t> mktime_internal(const tm *tm_out);
-// 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
+// EOVERFLOW is a POSIX extension and might not be defined on targets that only
+// support the C standard (e.g. generic baremetal). The C standard does not
+// require setting errno on overflow for time functions, but POSIX does.
+// We map it to ERANGE on non-POSIX targets to provide consistent error
+// reporting while remaining compliant.
+#ifdef EOVERFLOW
+constexpr int TIME_OVERFLOW = EOVERFLOW;
+#else
+constexpr int TIME_OVERFLOW = ERANGE;
#endif
+
/// Update the "tm" structure's year, month, etc. members from seconds.
///
/// \param total_seconds The number of seconds since January 1st, 1970.
@@ -75,7 +80,7 @@ LIBC_INLINE ErrorOr<char *> asctime(const tm *timeptr, char *buffer,
if (written_size < 0)
return cpp::unexpected(EINVAL);
if (static_cast<size_t>(written_size) >= bufferLength) {
- return cpp::unexpected(EOVERFLOW);
+ return cpp::unexpected(TIME_OVERFLOW);
}
return buffer;
}
@@ -339,11 +344,11 @@ class TMReader final {
/// Get epoch time.
///
- /// \return Epoch time in seconds on success, or EOVERFLOW on failure.
+ /// \return Epoch time in seconds on success, or TIME_OVERFLOW on failure.
LIBC_INLINE ErrorOr<time_t> get_epoch() const {
auto seconds = mktime_internal(timeptr);
if (!seconds)
- return cpp::unexpected(EOVERFLOW);
+ return cpp::unexpected(TIME_OVERFLOW);
return *seconds;
}
diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp
index 3e089766aab81..0d9d0631d2227 100644
--- a/libc/test/src/time/asctime_test.cpp
+++ b/libc/test/src/time/asctime_test.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "hdr/errno_macros.h"
+
+#include "src/time/time_utils.h"
#include "hdr/signal_macros.h"
#include "src/time/asctime.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
@@ -208,6 +210,6 @@ TEST_F(LlvmLibcAsctime, Max64BitYear) {
50, // sec
2, // wday
50); // yday
- ASSERT_ERRNO_EQ(EOVERFLOW);
+ ASSERT_ERRNO_EQ(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW);
ASSERT_STREQ(nullptr, result);
}
diff --git a/libc/test/src/time/ctime_r_test.cpp b/libc/test/src/time/ctime_r_test.cpp
index f23d4a3464bc1..1807757fc62b9 100644
--- a/libc/test/src/time/ctime_r_test.cpp
+++ b/libc/test/src/time/ctime_r_test.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "hdr/signal_macros.h"
+#include "src/time/time_utils.h"
#include "src/time/ctime_r.h"
#include "src/time/time_constants.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
@@ -61,6 +62,6 @@ TEST_F(LlvmLibcCtimeR, InvalidArgument) {
char *result;
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_ERRNO_EQ(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW);
ASSERT_STREQ(nullptr, result);
}
diff --git a/libc/test/src/time/ctime_test.cpp b/libc/test/src/time/ctime_test.cpp
index e96baad635f85..0d704f89cc3ec 100644
--- a/libc/test/src/time/ctime_test.cpp
+++ b/libc/test/src/time/ctime_test.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "hdr/signal_macros.h"
+#include "src/time/time_utils.h"
#include "src/time/ctime.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
@@ -47,6 +48,6 @@ TEST_F(LlvmLibcCtime, InvalidArgument) {
char *result;
t = 253402300800; // 10000-01-01 00:00:00 UTC (overflows 26-byte buffer)
result = LIBC_NAMESPACE::ctime(&t);
- ASSERT_ERRNO_EQ(EOVERFLOW);
+ ASSERT_ERRNO_EQ(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW);
ASSERT_STREQ(nullptr, result);
}
diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp
index b5880ceba9ed6..88b301d663c29 100644
--- a/libc/test/src/time/gmtime_test.cpp
+++ b/libc/test/src/time/gmtime_test.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "hdr/errno_macros.h"
+
+#include "src/time/time_utils.h"
#include "hdr/signal_macros.h"
#include "hdr/types/struct_tm.h"
#include "src/__support/CPP/limits.h" // INT_MAX, INT_MIN
@@ -26,7 +28,7 @@ TEST_F(LlvmLibcGmTime, OutOfRange) {
LIBC_NAMESPACE::time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
struct tm *tm_data = LIBC_NAMESPACE::gmtime(&seconds);
EXPECT_TRUE(tm_data == nullptr);
- ASSERT_ERRNO_EQ(EOVERFLOW);
+ ASSERT_ERRNO_EQ(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW);
seconds =
INT_MIN *
@@ -35,7 +37,7 @@ TEST_F(LlvmLibcGmTime, OutOfRange) {
1;
tm_data = LIBC_NAMESPACE::gmtime(&seconds);
EXPECT_TRUE(tm_data == nullptr);
- ASSERT_ERRNO_EQ(EOVERFLOW);
+ ASSERT_ERRNO_EQ(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW);
}
TEST_F(LlvmLibcGmTime, NullPtr) {
diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp
index 6927ec935b85a..5aed57129988a 100644
--- a/libc/test/src/time/mktime_test.cpp
+++ b/libc/test/src/time/mktime_test.cpp
@@ -18,9 +18,7 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
using LIBC_NAMESPACE::time_constants::Month;
-#ifndef EOVERFLOW
-#define EOVERFLOW 0
-#endif
+#include "src/time/time_utils.h"
static inline constexpr int tm_year(int year) {
return year - LIBC_NAMESPACE::time_constants::TIME_YEAR_BASE;
@@ -37,7 +35,7 @@ TEST(LlvmLibcMkTime, FailureSetsErrno) {
.tm_yday = 0,
.tm_isdst = 0};
EXPECT_THAT(static_cast<int>(LIBC_NAMESPACE::mktime(&tm_data)),
- Fails(EOVERFLOW));
+ Fails(LIBC_NAMESPACE::time_utils::TIME_OVERFLOW));
}
TEST(LlvmLibcMkTime, InvalidSeconds) {
>From 2f94c4d0e1561a1fbccd4a18c1712dc71ae14750 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 11 Jun 2026 19:40:15 +0100
Subject: [PATCH 3/3] [libc] Address review comments on PR 203298
---
libc/src/time/strftime_core/composite_converter.h | 3 ++-
libc/src/time/strftime_core/converter.h | 3 ++-
libc/src/time/time_utils.cpp | 4 ++--
libc/src/time/time_utils.h | 1 -
libc/test/src/time/asctime_test.cpp | 4 ++--
libc/test/src/time/ctime_r_test.cpp | 5 +++--
libc/test/src/time/ctime_test.cpp | 5 +++--
libc/test/src/time/gmtime_test.cpp | 2 +-
8 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/libc/src/time/strftime_core/composite_converter.h b/libc/src/time/strftime_core/composite_converter.h
index 26300c0daeefb..9d343f3aef611 100644
--- a/libc/src/time/strftime_core/composite_converter.h
+++ b/libc/src/time/strftime_core/composite_converter.h
@@ -32,7 +32,8 @@ 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;
- // We only call this for formats that cannot fail (not %s).
+ // This cannot error because we only call this for formats that cannot fail
+ // (not %s).
IntFormatSection result = get_int_format(new_conv, timeptr).value();
// If the user set the padding, but it's below the width of the trailing
diff --git a/libc/src/time/strftime_core/converter.h b/libc/src/time/strftime_core/converter.h
index 7fc4b95eb51d5..226327c6bf37a 100644
--- a/libc/src/time/strftime_core/converter.h
+++ b/libc/src/time/strftime_core/converter.h
@@ -75,6 +75,7 @@ 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
@@ -83,6 +84,7 @@ 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)
@@ -100,7 +102,6 @@ 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);
}
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 5ef562a246b85..945e555e8efaf 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -20,8 +20,8 @@ namespace time_utils {
cpp::optional<time_t> mktime_internal(const tm *tm_out) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds.
- int64_t tm_year_from_base = static_cast<int64_t>(tm_out->tm_year) +
- time_constants::TIME_YEAR_BASE;
+ int64_t tm_year_from_base =
+ static_cast<int64_t>(tm_out->tm_year) + time_constants::TIME_YEAR_BASE;
// Years are ints. A 32-bit year will fit into a 64-bit time_t.
// A 64-bit year will not.
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index 531068e0eb76d..208393a4aae8e 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -47,7 +47,6 @@ constexpr int TIME_OVERFLOW = EOVERFLOW;
constexpr int TIME_OVERFLOW = ERANGE;
#endif
-
/// Update the "tm" structure's year, month, etc. members from seconds.
///
/// \param total_seconds The number of seconds since January 1st, 1970.
diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp
index 0d9d0631d2227..634c718f27183 100644
--- a/libc/test/src/time/asctime_test.cpp
+++ b/libc/test/src/time/asctime_test.cpp
@@ -8,9 +8,9 @@
#include "hdr/errno_macros.h"
-#include "src/time/time_utils.h"
#include "hdr/signal_macros.h"
#include "src/time/asctime.h"
+#include "src/time/time_utils.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
#include "test/src/time/TmHelper.h"
@@ -26,7 +26,7 @@ static inline char *call_asctime(struct tm *tm_data, int year, int month,
}
TEST_F(LlvmLibcAsctime, Nullptr) {
- EXPECT_DEATH([] { LIBC_NAMESPACE::asctime(nullptr); }, WITH_SIGNAL(SIGILL));
+ EXPECT_DEATH([] { LIBC_NAMESPACE::asctime(nullptr); }, WITH_SIGNAL(-1));
}
// 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 1807757fc62b9..571bc310a1faf 100644
--- a/libc/test/src/time/ctime_r_test.cpp
+++ b/libc/test/src/time/ctime_r_test.cpp
@@ -7,9 +7,9 @@
//===----------------------------------------------------------------------===//
#include "hdr/signal_macros.h"
-#include "src/time/time_utils.h"
#include "src/time/ctime_r.h"
#include "src/time/time_constants.h"
+#include "src/time/time_utils.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
#include "test/src/time/TmHelper.h"
@@ -50,7 +50,8 @@ 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.
+ // 2039-01-01 00:00:00 UTC. Test with a valid buffer size. This is after the
+ // 32-bit time_t max.
t = 2177452800;
result = LIBC_NAMESPACE::ctime_r(&t, buffer);
ASSERT_STREQ("Sat Jan 1 00:00:00 2039\n", result);
diff --git a/libc/test/src/time/ctime_test.cpp b/libc/test/src/time/ctime_test.cpp
index 0d704f89cc3ec..e782423014c52 100644
--- a/libc/test/src/time/ctime_test.cpp
+++ b/libc/test/src/time/ctime_test.cpp
@@ -7,8 +7,8 @@
//===----------------------------------------------------------------------===//
#include "hdr/signal_macros.h"
-#include "src/time/time_utils.h"
#include "src/time/ctime.h"
+#include "src/time/time_utils.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
#include "test/src/time/TmHelper.h"
@@ -38,7 +38,8 @@ TEST_F(LlvmLibcCtime, ValidUnixTimestamp32Int) {
TEST_F(LlvmLibcCtime, ValidUnixTimestamp2039) {
time_t t;
char *result;
- t = 2177452800; // 2039-01-01 00:00:00 UTC
+ // 2039-01-01 00:00:00 UTC. This is after the 32-bit time_t max.
+ t = 2177452800;
result = LIBC_NAMESPACE::ctime(&t);
ASSERT_STREQ("Sat Jan 1 00:00:00 2039\n", result);
}
diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp
index 88b301d663c29..0a0d6d0ee4826 100644
--- a/libc/test/src/time/gmtime_test.cpp
+++ b/libc/test/src/time/gmtime_test.cpp
@@ -8,12 +8,12 @@
#include "hdr/errno_macros.h"
-#include "src/time/time_utils.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"
#include "src/time/time_constants.h"
+#include "src/time/time_utils.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
#include "test/src/time/TmMatcher.h"
More information about the libc-commits
mailing list