Skip to content

Commit 9e875ec

Browse files
[libc] Implement strftime
TODO: DESCRIPTION TODO: TESTS TODO: RotFO (Rest of the Formatting Options) Roughly based on llvm#111305, but with major rewrites.
1 parent f9c2377 commit 9e875ec

File tree

20 files changed

+1138
-20
lines changed

20 files changed

+1138
-20
lines changed

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ if(LLVM_LIBC_FULL_BUILD)
11031103
libc.src.time.gmtime_r
11041104
libc.src.time.mktime
11051105
libc.src.time.nanosleep
1106+
libc.src.time.strftime
11061107
libc.src.time.time
11071108
libc.src.time.timespec_get
11081109

libc/docs/dev/undefined_behavior.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,27 @@ 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. This behavior is not
120+
necessarily consistent between conversions, even similar ones. For conversions
121+
that result in strings, passing an out of range value will result in "?".
122+
123+
Posix adds padding support to strftime, but says "the default padding character
124+
is unspecified." For LLVM-libc, the default padding character is ' ' (space),
125+
for all string-type conversions and '0' for integer-type conversions.
126+
127+
Posix also adds flags and a minimum field width, but leaves unspecified what
128+
happens for most combinations of these. For LLVM-libc:
129+
An unspecified minimum field width defaults to 0.
130+
More specific flags take precedence over less specific flags (i.e. '+' takes precedence over '0')
131+
Any conversion with a minimum width is padded with the padding character until it is at least as long as the minimum width.
132+
Modifiers are applied, then the result is padded if necessary.

libc/include/llvm-libc-types/struct_tm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct tm {
1919
int tm_wday; // days since Sunday
2020
int tm_yday; // days since January
2121
int tm_isdst; // Daylight Saving Time flag
22+
// TODO: add tm_gmtoff and tm_zone? (posix extensions)
2223
};
2324

2425
#endif // LLVM_LIBC_TYPES_STRUCT_TM_H

libc/include/time.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,25 @@ 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
103+
- name: strftime_l
104+
standard:
105+
- stdc
106+
return_type: size_t
107+
arguments:
108+
- type: char *__restrict
109+
- type: size_t
110+
- type: const char *__restrict
111+
- type: const struct tm *__restrict
112+
- type: locale_t
94113
- name: time
95114
standard:
96115
- stdc

libc/src/time/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ add_object_library(
2222
DEPENDS
2323
libc.include.time
2424
libc.src.__support.CPP.limits
25+
libc.src.__support.CPP.string_view
26+
libc.src.__support.CPP.optional
2527
libc.src.errno.errno
2628
.time_constants
2729
libc.hdr.types.time_t
@@ -133,6 +135,21 @@ add_entrypoint_object(
133135
libc.hdr.types.struct_tm
134136
)
135137

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+
136153
add_entrypoint_object(
137154
time
138155
SRCS

libc/src/time/mktime.cpp

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@
1414

1515
namespace LIBC_NAMESPACE_DECL {
1616

17-
// Returns number of years from (1, year).
18-
static constexpr int64_t get_num_of_leap_years_before(int64_t year) {
19-
return (year / 4) - (year / 100) + (year / 400);
20-
}
21-
22-
// Returns True if year is a leap year.
23-
static constexpr bool is_leap_year(const int64_t year) {
24-
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
25-
}
26-
2717
LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
2818
// Unlike most C Library functions, mktime doesn't just die on bad input.
2919
// TODO(rtenneti); Handle leap seconds.
@@ -69,7 +59,7 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
6959
}
7060
tm_year_from_base += years;
7161
}
72-
bool tm_year_is_leap = is_leap_year(tm_year_from_base);
62+
bool tm_year_is_leap = time_utils::is_leap_year(tm_year_from_base);
7363

7464
// Calculate total number of days based on the month and the day (tm_mday).
7565
int64_t total_days = tm_out->tm_mday - 1;
@@ -83,21 +73,25 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
8373
total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
8474
time_constants::DAYS_PER_NON_LEAP_YEAR;
8575
if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
86-
total_days += get_num_of_leap_years_before(tm_year_from_base - 1) -
87-
get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
76+
total_days +=
77+
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1) -
78+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
8879
} else if (tm_year_from_base >= 1) {
89-
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
90-
get_num_of_leap_years_before(tm_year_from_base - 1);
80+
total_days -=
81+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
82+
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1);
9183
} else {
9284
// Calculate number of leap years until 0th year.
93-
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
94-
get_num_of_leap_years_before(0);
85+
total_days -=
86+
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
87+
time_utils::get_num_of_leap_years_before(0);
9588
if (tm_year_from_base <= 0) {
9689
total_days -= 1; // Subtract 1 for 0th year.
9790
// Calculate number of leap years until -1 year
9891
if (tm_year_from_base < 0) {
99-
total_days -= get_num_of_leap_years_before(-tm_year_from_base) -
100-
get_num_of_leap_years_before(1);
92+
total_days -=
93+
time_utils::get_num_of_leap_years_before(-tm_year_from_base) -
94+
time_utils::get_num_of_leap_years_before(1);
10195
}
10296
}
10397
}

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 struct 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 struct 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+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===-- Format specifier converter implmentation for strftime -------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See htto_conv.times://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/__support/macros/config.h"
10+
#include "src/stdio/printf_core/writer.h"
11+
#include "src/time/strftime_core/core_structs.h"
12+
13+
// #include "composite_converter.h"
14+
#include "num_converter.h"
15+
// #include "str_converter.h"
16+
17+
namespace LIBC_NAMESPACE_DECL {
18+
namespace strftime_core {
19+
20+
int convert(printf_core::Writer *writer, const FormatSection &to_conv,
21+
const tm *timeptr) {
22+
// TODO: Implement the locale support.
23+
if (to_conv.modifier != ConvModifier::none) {
24+
return writer->write(to_conv.raw_string);
25+
}
26+
27+
if (!to_conv.has_conv)
28+
return writer->write(to_conv.raw_string);
29+
switch (to_conv.conv_name) {
30+
// The cases are grouped by type, then alphabetized with lowercase before
31+
// uppercase.
32+
33+
// raw conversions
34+
case '%':
35+
return writer->write("%");
36+
case 'n':
37+
return writer->write("\n");
38+
case 't':
39+
return writer->write("\t");
40+
41+
// numeric conversions
42+
case 'C': // Century
43+
case 'd': // Day of the month [01-31]
44+
case 'e': // Day of the month [1-31]
45+
case 'g': // last 2 digits of ISO year
46+
case 'G': // ISO year
47+
case 'H': // 24-hour format
48+
case 'I': // 12-hour format
49+
case 'j': // Day of the year
50+
case 'm': // Month of the year
51+
case 'M': // Minute of the hour
52+
case 's': // Seconds since the epoch
53+
case 'S': // Second of the minute
54+
case 'u': // ISO day of the week ([1-7] starting Monday)
55+
case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
56+
case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
57+
case 'w': // Day of week ([0-6] starting Sunday)
58+
case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
59+
case 'y': // Year of the Century
60+
case 'Y': // Full year
61+
return convert_int(writer, to_conv, timeptr);
62+
63+
// string conversions
64+
case 'a': // Abbreviated weekday name
65+
case 'A': // Full weekday name
66+
case 'b': // Abbreviated month name
67+
case 'B': // Full month name
68+
case 'h': // same as %b
69+
case 'p': // AM/PM designation
70+
case 'z': // Timezone offset (+/-hhmm)
71+
case 'Z': // Timezone name
72+
// return write_str(writer, to_conv, timeptr);
73+
74+
// composite conversions
75+
case 'c': // locale specified date and time
76+
case 'D': // %m/%d/%y (month/day/year)
77+
case 'F': // %Y-%m-%d (year-month-day)
78+
case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
79+
case 'R': // %H:%M (hour:minute)
80+
case 'T': // %H:%M:%S (hour:minute:second)
81+
case 'x': // locale specified date
82+
case 'X': // locale specified time
83+
// return write_composite(writer, to_conv, timeptr);
84+
default:
85+
return writer->write(to_conv.raw_string);
86+
}
87+
return 0;
88+
}
89+
90+
} // namespace strftime_core
91+
} // namespace LIBC_NAMESPACE_DECL
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===-- Format specifier converter for 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_STDIO_STRFTIME_CORE_CONVERTER_H
10+
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
11+
12+
#include "src/stdio/printf_core/writer.h"
13+
#include "src/time/strftime_core/core_structs.h"
14+
15+
#include <stddef.h>
16+
17+
namespace LIBC_NAMESPACE_DECL {
18+
namespace strftime_core {
19+
20+
// convert will call a conversion function to convert the FormatSection into
21+
// its string representation, and then that will write the result to the
22+
// writer.
23+
int convert(printf_core::Writer *writer, const FormatSection &to_conv,
24+
const tm *timeptr);
25+
26+
} // namespace strftime_core
27+
} // namespace LIBC_NAMESPACE_DECL
28+
29+
#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H

0 commit comments

Comments
 (0)