diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -250,6 +250,7 @@ let Functions = [ "gmtime", + "gmtime_r", "mktime", ]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -177,6 +177,7 @@ # time.h entrypoints libc.src.time.gmtime + libc.src.time.gmtime_r libc.src.time.mktime # unistd.h entrypoints diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -603,6 +603,14 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "gmtime_r", + RetValSpec, + [ + ArgSpec, + ArgSpec, + ] + >, FunctionSpec< "mktime", RetValSpec, diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -4,6 +4,10 @@ time_utils.cpp HDRS time_utils.h + DEPENDS + libc.include.errno + libc.include.time + libc.src.errno.__errno_location ) add_entrypoint_object( @@ -14,9 +18,18 @@ gmtime.h DEPENDS .time_utils - libc.include.errno libc.include.time - libc.src.errno.__errno_location +) + +add_entrypoint_object( + gmtime_r + SRCS + gmtime_r.cpp + HDRS + gmtime_r.h + DEPENDS + .time_utils + libc.include.time ) add_entrypoint_object( diff --git a/libc/src/time/gmtime.cpp b/libc/src/time/gmtime.cpp --- a/libc/src/time/gmtime.cpp +++ b/libc/src/time/gmtime.cpp @@ -10,20 +10,11 @@ #include "src/__support/common.h" #include "src/time/time_utils.h" -#include - namespace __llvm_libc { LLVM_LIBC_FUNCTION(struct tm *, gmtime, (const time_t *timer)) { static struct tm tm_out; - time_t seconds = *timer; - // Update the tm structure's year, month, day, etc. from seconds. - if (time_utils::UpdateFromSeconds(seconds, &tm_out) < 0) { - time_utils::OutOfRange(); - return nullptr; - } - - return &tm_out; + return time_utils::gmtime_internal(timer, &tm_out); } } // namespace __llvm_libc diff --git a/libc/src/time/gmtime_r.h b/libc/src/time/gmtime_r.h new file mode 100644 --- /dev/null +++ b/libc/src/time/gmtime_r.h @@ -0,0 +1,22 @@ +//===-- Implementation header of gmtime_r -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_TIME_GMTIME_R_H +#define LLVM_LIBC_SRC_TIME_GMTIME_R_H + +#include + +namespace __llvm_libc { + +struct tm *gmtime_r(const time_t *timer, struct tm *result); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_GMTIME_R_H + +#include "include/time.h" diff --git a/libc/src/time/gmtime_r.cpp b/libc/src/time/gmtime_r.cpp new file mode 100644 --- /dev/null +++ b/libc/src/time/gmtime_r.cpp @@ -0,0 +1,20 @@ +//===-- Implementation of gmtime_r function -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/time/gmtime_r.h" +#include "src/__support/common.h" +#include "src/time/time_utils.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(struct tm *, gmtime_r, + (const time_t *timer, struct tm *result)) { + return time_utils::gmtime_internal(timer, result); +} + +} // namespace __llvm_libc diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h --- a/libc/src/time/time_utils.h +++ b/libc/src/time/time_utils.h @@ -53,15 +53,27 @@ static constexpr time_t OutOfRangeReturnValue = -1; }; +// Update the "tm" structure's year, month, etc. members from seconds. +// "total_seconds" is the number of seconds since January 1st, 1970. +extern int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm); + // POSIX.1-2017 requires this. static inline time_t OutOfRange() { llvmlibc_errno = EOVERFLOW; return static_cast(-1); } -// Update the "tm" structure's year, month, etc. members from seconds. -// "total_seconds" is the number of seconds since January 1st, 1970. -extern int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm); +static inline struct tm *gmtime_internal(const time_t *timer, + struct tm *result) { + int64_t seconds = *timer; + // Update the tm structure's year, month, day, etc. from seconds. + if (UpdateFromSeconds(seconds, result) < 0) { + OutOfRange(); + return nullptr; + } + + return result; +} } // namespace time_utils } // namespace __llvm_libc diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -1,15 +1,37 @@ add_libc_testsuite(libc_time_unittests) add_libc_unittest( - mktime + gmtime SUITE libc_time_unittests SRCS gmtime_test.cpp - mktime_test.cpp HDRS TmMatcher.h DEPENDS libc.src.time.gmtime +) + +add_libc_unittest( + gmtime_r + SUITE + libc_time_unittests + SRCS + gmtime_r_test.cpp + HDRS + TmMatcher.h + DEPENDS + libc.src.time.gmtime_r +) + +add_libc_unittest( + mktime + SUITE + libc_time_unittests + SRCS + mktime_test.cpp + HDRS + TmMatcher.h + DEPENDS libc.src.time.mktime ) diff --git a/libc/test/src/time/gmtime_r_test.cpp b/libc/test/src/time/gmtime_r_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/time/gmtime_r_test.cpp @@ -0,0 +1,57 @@ +//===-- Unittests for gmtime_r --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/time/gmtime_r.h" +#include "src/time/time_utils.h" +#include "test/src/time/TmMatcher.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::time_utils::TimeConstants; + +// gmtime and gmtime_r share the same code and thus didn't repeat all the tests +// from gmtime. Added couple of validation tests. +TEST(LlvmLibcGmTimeR, EndOf32BitEpochYear) { + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + time_t seconds = 0x7FFFFFFF; + struct tm tm_data; + struct tm *tm_data_ptr; + tm_data_ptr = __llvm_libc::gmtime_r(&seconds, &tm_data); + EXPECT_TM_EQ((tm{7, // sec + 14, // min + 3, // hr + 19, // day + 0, // tm_mon starts with 0 for Jan + 2038 - TimeConstants::TimeYearBase, // year + 2, // wday + 7, // yday + 0}), + *tm_data_ptr); + EXPECT_TM_EQ(*tm_data_ptr, tm_data); +} + +TEST(LlvmLibcGmTimeR, Max64BitYear) { + if (sizeof(time_t) == 4) + return; + // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. + time_t seconds = 67767976202043050; + struct tm tm_data; + struct tm *tm_data_ptr; + tm_data_ptr = __llvm_libc::gmtime_r(&seconds, &tm_data); + EXPECT_TM_EQ((tm{50, // sec + 50, // min + 12, // hr + 1, // day + 0, // tm_mon starts with 0 for Jan + 2147483647 - TimeConstants::TimeYearBase, // year + 2, // wday + 50, // yday + 0}), + *tm_data_ptr); + EXPECT_TM_EQ(*tm_data_ptr, tm_data); +}