Skip to content

[libc] Implement strftime #122556

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

Merged
merged 2 commits into from
Feb 14, 2025
Merged
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
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.time.gmtime_r
libc.src.time.mktime
libc.src.time.nanosleep
libc.src.time.strftime
libc.src.time.time
libc.src.time.timespec_get

Expand Down
37 changes: 37 additions & 0 deletions libc/docs/dev/undefined_behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,40 @@ uninitialized spinlock and invalid spinlock is left undefined. We follow the rec
POSIX.1-2024, where EINVAL is returned if the spinlock is invalid (here we only check for null pointers) or
EBUSY is returned if the spinlock is currently locked. The lock is poisoned after a successful destroy. That is,
subsequent operations on the lock object without any reinitialization will return EINVAL.

Strftime
--------
In the C Standard, it provides a list of modifiers, and the conversions these
are valid on. It also says that a modifier on an unspecified conversion is
undefined. For LLVM-libc, the conversion is treated as if the modifier isn't
there.

If a struct tm with values out of the normal range is passed, the standard says
the result is undefined. For LLVM-libc, the result may be either the normalized
value (e.g. weekday % 7) or the actual, out of range value. For any numeric
conversion where the result is just printing a value out of the struct
(e.g. "%w" prints the day of the week), no normalization occurs ("%w" on a
tm_wday of 32 prints "32"). For any numeric conversion where the value is
calculated (e.g. "%u" prints the day of the week, starting on monday), the
value is normalized (e.g. "%u" on a tm_wday of 32 prints "4"). For conversions
that result in strings, passing an out of range value will result in "?".

Posix adds padding support to strftime, but says "the default padding character
is unspecified." For LLVM-libc, the default padding character is ' ' (space)
for all string-type conversions and '0' for integer-type conversions. Composite
conversions pass the padding to the first (leftmost) conversion. In practice
this is always a numeric conversion, so it pads with '0'. For the purposes of
padding, composite conversions also assume the non-leading conversions have
valid inputs and output their expected number of characters. For %c this means
that the padding will be off if the year is outside of the range -999 to 9999.

The %e conversion is padded with spaces by default, but pads with 0s if the '0'
flag is set.

Posix also adds flags and a minimum field width, but leaves unspecified what
happens for most combinations of these. For LLVM-libc:
An unspecified minimum field width defaults to 0.
More specific flags take precedence over less specific flags (i.e. '+' takes precedence over '0')
Any conversion with a minimum width is padded with the padding character until it is at least as long as the minimum width.
Modifiers are applied, then the result is padded if necessary.
Any composite conversion will pass along all flags to the component conversions.
9 changes: 9 additions & 0 deletions libc/include/time.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ functions:
arguments:
- type: const struct timespec *
- type: struct timespec *
- 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: time
standard:
- stdc
Expand Down
15 changes: 15 additions & 0 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ add_entrypoint_object(
libc.hdr.types.struct_tm
)

add_subdirectory(strftime_core) #TODO: Move to top

add_entrypoint_object(
strftime
SRCS
strftime.cpp
HDRS
strftime.h
DEPENDS
libc.hdr.types.size_t
libc.hdr.types.struct_tm
libc.src.stdio.printf_core.writer
libc.src.time.strftime_core.strftime_main
)

add_entrypoint_object(
time
SRCS
Expand Down
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 "hdr/types/size_t.h"
#include "hdr/types/struct_tm.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/strftime_main.h"

namespace LIBC_NAMESPACE_DECL {

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

printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
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.
wb.buff[wb.buff_cur] = '\0';
return (ret < 0 || static_cast<size_t>(ret) > buffsz) ? 0 : ret;
}

} // 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 "hdr/types/size_t.h"
#include "hdr/types/struct_tm.h"
#include "src/__support/macros/config.h"

namespace LIBC_NAMESPACE_DECL {

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

} // namespace LIBC_NAMESPACE_DECL

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

add_header_library(
parser
HDRS
parser.h
DEPENDS
.core_structs
libc.src.__support.CPP.string_view
libc.src.__support.ctype_utils
libc.src.__support.str_to_integer
)

add_object_library(
converter
SRCS
converter.cpp
HDRS
converter.h
num_converter.h
str_converter.h
composite_converter.h
DEPENDS
.core_structs
libc.src.time.time_utils
libc.src.time.time_constants
libc.src.stdio.printf_core.writer
libc.src.__support.CPP.string_view
libc.src.__support.integer_to_string
)

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.hdr.types.struct_tm
)
Loading