[libc] Introduces gmtime_r to LLVM libc, based on C99/C2X/Single Unix Sp.

gmtime and gmtime_r share the same common code. They call gmtime_internal
a static inline function. Thus added only validation tests for gmtime_r.

Reviewed By: sivachandra

Differential Revision: https://reviews.llvm.org/D99046

GitOrigin-RevId: 8b35159ac720fcb9914e4931b416e17b0fbda771
diff --git a/config/linux/api.td b/config/linux/api.td
index c8dfd3b..36067fb 100644
--- a/config/linux/api.td
+++ b/config/linux/api.td
@@ -250,6 +250,7 @@
 
   let Functions = [
     "gmtime",
+    "gmtime_r",
     "mktime",
   ];
 }
diff --git a/config/linux/x86_64/entrypoints.txt b/config/linux/x86_64/entrypoints.txt
index 8dca712..28ce217 100644
--- a/config/linux/x86_64/entrypoints.txt
+++ b/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/spec/stdc.td b/spec/stdc.td
index 8afb37f..2b3602b 100644
--- a/spec/stdc.td
+++ b/spec/stdc.td
@@ -604,6 +604,14 @@
               [ArgSpec<TimeTTypePtr>]
           >,
           FunctionSpec<
+              "gmtime_r",
+              RetValSpec<StructTmPtr>,
+              [
+                  ArgSpec<TimeTTypePtr>,
+                  ArgSpec<StructTmPtr>,
+              ]
+          >,
+          FunctionSpec<
               "mktime",
               RetValSpec<TimeTType>,
               [ArgSpec<StructTmPtr>]
diff --git a/src/time/CMakeLists.txt b/src/time/CMakeLists.txt
index c11a658..03343cf 100644
--- a/src/time/CMakeLists.txt
+++ b/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/src/time/gmtime.cpp b/src/time/gmtime.cpp
index 04991a5..75ce859 100644
--- a/src/time/gmtime.cpp
+++ b/src/time/gmtime.cpp
@@ -10,20 +10,11 @@
 #include "src/__support/common.h"
 #include "src/time/time_utils.h"
 
-#include <limits.h>
-
 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/src/time/gmtime_r.cpp b/src/time/gmtime_r.cpp
new file mode 100644
index 0000000..67bf126
--- /dev/null
+++ b/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/src/time/gmtime_r.h b/src/time/gmtime_r.h
new file mode 100644
index 0000000..8e9fc94
--- /dev/null
+++ b/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 <time.h>
+
+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/src/time/time_utils.h b/src/time/time_utils.h
index c87124e..cca0300 100644
--- a/src/time/time_utils.h
+++ b/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<time_t>(-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/test/src/time/CMakeLists.txt b/test/src/time/CMakeLists.txt
index 690cdce..e3bf5e2 100644
--- a/test/src/time/CMakeLists.txt
+++ b/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/test/src/time/gmtime_r_test.cpp b/test/src/time/gmtime_r_test.cpp
new file mode 100644
index 0000000..037460c
--- /dev/null
+++ b/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);
+}