[libc] Introduce asctime, asctime_r to LLVM libc

[libc] Introduce asctime, asctime_r to LLVM libc

asctime and asctime_r share the same common code. They call asctime_internal
a static inline function.

asctime uses snprintf to return the string representation in a buffer.
It uses the following format (26 characters is the buffer size) as per
7.27.3.1 section in http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2478.pdf.
The buf parameter for asctime_r shall point to a buffer of at least 26 bytes.

snprintf(buf, 26, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",...)

Reviewed By: sivachandra

Differential Revision: https://reviews.llvm.org/D99686
This commit is contained in:
Raman Tenneti 2021-03-31 13:56:41 -07:00
parent 47ee47e1e8
commit a72499e475
14 changed files with 526 additions and 19 deletions

View File

@ -249,6 +249,8 @@ def TimeAPI : PublicAPI<"time.h"> {
];
let Functions = [
"asctime",
"asctime_r",
"gmtime",
"gmtime_r",
"mktime",

View File

@ -181,6 +181,8 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.threads.thrd_join
# time.h entrypoints
libc.src.time.asctime
libc.src.time.asctime_r
libc.src.time.gmtime
libc.src.time.gmtime_r
libc.src.time.mktime

View File

@ -599,6 +599,19 @@ def StdC : StandardSpec<"stdc"> {
],
[], // Enumerations
[
FunctionSpec<
"asctime",
RetValSpec<CharPtr>,
[ArgSpec<StructTmPtr>]
>,
FunctionSpec<
"asctime_r",
RetValSpec<CharPtr>,
[
ArgSpec<StructTmPtr>,
ArgSpec<CharPtr>,
]
>,
FunctionSpec<
"gmtime",
RetValSpec<StructTmPtr>,

View File

@ -10,6 +10,28 @@ add_object_library(
libc.src.errno.__errno_location
)
add_entrypoint_object(
asctime
SRCS
asctime.cpp
HDRS
asctime.h
DEPENDS
.time_utils
libc.include.time
)
add_entrypoint_object(
asctime_r
SRCS
asctime_r.cpp
HDRS
asctime_r.h
DEPENDS
.time_utils
libc.include.time
)
add_entrypoint_object(
gmtime
SRCS

22
libc/src/time/asctime.cpp Normal file
View File

@ -0,0 +1,22 @@
//===-- Implementation of asctime 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/asctime.h"
#include "src/__support/common.h"
#include "src/time/time_utils.h"
namespace __llvm_libc {
using __llvm_libc::time_utils::TimeConstants;
LLVM_LIBC_FUNCTION(char *, asctime, (const struct tm *timeptr)) {
static char buffer[TimeConstants::AsctimeBufferSize];
return time_utils::asctime(timeptr, buffer, TimeConstants::AsctimeMaxBytes);
}
} // namespace __llvm_libc

22
libc/src/time/asctime.h Normal file
View File

@ -0,0 +1,22 @@
//===-- Implementation header of asctime ------------------------*- 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_ASCTIME_H
#define LLVM_LIBC_SRC_TIME_ASCTIME_H
#include <time.h>
namespace __llvm_libc {
char *asctime(const struct tm *timeptr);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_TIME_ASCTIME_H
#include "include/time.h"

View File

@ -0,0 +1,22 @@
//===-- Implementation of asctime_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/asctime_r.h"
#include "src/__support/common.h"
#include "src/time/time_utils.h"
namespace __llvm_libc {
using __llvm_libc::time_utils::TimeConstants;
LLVM_LIBC_FUNCTION(char *, asctime_r,
(const struct tm *timeptr, char *buffer)) {
return time_utils::asctime(timeptr, buffer, TimeConstants::AsctimeMaxBytes);
}
} // namespace __llvm_libc

22
libc/src/time/asctime_r.h Normal file
View File

@ -0,0 +1,22 @@
//===-- Implementation header of asctime_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_ASCTIME_R_H
#define LLVM_LIBC_SRC_TIME_ASCTIME_R_H
#include <time.h>
namespace __llvm_libc {
char *asctime_r(const struct tm *timeptr, char *buffer);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_TIME_ASCTIME_R_H
#include "include/time.h"

View File

@ -9,6 +9,8 @@
#ifndef LLVM_LIBC_SRC_TIME_TIME_UTILS_H
#define LLVM_LIBC_SRC_TIME_TIME_UTILS_H
#include <stddef.h> // For size_t.
#include "include/errno.h"
#include "src/errno/llvmlibc_errno.h"
@ -33,6 +35,14 @@ struct TimeConstants {
static constexpr int NumberOfSecondsInLeapYear =
(DaysPerNonLeapYear + 1) * SecondsPerDay;
// For asctime the behavior is undefined if struct tm's tm_wday or tm_mon are
// not within the normal ranges as defined in <time.h>, or if struct tm's
// tm_year exceeds {INT_MAX}-1990, or if the below asctime_internal algorithm
// would attempt to generate more than 26 bytes of output (including the
// terminating null).
static constexpr int AsctimeBufferSize = 256;
static constexpr int AsctimeMaxBytes = 26;
/* 2000-03-01 (mod 400 year, immediately after feb29 */
static constexpr int64_t SecondsUntil2000MarchFirst =
(946684800LL + SecondsPerDay * (31 + 29));
@ -63,6 +73,46 @@ static inline time_t OutOfRange() {
return static_cast<time_t>(-1);
}
static inline void InvalidValue() { llvmlibc_errno = EINVAL; }
static inline char *asctime(const struct tm *timeptr, char *buffer,
size_t bufferLength) {
if (timeptr == nullptr || buffer == nullptr) {
InvalidValue();
return nullptr;
}
if (timeptr->tm_wday < 0 ||
timeptr->tm_wday > (TimeConstants::DaysPerWeek - 1)) {
InvalidValue();
return nullptr;
}
if (timeptr->tm_mon < 0 ||
timeptr->tm_mon > (TimeConstants::MonthsPerYear - 1)) {
InvalidValue();
return nullptr;
}
// TODO(rtenneti): i18n the following strings.
static const char *WeekDaysName[TimeConstants::DaysPerWeek] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *MonthsName[TimeConstants::MonthsPerYear] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int written_size = __builtin_snprintf(
buffer, bufferLength, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
WeekDaysName[timeptr->tm_wday], MonthsName[timeptr->tm_mon],
timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec,
TimeConstants::TimeYearBase + timeptr->tm_year);
if (written_size < 0)
return nullptr;
if (static_cast<size_t>(written_size) >= bufferLength) {
OutOfRange();
return nullptr;
}
return buffer;
}
static inline struct tm *gmtime_internal(const time_t *timer,
struct tm *result) {
int64_t seconds = *timer;

View File

@ -1,5 +1,31 @@
add_libc_testsuite(libc_time_unittests)
add_libc_unittest(
asctime
SUITE
libc_time_unittests
SRCS
asctime_test.cpp
HDRS
TmHelper.h
TmMatcher.h
DEPENDS
libc.src.time.asctime
)
add_libc_unittest(
asctime_r
SUITE
libc_time_unittests
SRCS
asctime_r_test.cpp
HDRS
TmHelper.h
TmMatcher.h
DEPENDS
libc.src.time.asctime_r
)
add_libc_unittest(
gmtime
SUITE
@ -31,6 +57,7 @@ add_libc_unittest(
SRCS
mktime_test.cpp
HDRS
TmHelper.h
TmMatcher.h
DEPENDS
libc.src.time.mktime

View File

@ -0,0 +1,42 @@
//===---- TmHelper.h ------------------------------------------*- 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_TEST_SRC_TIME_TM_HELPER_H
#define LLVM_LIBC_TEST_SRC_TIME_TM_HELPER_H
#include <time.h>
#include "src/time/time_utils.h"
using __llvm_libc::time_utils::TimeConstants;
namespace __llvm_libc {
namespace tmhelper {
namespace testing {
// A helper function to initialize tm data structure.
static inline void InitializeTmData(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec,
int wday, int yday) {
struct tm temp = {.tm_sec = sec,
.tm_min = min,
.tm_hour = hour,
.tm_mday = mday,
.tm_mon = month - 1, // tm_mon starts with 0 for Jan
// years since 1900
.tm_year = year - TimeConstants::TimeYearBase,
.tm_wday = wday,
.tm_yday = yday};
*tm_data = temp;
}
} // namespace testing
} // namespace tmhelper
} // namespace __llvm_libc
#endif // LLVM_LIBC_TEST_SRC_TIME_TM_HELPER_H

View File

@ -0,0 +1,60 @@
//===-- Unittests for asctime_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/asctime_r.h"
#include "src/time/time_utils.h"
#include "test/src/time/TmHelper.h"
#include "utils/UnitTest/Test.h"
using __llvm_libc::time_utils::TimeConstants;
static inline char *call_asctime_r(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec,
int wday, int yday, char *buffer) {
__llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday,
hour, min, sec, wday, yday);
return __llvm_libc::asctime_r(tm_data, buffer);
}
// asctime and asctime_r share the same code and thus didn't repeat all the
// tests from asctime. Added couple of validation tests.
TEST(LlvmLibcAsctimeR, Nullptr) {
char *result;
result = __llvm_libc::asctime_r(nullptr, nullptr);
ASSERT_EQ(EINVAL, llvmlibc_errno);
ASSERT_STREQ(nullptr, result);
char buffer[TimeConstants::AsctimeBufferSize];
result = __llvm_libc::asctime_r(nullptr, buffer);
ASSERT_EQ(EINVAL, llvmlibc_errno);
ASSERT_STREQ(nullptr, result);
struct tm tm_data;
result = __llvm_libc::asctime_r(&tm_data, nullptr);
ASSERT_EQ(EINVAL, llvmlibc_errno);
ASSERT_STREQ(nullptr, result);
}
TEST(LlvmLibcAsctimeR, ValidDate) {
char buffer[TimeConstants::AsctimeBufferSize];
struct tm tm_data;
char *result;
// 1970-01-01 00:00:00. Test with a valid buffer size.
result = call_asctime_r(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0, // yday
buffer);
ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result);
}

View File

@ -0,0 +1,215 @@
//===-- Unittests for asctime ---------------------------------------------===//
//
// 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/asctime.h"
#include "test/src/time/TmHelper.h"
#include "utils/UnitTest/Test.h"
static inline char *call_asctime(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec, int wday,
int yday) {
__llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday,
hour, min, sec, wday, yday);
return __llvm_libc::asctime(tm_data);
}
TEST(LlvmLibcAsctime, Nullptr) {
char *result;
result = __llvm_libc::asctime(nullptr);
ASSERT_EQ(EINVAL, llvmlibc_errno);
ASSERT_STREQ(nullptr, result);
}
// Weekdays are in the range 0 to 6. Test passing invalid value in wday.
TEST(LlvmLibcAsctime, InvalidWday) {
struct tm tm_data;
char *result;
// Test with wday = -1.
result = call_asctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
-1, // wday
0); // yday
ASSERT_EQ(EINVAL, llvmlibc_errno);
// Test with wday = 7.
result = call_asctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
7, // wday
0); // yday
ASSERT_EQ(EINVAL, llvmlibc_errno);
}
// Months are from January to December. Test passing invalid value in month.
TEST(LlvmLibcAsctime, InvalidMonth) {
struct tm tm_data;
char *result;
// Test with month = 0.
result = call_asctime(&tm_data,
1970, // year
0, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_EQ(EINVAL, llvmlibc_errno);
// Test with month = 13.
result = call_asctime(&tm_data,
1970, // year
13, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_EQ(EINVAL, llvmlibc_errno);
}
TEST(LlvmLibcAsctime, ValidWeekdays) {
struct tm tm_data;
char *result;
// 1970-01-01 00:00:00.
result = call_asctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result);
// 1970-01-03 00:00:00.
result = call_asctime(&tm_data,
1970, // year
1, // month
3, // day
0, // hr
0, // min
0, // sec
6, // wday
0); // yday
ASSERT_STREQ("Sat Jan 3 00:00:00 1970\n", result);
// 1970-01-04 00:00:00.
result = call_asctime(&tm_data,
1970, // year
1, // month
4, // day
0, // hr
0, // min
0, // sec
0, // wday
0); // yday
ASSERT_STREQ("Sun Jan 4 00:00:00 1970\n", result);
}
TEST(LlvmLibcAsctime, ValidMonths) {
struct tm tm_data;
char *result;
// 1970-01-01 00:00:00.
result = call_asctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_STREQ("Thu Jan 1 00:00:00 1970\n", result);
// 1970-02-01 00:00:00.
result = call_asctime(&tm_data,
1970, // year
2, // month
1, // day
0, // hr
0, // min
0, // sec
0, // wday
0); // yday
ASSERT_STREQ("Sun Feb 1 00:00:00 1970\n", result);
// 1970-12-31 23:59:59.
result = call_asctime(&tm_data,
1970, // year
12, // month
31, // day
23, // hr
59, // min
59, // sec
4, // wday
0); // yday
ASSERT_STREQ("Thu Dec 31 23:59:59 1970\n", result);
}
TEST(LlvmLibcAsctime, EndOf32BitEpochYear) {
struct tm tm_data;
char *result;
// Test for maximum value of a signed 32-bit integer.
// Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
result = call_asctime(&tm_data,
2038, // year
1, // month
19, // day
3, // hr
14, // min
7, // sec
2, // wday
7); // yday
ASSERT_STREQ("Tue Jan 19 03:14:07 2038\n", result);
}
TEST(LlvmLibcAsctime, Max64BitYear) {
if (sizeof(time_t) == 4)
return;
// Mon Jan 1 12:50:50 2170 (200 years from 1970),
struct tm tm_data;
char *result;
result = call_asctime(&tm_data,
2170, // year
1, // month
1, // day
12, // hr
50, // min
50, // sec
1, // wday
50); // yday
ASSERT_STREQ("Mon Jan 1 12:50:50 2170\n", result);
// Test for Tue Jan 1 12:50:50 in 2,147,483,647th year.
// This test would cause buffer overflow and thus asctime returns nullptr.
result = call_asctime(&tm_data,
2147483647, // year
1, // month
1, // day
12, // hr
50, // min
50, // sec
2, // wday
50); // yday
ASSERT_EQ(EOVERFLOW, llvmlibc_errno);
ASSERT_STREQ(nullptr, result);
}

View File

@ -9,6 +9,7 @@
#include "src/time/mktime.h"
#include "src/time/time_utils.h"
#include "test/ErrnoSetterMatcher.h"
#include "test/src/time/TmHelper.h"
#include "test/src/time/TmMatcher.h"
#include "utils/UnitTest/Test.h"
@ -20,33 +21,18 @@ using __llvm_libc::testing::ErrnoSetterMatcher::Fails;
using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds;
using __llvm_libc::time_utils::TimeConstants;
// A helper function to initialize tm data structure.
static inline void initialize_tm_data(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec,
int wday, int yday) {
struct tm temp = {.tm_sec = sec,
.tm_min = min,
.tm_hour = hour,
.tm_mday = mday,
.tm_mon = month - 1, // tm_mon starts with 0 for Jan
// years since 1900
.tm_year = year - TimeConstants::TimeYearBase,
.tm_wday = wday,
.tm_yday = yday};
*tm_data = temp;
}
static inline time_t call_mktime(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec, int wday,
int yday) {
initialize_tm_data(tm_data, year, month, mday, hour, min, sec, wday, yday);
__llvm_libc::tmhelper::testing::InitializeTmData(tm_data, year, month, mday,
hour, min, sec, wday, yday);
return __llvm_libc::mktime(tm_data);
}
TEST(LlvmLibcMkTime, FailureSetsErrno) {
struct tm tm_data;
initialize_tm_data(&tm_data, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1,
0, 0);
__llvm_libc::tmhelper::testing::InitializeTmData(
&tm_data, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1, 0, 0);
EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW));
}