Skip to content

Commit

Permalink
[libc][c11] implement ctime (llvm#86567)
Browse files Browse the repository at this point in the history
  • Loading branch information
zimirza committed Sep 3, 2024
1 parent 079746d commit e84118c
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 0 deletions.
11 changes: 11 additions & 0 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ add_entrypoint_object(
libc.include.time
)

add_entrypoint_object(
ctime
SRCS
ctime.cpp
HDRS
ctime.h
DEPENDS
.time_utils
libc.include.time
)

add_entrypoint_object(
difftime
SRCS
Expand Down
23 changes: 23 additions & 0 deletions libc/src/time/ctime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===-- 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/ctime.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/time/time_utils.h"

namespace LIBC_NAMESPACE_DECL {

using LIBC_NAMESPACE::time_utils::TimeConstants;

LLVM_LIBC_FUNCTION(char *, ctime, (const struct tm *timeptr)) {
static char buffer[TimeConstants::CTIME_BUFFER_SIZE];
return time_utils::ctime(timeptr, buffer, TimeConstants::CTIME_MAX_BYTES);
}

} // namespace LIBC_NAMESPACE_DECL
21 changes: 21 additions & 0 deletions libc/src/time/ctime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header of ctime ------------------------*- 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_CTIME_H
#define LLVM_LIBC_SRC_TIME_CTIME_H

#include "src/__support/macros/config.h"
#include <time.h>

namespace LIBC_NAMESPACE_DECL {

char *ctime(const struct tm *timeptr);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_TIME_CTIME_H
46 changes: 46 additions & 0 deletions libc/src/time/time_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ struct TimeConstants {
static constexpr int ASCTIME_BUFFER_SIZE = 256;
static constexpr int ASCTIME_MAX_BYTES = 26;

static constexpr int CTIME_BUFFER_SIZE = 256;
static constexpr int CTIME_MAX_BYTES = 26;

/* 2000-03-01 (mod 400 year, immediately after feb29 */
static constexpr int64_t SECONDS_UNTIL2000_MARCH_FIRST =
(946684800LL + SECONDS_PER_DAY * (31 + 29));
Expand Down Expand Up @@ -145,6 +148,49 @@ LIBC_INLINE char *asctime(const struct tm *timeptr, char *buffer,
return buffer;
}

LIBC_INLINE char *ctime(const struct tm *timeptr, char *buffer,
size_t bufferLength) {
if (timeptr == nullptr || buffer == nullptr) {
invalid_value();
return nullptr;
}
if (timeptr->tm_wday < 0 ||
timeptr->tm_wday > (TimeConstants::DAYS_PER_WEEK - 1)) {
invalid_value();
return nullptr;
}
if (timeptr->tm_mon < 0 ||
timeptr->tm_mon > (TimeConstants::MONTHS_PER_YEAR - 1)) {
invalid_value();
return nullptr;
}

// TODO(rtenneti): i18n the following strings.
static const char *week_days_name[TimeConstants::DAYS_PER_WEEK] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

static const char *months_name[TimeConstants::MONTHS_PER_YEAR] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

// TODO(michaelr): look into removing this call to __builtin_snprintf that may
// be emitted as a call to snprintf. Alternatively, look into using our
// internal printf machinery.
int written_size = __builtin_snprintf(
buffer, bufferLength, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
week_days_name[timeptr->tm_wday], months_name[timeptr->tm_mon],
timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec,
TimeConstants::TIME_YEAR_BASE + timeptr->tm_year);
if (written_size < 0)
return nullptr;
if (static_cast<size_t>(written_size) >= bufferLength) {
out_of_range();
return nullptr;
}
return buffer;
}


LIBC_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.
Expand Down
215 changes: 215 additions & 0 deletions libc/test/src/time/ctime_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//===-- Unittests for ctime ---------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// make check-libc LIBC_TEST_TARGET=call_ctime VERBOSE=1
#include "src/errno/libc_errno.h"
#include "src/time/ctime.h"
#include "test/UnitTest/Test.h"
#include "test/src/time/TmHelper.h"

static inline char *call_ctime(struct tm *tm_data, int year, int month,
int mday, int hour, int min, int sec, int wday,
int yday) {
LIBC_NAMESPACE::tmhelper::testing::initialize_tm_data(
tm_data, year, month, mday, hour, min, sec, wday, yday);
return LIBC_NAMESPACE::ctime(tm_data);
}

TEST(LlvmLibcCtime, Nullptr) {
char *result;
result = LIBC_NAMESPACE::ctime(nullptr);
ASSERT_ERRNO_EQ(EINVAL);
ASSERT_STREQ(nullptr, result);
}

// Weekdays are in the range 0 to 6. Test passing invalid value in wday.
TEST(LlvmLibcCtime, InvalidWday) {
struct tm tm_data;

// Test with wday = -1.
call_ctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
-1, // wday
0); // yday
ASSERT_ERRNO_EQ(EINVAL);

// Test with wday = 7.
call_ctime(&tm_data,
1970, // year
1, // month
1, // day
0, // hr
0, // min
0, // sec
7, // wday
0); // yday
ASSERT_ERRNO_EQ(EINVAL);
}

// Months are from January to December. Test passing invalid value in month.
TEST(LlvmLibcCtime, InvalidMonth) {
struct tm tm_data;

// Test with month = 0.
call_ctime(&tm_data,
1970, // year
0, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_ERRNO_EQ(EINVAL);

// Test with month = 13.
call_ctime(&tm_data,
1970, // year
13, // month
1, // day
0, // hr
0, // min
0, // sec
4, // wday
0); // yday
ASSERT_ERRNO_EQ(EINVAL);
}

TEST(LlvmLibcCtime, ValidWeekdays) {
struct tm tm_data;
char *result;
// 1970-01-01 00:00:00.
result = call_ctime(&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_ctime(&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_ctime(&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(LlvmLibcCtime, ValidMonths) {
struct tm tm_data;
char *result;
// 1970-01-01 00:00:00.
result = call_ctime(&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_ctime(&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_ctime(&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(LlvmLibcCtime, 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_ctime(&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(LlvmLibcCtime, 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_ctime(&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_ctime(&tm_data,
2147483647, // year
1, // month
1, // day
12, // hr
50, // min
50, // sec
2, // wday
50); // yday
ASSERT_ERRNO_EQ(EOVERFLOW);
ASSERT_STREQ(nullptr, result);
}

0 comments on commit e84118c

Please sign in to comment.