Skip to content

Commit 398f865

Browse files
[libc] Implement strftime (#122556)
Implements the posix-specified strftime conversions for the default locale, along with comprehensive unit tests. This reuses a lot of design from printf, as well as the printf writer. Roughly based on #111305, but with major rewrites.
1 parent 77ddffc commit 398f865

19 files changed

+3374
-0
lines changed

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,7 @@ if(LLVM_LIBC_FULL_BUILD)
11271127
libc.src.time.gmtime_r
11281128
libc.src.time.mktime
11291129
libc.src.time.nanosleep
1130+
libc.src.time.strftime
11301131
libc.src.time.time
11311132
libc.src.time.timespec_get
11321133

libc/docs/dev/undefined_behavior.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,40 @@ uninitialized spinlock and invalid spinlock is left undefined. We follow the rec
106106
POSIX.1-2024, where EINVAL is returned if the spinlock is invalid (here we only check for null pointers) or
107107
EBUSY is returned if the spinlock is currently locked. The lock is poisoned after a successful destroy. That is,
108108
subsequent operations on the lock object without any reinitialization will return EINVAL.
109+
110+
Strftime
111+
--------
112+
In the C Standard, it provides a list of modifiers, and the conversions these
113+
are valid on. It also says that a modifier on an unspecified conversion is
114+
undefined. For LLVM-libc, the conversion is treated as if the modifier isn't
115+
there.
116+
117+
If a struct tm with values out of the normal range is passed, the standard says
118+
the result is undefined. For LLVM-libc, the result may be either the normalized
119+
value (e.g. weekday % 7) or the actual, out of range value. For any numeric
120+
conversion where the result is just printing a value out of the struct
121+
(e.g. "%w" prints the day of the week), no normalization occurs ("%w" on a
122+
tm_wday of 32 prints "32"). For any numeric conversion where the value is
123+
calculated (e.g. "%u" prints the day of the week, starting on monday), the
124+
value is normalized (e.g. "%u" on a tm_wday of 32 prints "4"). For conversions
125+
that result in strings, passing an out of range value will result in "?".
126+
127+
Posix adds padding support to strftime, but says "the default padding character
128+
is unspecified." For LLVM-libc, the default padding character is ' ' (space)
129+
for all string-type conversions and '0' for integer-type conversions. Composite
130+
conversions pass the padding to the first (leftmost) conversion. In practice
131+
this is always a numeric conversion, so it pads with '0'. For the purposes of
132+
padding, composite conversions also assume the non-leading conversions have
133+
valid inputs and output their expected number of characters. For %c this means
134+
that the padding will be off if the year is outside of the range -999 to 9999.
135+
136+
The %e conversion is padded with spaces by default, but pads with 0s if the '0'
137+
flag is set.
138+
139+
Posix also adds flags and a minimum field width, but leaves unspecified what
140+
happens for most combinations of these. For LLVM-libc:
141+
An unspecified minimum field width defaults to 0.
142+
More specific flags take precedence over less specific flags (i.e. '+' takes precedence over '0')
143+
Any conversion with a minimum width is padded with the padding character until it is at least as long as the minimum width.
144+
Modifiers are applied, then the result is padded if necessary.
145+
Any composite conversion will pass along all flags to the component conversions.

libc/include/time.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ functions:
9191
arguments:
9292
- type: const struct timespec *
9393
- type: struct timespec *
94+
- name: strftime
95+
standard:
96+
- stdc
97+
return_type: size_t
98+
arguments:
99+
- type: char *__restrict
100+
- type: size_t
101+
- type: const char *__restrict
102+
- type: const struct tm *__restrict
94103
- name: time
95104
standard:
96105
- stdc

libc/src/time/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@ add_entrypoint_object(
135135
libc.hdr.types.struct_tm
136136
)
137137

138+
add_subdirectory(strftime_core) #TODO: Move to top
139+
140+
add_entrypoint_object(
141+
strftime
142+
SRCS
143+
strftime.cpp
144+
HDRS
145+
strftime.h
146+
DEPENDS
147+
libc.hdr.types.size_t
148+
libc.hdr.types.struct_tm
149+
libc.src.stdio.printf_core.writer
150+
libc.src.time.strftime_core.strftime_main
151+
)
152+
138153
add_entrypoint_object(
139154
time
140155
SRCS

libc/src/time/strftime.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===-- Implementation of strftime function -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/time/strftime.h"
10+
#include "hdr/types/size_t.h"
11+
#include "hdr/types/struct_tm.h"
12+
#include "src/__support/common.h"
13+
#include "src/__support/macros/config.h"
14+
#include "src/stdio/printf_core/writer.h"
15+
#include "src/time/strftime_core/strftime_main.h"
16+
17+
namespace LIBC_NAMESPACE_DECL {
18+
19+
LLVM_LIBC_FUNCTION(size_t, strftime,
20+
(char *__restrict buffer, size_t buffsz,
21+
const char *__restrict format, const tm *timeptr)) {
22+
23+
printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
24+
printf_core::Writer writer(&wb);
25+
int ret = strftime_core::strftime_main(&writer, format, timeptr);
26+
if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
27+
wb.buff[wb.buff_cur] = '\0';
28+
return (ret < 0 || static_cast<size_t>(ret) > buffsz) ? 0 : ret;
29+
}
30+
31+
} // namespace LIBC_NAMESPACE_DECL

libc/src/time/strftime.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===-- Implementation header of strftime -----------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_SRC_TIME_STRFTIME_H
10+
#define LLVM_LIBC_SRC_TIME_STRFTIME_H
11+
12+
#include "hdr/types/size_t.h"
13+
#include "hdr/types/struct_tm.h"
14+
#include "src/__support/macros/config.h"
15+
16+
namespace LIBC_NAMESPACE_DECL {
17+
18+
size_t strftime(char *__restrict, size_t max, const char *__restrict format,
19+
const tm *timeptr);
20+
21+
} // namespace LIBC_NAMESPACE_DECL
22+
23+
#endif // LLVM_LIBC_SRC_TIME_STRFTIME_H
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
add_header_library(
2+
core_structs
3+
HDRS
4+
core_structs.h
5+
DEPENDS
6+
libc.src.__support.CPP.string_view
7+
libc.hdr.types.struct_tm
8+
)
9+
10+
add_header_library(
11+
parser
12+
HDRS
13+
parser.h
14+
DEPENDS
15+
.core_structs
16+
libc.src.__support.CPP.string_view
17+
libc.src.__support.ctype_utils
18+
libc.src.__support.str_to_integer
19+
)
20+
21+
add_object_library(
22+
converter
23+
SRCS
24+
converter.cpp
25+
HDRS
26+
converter.h
27+
num_converter.h
28+
str_converter.h
29+
composite_converter.h
30+
DEPENDS
31+
.core_structs
32+
libc.src.time.time_utils
33+
libc.src.time.time_constants
34+
libc.src.stdio.printf_core.writer
35+
libc.src.__support.CPP.string_view
36+
libc.src.__support.integer_to_string
37+
)
38+
39+
add_object_library(
40+
strftime_main
41+
SRCS
42+
strftime_main.cpp
43+
HDRS
44+
strftime_main.h
45+
DEPENDS
46+
.core_structs
47+
.parser
48+
.converter
49+
libc.src.stdio.printf_core.writer
50+
libc.hdr.types.struct_tm
51+
)

0 commit comments

Comments
 (0)