Skip to content

[libc] Implement strftime #111305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libc/config/gpu/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.time.clock
libc.src.time.clock_gettime
libc.src.time.nanosleep
libc.src.time.strftime
libc.src.time.strftime_l

# wchar.h entrypoints
libc.src.wchar.wctob
Expand Down
20 changes: 20 additions & 0 deletions libc/newhdrgen/yaml/time.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ types:
- type_name: time_t
- type_name: clock_t
- type_name: size_t
- type_name: locale_t
enums: []
objects: []
functions:
Expand Down Expand Up @@ -96,3 +97,22 @@ functions:
return_type: time_t
arguments:
- type: time_t *
- name: strftime
standard:
- stdc
return_type: size_t
arguments:
- type: char *__restrict
- type: size_t
- type: const char *__restrict
- type: const struct tm *__restrict
- name: strftime_l
standard:
- stdc
return_type: size_t
arguments:
- type: char *__restrict
- type: size_t
- type: const char *__restrict
- type: const struct tm *__restrict
- type: locale_t
21 changes: 21 additions & 0 deletions libc/spec/stdc.td
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,7 @@ def StdC : StandardSpec<"stdc"> {
StructTimeSpec,
TimeTType,
SizeTType,
LocaleT,
],
[], // Enumerations
[
Expand Down Expand Up @@ -1651,6 +1652,26 @@ def StdC : StandardSpec<"stdc"> {
RetValSpec<TimeTType>,
[ArgSpec<TimeTTypePtr>]
>,
FunctionSpec<
"strftime",
RetValSpec<SizeTType>,
[
ArgSpec<CharRestrictedPtr>,
ArgSpec<SizeTType>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<StructTmPtr>
]
FunctionSpec<
"strftime_l",
RetValSpec<SizeTType>,
[
ArgSpec<CharRestrictedPtr>,
ArgSpec<SizeTType>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<StructTmPtr>,
ArgSpec<LocaleT>
]
>,
]
>;

Expand Down
34 changes: 30 additions & 4 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()
# if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
# endif()

Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed?

add_object_library(
time_utils
Expand Down Expand Up @@ -99,7 +99,6 @@ add_entrypoint_object(
HDRS
mktime.h
DEPENDS
.time_utils
libc.include.time
libc.src.errno.errno
)
Expand Down Expand Up @@ -138,3 +137,30 @@ add_entrypoint_object(
DEPENDS
.${LIBC_TARGET_OS}.gettimeofday
)

add_subdirectory(strftime_core)

add_entrypoint_object(
strftime
SRCS
strftime.cpp
HDRS
strftime.h
DEPENDS
libc.include.time
libc.src.time.strftime_core.strftime_main
libc.src.stdio.printf_core.writer
)

add_entrypoint_object(
strftime_l
SRCS
strftime_l.cpp
HDRS
strftime_l.h
DEPENDS
libc.include.time
libc.include.locale
libc.src.time.strftime_core.strftime_main
libc.src.stdio.printf_core.writer
)
31 changes: 31 additions & 0 deletions libc/src/time/strftime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===-- Implementation of strftime 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/strftime.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/errno/libc_errno.h"
#include "src/time/time_utils.h"

#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/strftime_main.h"
namespace LIBC_NAMESPACE_DECL {

size_t strftime(char *__restrict buffer, size_t buffsz,
const char *__restrict format, const struct tm *timeptr) {

printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0),
strftime_core::overflow_write_mock, nullptr);
printf_core::Writer writer(&wb);
int ret = strftime_core::strftime_main(&writer, format, timeptr);
if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps before doing any work, we should explicitly validate the inputs? Then we could explicit check that the pointer parameters are not null, bufsz is not zero, etc.

wb.buff[wb.buff_cur] = '\0';
return ret > 0 ? ret : 0;
}

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

#ifndef LLVM_LIBC_SRC_TIME_STRFTIME_H
#define LLVM_LIBC_SRC_TIME_STRFTIME_H

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

namespace LIBC_NAMESPACE_DECL {

size_t strftime(char *__restrict, size_t max, const char *__restrict format,
const struct tm *timeptr);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_TIME_STRFTIME_H
57 changes: 57 additions & 0 deletions libc/src/time/strftime_core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
add_header_library(
core_structs
HDRS
core_structs.h
DEPENDS
libc.src.__support.CPP.string_view
libc.include.time
)

add_header_library(
parser
HDRS
parser.h
DEPENDS
.core_structs
libc.src.string.string_utils
libc.include.time

)

add_object_library(
converter
SRCS
converter.cpp
HDRS
converter.h
num_converter.h
str_converter.h
composite_converter.h
DEPENDS
.core_structs
libc.src.__support.arg_list
libc.src.stdio.printf_core.writer
libc.src.stdio.printf_core.printf_main
libc.src.__support.big_int
libc.src.__support.CPP.string_view
libc.src.__support.float_to_string
libc.src.__support.integer_to_string
libc.src.__support.uint128
libc.src.__support.StringUtil.error_to_string
libc.include.time

)

add_object_library(
strftime_main
SRCS
strftime_main.cpp
HDRS
strftime_main.h
DEPENDS
.core_structs
.parser
.converter
libc.src.stdio.printf_core.writer
libc.include.time
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline.

124 changes: 124 additions & 0 deletions libc/src/time/strftime_core/composite_converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//===-- Format specifier converter for printf -------------------*- C++ -*-===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fix this comment

//
// 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.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_COMPOSITE_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_COMPOSITE_CONVERTER_H

#include "src/__support/CPP/string_view.h"
#include "src/__support/arg_list.h"
#include "src/__support/integer_to_string.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/printf_main.h"
#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/core_structs.h"
#include "src/time/strftime_core/time_internal_def.h"

namespace LIBC_NAMESPACE_DECL {
namespace strftime_core {

namespace details {
int snprintf_impl(char *__restrict buffer, size_t buffsz,
const char *__restrict format, ...) {
va_list vlist;
va_start(vlist, format);
internal::ArgList args(vlist);
va_end(vlist);
printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
printf_core::Writer writer(&wb);

int ret_val = printf_core::printf_main(&writer, format, args);
if (buffsz > 0)
wb.buff[wb.buff_cur] = '\0';
return ret_val;
}
} // namespace details

int write_composite(printf_core::Writer *writer, const FormatSection &to_conv) {
char buffer[100];
auto &time = *to_conv.time;

switch (to_conv.conv_name) {
// Full date and time representation (e.g., equivalent to %a %b %e %T %Y)
case 'c': {
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%s %s %02d %02d:%02d:%02d %d",
safe_abbreviated_day_name(time.tm_wday),
safe_abbreviated_month_name(time.tm_mon), time.tm_mday, time.tm_hour,
time.tm_min, time.tm_sec, time.tm_year + 1900));
break;
}

// Zero-padded day of the month (equivalent to %m/%d/%y)
case 'D': {
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%02d/%02d/%02d", time.tm_mon + 1, time.tm_mday,
(time.tm_year + 1900) % 100));
break;
}

// ISO 8601 date representation in YYYY-MM-DD (equivalent to %Y-%m-%d)
case 'F': {
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%04d-%02d-%02d", time.tm_year + 1900,
time.tm_mon + 1, time.tm_mday));
break;
}

// 12-hour clock time with seconds and AM/PM (equivalent to %I:%M:%S %p)
case 'r': {
int hour12 = time.tm_hour % 12;
if (hour12 == 0)
hour12 = 12;
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%02d:%02d:%02d %s", hour12, time.tm_min,
time.tm_sec,
to_conv.time->tm_hour >= 12 ? default_PM_str : default_AM_str));
break;
}

// 24-hour time without seconds (equivalent to %H:%M)
case 'R': {
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%02d:%02d", time.tm_hour, time.tm_min));
break;
}

// Time with seconds (equivalent to %H:%M:%S)
case 'T': {
RET_IF_RESULT_NEGATIVE(
details::snprintf_impl(buffer, sizeof(buffer), "%02d:%02d:%02d",
time.tm_hour, time.tm_min, time.tm_sec));
break;
}

// Locale's date representation (often equivalent to %m/%d/%y)
case 'x': {
RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
buffer, sizeof(buffer), "%02d/%02d/%02d", time.tm_mon + 1, time.tm_mday,
(time.tm_year + 1900) % 100));
break;
}

// Locale's time representation (equivalent to %H:%M:%S)
case 'X': {
RET_IF_RESULT_NEGATIVE(
details::snprintf_impl(buffer, sizeof(buffer), "%02d:%02d:%02d",
time.tm_hour, time.tm_min, time.tm_sec));
break;
}

default:
return writer->write(to_conv.raw_string);
}
return writer->write(buffer);
}

} // namespace strftime_core
} // namespace LIBC_NAMESPACE_DECL

#endif
Loading
Loading