[libc-commits] [libc] [WIP] Basic structures for strftime (PR #111305)
Tsz Chan via libc-commits
libc-commits at lists.llvm.org
Sun Oct 13 08:40:00 PDT 2024
https://github.com/tszhin-swe updated https://github.com/llvm/llvm-project/pull/111305
>From 560e57a27fe968b9435e0b26baf6d550fca00e27 Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Sun, 13 Oct 2024 21:59:37 +0800
Subject: [PATCH 1/3] [libc] Implement strftime
---
libc/src/stdio/CMakeLists.txt | 1 +
libc/src/stdio/strftime_core/CMakeLists.txt | 52 +++
libc/src/stdio/strftime_core/converter.cpp | 264 +++++++++++++++
libc/src/stdio/strftime_core/converter.h | 29 ++
libc/src/stdio/strftime_core/core_structs.h | 97 ++++++
libc/src/stdio/strftime_core/parser.h | 125 +++++++
.../stdio/strftime_core/time_internal_def.h | 43 +++
libc/test/src/stdio/CMakeLists.txt | 1 +
.../src/stdio/strftime_core/CMakeLists.txt | 11 +
.../stdio/strftime_core/converter_test.cpp | 320 ++++++++++++++++++
.../src/stdio/strftime_core/parser_test.cpp | 0
11 files changed, 943 insertions(+)
create mode 100644 libc/src/stdio/strftime_core/CMakeLists.txt
create mode 100644 libc/src/stdio/strftime_core/converter.cpp
create mode 100644 libc/src/stdio/strftime_core/converter.h
create mode 100644 libc/src/stdio/strftime_core/core_structs.h
create mode 100644 libc/src/stdio/strftime_core/parser.h
create mode 100644 libc/src/stdio/strftime_core/time_internal_def.h
create mode 100644 libc/test/src/stdio/strftime_core/CMakeLists.txt
create mode 100644 libc/test/src/stdio/strftime_core/converter_test.cpp
create mode 100644 libc/test/src/stdio/strftime_core/parser_test.cpp
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index b9bc904471df9a..5338c17d6232b3 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -239,6 +239,7 @@ add_entrypoint_object(
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
+add_subdirectory(strftime_core)
add_entrypoint_object(
remove
diff --git a/libc/src/stdio/strftime_core/CMakeLists.txt b/libc/src/stdio/strftime_core/CMakeLists.txt
new file mode 100644
index 00000000000000..e234d8d064e159
--- /dev/null
+++ b/libc/src/stdio/strftime_core/CMakeLists.txt
@@ -0,0 +1,52 @@
+
+add_header_library(
+ core_structs
+ HDRS
+ core_structs.h
+ DEPENDS
+ libc.src.__support.CPP.string_view
+ libc.src.__support.FPUtil.fp_bits
+)
+
+add_header_library(
+ parser
+ HDRS
+ parser.h
+ DEPENDS
+ .core_structs
+ libc.src.__support.arg_list
+ libc.src.__support.ctype_utils
+ libc.src.__support.str_to_integer
+ libc.src.__support.CPP.algorithm
+ libc.src.__support.CPP.bit
+ libc.src.__support.CPP.optional
+ libc.src.__support.CPP.string_view
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.common
+)
+
+
+add_object_library(
+ converter
+ SRCS
+ converter.cpp
+ HDRS
+ converter.h
+ DEPENDS
+ .core_structs
+ libc.src.stdio.printf_core.writer
+ libc.src.__support.big_int
+ libc.src.math.log10
+ libc.src.__support.common
+ libc.src.__support.CPP.limits
+ libc.src.__support.CPP.span
+ libc.src.__support.CPP.string_view
+ libc.src.__support.float_to_string
+ libc.src.__support.FPUtil.fenv_impl
+ libc.src.__support.FPUtil.fp_bits
+ libc.src.__support.FPUtil.rounding_mode
+ libc.src.__support.integer_to_string
+ libc.src.__support.libc_assert
+ libc.src.__support.uint128
+ libc.src.__support.StringUtil.error_to_string
+)
\ No newline at end of file
diff --git a/libc/src/stdio/strftime_core/converter.cpp b/libc/src/stdio/strftime_core/converter.cpp
new file mode 100644
index 00000000000000..79e3727cc88f5b
--- /dev/null
+++ b/libc/src/stdio/strftime_core/converter.cpp
@@ -0,0 +1,264 @@
+//===-- Format specifier converter for printf -------------------*- C++ -*-===//
+//
+// 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_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
+
+#include "src/__support/CPP/string.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/integer_to_string.h"
+#include "src/__support/macros/config.h"
+#include "src/math/log10.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/stdio/strftime_core/core_structs.h"
+#include "src/stdio/strftime_core/time_internal_def.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+namespace details {
+
+LIBC_INLINE cpp::optional<cpp::string_view>
+num_to_strview(uintmax_t num, cpp::span<char> bufref) {
+ return IntegerToString<uintmax_t>::format_to(bufref, num);
+}
+
+template <int width>
+LIBC_INLINE int write_num(uintmax_t num, printf_core::Writer *writer) {
+ cpp::array<char, width> buf;
+ return writer->write(*num_to_strview(num, buf));
+}
+
+template <int width, char padding>
+LIBC_INLINE int write_num_with_padding(uintmax_t num,
+ printf_core::Writer *writer) {
+ cpp::array<char, width> buf;
+ auto digits = log10(num) + 1;
+ auto padding_needed = width - digits;
+ int char_written = 0;
+ for (int _ = 0; _ < padding_needed; _++) {
+ char_written += writer->write(padding);
+ }
+ char_written += writer->write(*num_to_strview(num, buf));
+ return char_written;
+}
+
+} // namespace details
+
+/* Nonzero if YEAR is a leap year (every 4 years,
+ except every 100th isn't, and every 400th is). */
+LIBC_INLINE bool is_leap(int year) {
+ return ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0));
+}
+
+LIBC_INLINE int convert_weekday(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return writer->write(day_names[to_conv.time->tm_wday]);
+}
+
+LIBC_INLINE int convert_zero_padded_day_of_year(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<3, '0'>(to_conv.time->tm_yday + 1,
+ writer);
+}
+
+LIBC_INLINE int convert_zero_padded_day_of_month(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(to_conv.time->tm_mday, writer);
+}
+
+LIBC_INLINE int
+convert_space_padded_day_of_month(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, ' '>(to_conv.time->tm_mday, writer);
+}
+
+LIBC_INLINE int convert_decimal_weekday(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num<1>(
+ to_conv.time->tm_wday == 0 ? 7 : to_conv.time->tm_wday, writer);
+}
+
+LIBC_INLINE int convert_decimal_weekday_iso(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num<1>(to_conv.time->tm_wday, writer);
+}
+
+LIBC_INLINE int convert_week_number_sunday(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ int wday = to_conv.time->tm_wday;
+ int yday = to_conv.time->tm_yday;
+ int week = (yday - wday + 7) / 7;
+ return details::write_num_with_padding<2, '0'>(week, writer);
+}
+
+LIBC_INLINE int convert_week_number_monday(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ int wday = (to_conv.time->tm_wday + 6) % 7;
+ int yday = to_conv.time->tm_yday;
+ int week = (yday - wday + 7) / 7;
+ return details::write_num_with_padding<2, '0'>(week, writer);
+}
+
+LIBC_INLINE int convert_full_month(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return writer->write(month_names[to_conv.time->tm_mon]);
+}
+
+LIBC_INLINE int convert_abbreviated_month(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return writer->write(abbreviated_month_names[to_conv.time->tm_mon]);
+}
+
+LIBC_INLINE int convert_zero_padded_month(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(to_conv.time->tm_mon + 1,
+ writer);
+}
+
+LIBC_INLINE int convert_full_year(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<4, '0'>(
+ to_conv.time->tm_year + YEAR_BASE, writer);
+}
+
+LIBC_INLINE int convert_two_digit_year(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(
+ (to_conv.time->tm_year + YEAR_BASE) % 100, writer);
+}
+
+LIBC_INLINE int convert_century(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(
+ (to_conv.time->tm_year + YEAR_BASE) / 100, writer);
+}
+
+static int iso_week_days(int yday, int wday) {
+ /* Add enough to the first operand of % to make it nonnegative. */
+ int big_enough_multiple_of_7 = (-YDAY_MINIMUM / 7 + 2) * 7;
+ return (yday - (yday - wday + ISO_WEEK1_WDAY + big_enough_multiple_of_7) % 7 +
+ ISO_WEEK1_WDAY - ISO_WEEK_START_WDAY);
+}
+
+LIBC_INLINE int convert_iso_year(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ int year = to_conv.time->tm_year + YEAR_BASE;
+ int days = iso_week_days(to_conv.time->tm_yday, to_conv.time->tm_wday);
+
+ if (days < 0) {
+ /* This ISO week belongs to the previous year. */
+ year--;
+ days = iso_week_days(to_conv.time->tm_yday + (365 + is_leap(year)),
+ to_conv.time->tm_wday);
+ } else {
+ int d = iso_week_days(to_conv.time->tm_yday - (365 + is_leap(year)),
+ to_conv.time->tm_wday);
+ if (0 <= d) {
+ /* This ISO week belongs to the next year. */
+ year++;
+ days = d;
+ }
+ }
+
+ return details::write_num<4>(year, writer);
+}
+
+LIBC_INLINE int convert_hour_24(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(to_conv.time->tm_hour, writer);
+}
+
+LIBC_INLINE int convert_pm(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ static const cpp::string_view AM = "AM";
+ static const cpp::string_view PM = "PM";
+ return writer->write(to_conv.time->tm_hour >= 12 ? PM : AM);
+}
+
+LIBC_INLINE int convert_hour_12(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ int hour = to_conv.time->tm_hour % 12; // Convert to 12-hour format
+ if (hour == 0)
+ hour = 12; // Adjust for midnight
+ return details::write_num_with_padding<2, '0'>(hour, writer);
+}
+
+LIBC_INLINE int convert_minute(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(to_conv.time->tm_min, writer);
+}
+
+LIBC_INLINE int convert_second(printf_core::Writer *writer,
+ const FormatSection &to_conv) {
+ return details::write_num_with_padding<2, '0'>(to_conv.time->tm_sec, writer);
+}
+
+int convert(printf_core::Writer *writer, const FormatSection &to_conv) {
+ if (!to_conv.has_conv)
+ return writer->write(to_conv.raw_string);
+ switch (to_conv.conv_name) {
+ // day of the week
+ case 'a':
+ return convert_weekday(writer, to_conv);
+ case 'w':
+ return convert_decimal_weekday(writer, to_conv);
+ case 'u':
+ return convert_decimal_weekday_iso(writer, to_conv);
+ // day of the year/ month
+ case 'j':
+ return convert_zero_padded_day_of_year(writer, to_conv);
+ case 'd':
+ return convert_zero_padded_day_of_month(writer, to_conv);
+ case 'e':
+ return convert_space_padded_day_of_month(writer, to_conv);
+ // week
+ case 'U':
+ return convert_week_number_sunday(writer, to_conv);
+ case 'W':
+ return convert_week_number_monday(writer, to_conv);
+ case 'V': // TODO: ISO 8061
+ // month
+ case 'B':
+ return convert_full_month(writer, to_conv);
+ case 'b':
+ case 'h':
+ return convert_abbreviated_month(writer, to_conv);
+ case 'm':
+ return convert_zero_padded_month(writer, to_conv);
+ // year
+ case 'Y':
+ return convert_full_year(writer, to_conv);
+ case 'y':
+ return convert_two_digit_year(writer, to_conv);
+ case 'C':
+ return convert_century(writer, to_conv);
+ case 'G':
+ // TODO
+ return convert_iso_year(writer, to_conv);
+ // hours
+ case 'p':
+ return convert_pm(writer, to_conv);
+ case 'H':
+ return convert_hour_24(writer, to_conv);
+ case 'I':
+ return convert_hour_12(writer, to_conv);
+ // minutes
+ case 'M':
+ return convert_minute(writer, to_conv);
+ // seconds
+ case 'S':
+ return convert_second(writer, to_conv);
+ }
+ return 0;
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+#endif
diff --git a/libc/src/stdio/strftime_core/converter.h b/libc/src/stdio/strftime_core/converter.h
new file mode 100644
index 00000000000000..57801b8e8e1262
--- /dev/null
+++ b/libc/src/stdio/strftime_core/converter.h
@@ -0,0 +1,29 @@
+//===-- Format specifier converter for printf -------------------*- 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_STDIO_STRFTIME_CORE_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
+
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/stdio/strftime_core/core_structs.h"
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+// convert will call a conversion function to convert the FormatSection into
+// its string representation, and then that will write the result to the
+// writer.
+int convert(printf_core::Writer *writer, const FormatSection &to_conv);
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
diff --git a/libc/src/stdio/strftime_core/core_structs.h b/libc/src/stdio/strftime_core/core_structs.h
new file mode 100644
index 00000000000000..404902bcfbf6f3
--- /dev/null
+++ b/libc/src/stdio/strftime_core/core_structs.h
@@ -0,0 +1,97 @@
+//===-- Core Structures for printf ------------------------------*- 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_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
+
+#include "src/__support/macros/config.h"
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/FPUtil/FPBits.h"
+
+#include <inttypes.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+struct tm {
+ int tm_sec; /* seconds after the minute [0-60] */
+ int tm_min; /* minutes after the hour [0-59] */
+ int tm_hour; /* hours since midnight [0-23] */
+ int tm_mday; /* day of the month [1-31] */
+ int tm_mon; /* months since January [0-11] */
+ int tm_year; /* years since 1900 */
+ int tm_wday; /* days since Sunday [0-6] */
+ int tm_yday; /* days since January 1 [0-365] */
+ int tm_isdst; /* Daylight Savings Time flag */
+ long tm_gmtoff; /* offset from UTC in seconds */
+ char *tm_zone; /* timezone abbreviation */
+};
+
+struct FormatSection {
+ bool has_conv{false};
+ bool isE{false};
+ bool isO{false};
+ cpp::string_view raw_string;
+ char conv_name;
+ const struct tm *time;
+};
+
+enum PrimaryType : uint8_t {
+ Unknown = 0,
+ Float = 1,
+ Pointer = 2,
+ Integer = 3,
+ FixedPoint = 4,
+};
+
+// TypeDesc stores the information about a type that is relevant to printf in
+// a relatively compact manner.
+struct TypeDesc {
+ uint8_t size;
+ PrimaryType primary_type;
+ LIBC_INLINE constexpr bool operator==(const TypeDesc &other) const {
+ return (size == other.size) && (primary_type == other.primary_type);
+ }
+};
+
+template <typename T> LIBC_INLINE constexpr TypeDesc type_desc_from_type() {
+ if constexpr (cpp::is_same_v<T, void>) {
+ return TypeDesc{0, PrimaryType::Unknown};
+ } else {
+ constexpr bool IS_POINTER = cpp::is_pointer_v<T>;
+ constexpr bool IS_FLOAT = cpp::is_floating_point_v<T>;
+#ifdef LIBC_INTERNAL_STRFTIME_HAS_FIXED_POINT
+ constexpr bool IS_FIXED_POINT = cpp::is_fixed_point_v<T>;
+#else
+ constexpr bool IS_FIXED_POINT = false;
+#endif // LIBC_INTERNAL_STRFTIME_HAS_FIXED_POINT
+
+ return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
+ : IS_FLOAT ? PrimaryType::Float
+ : IS_FIXED_POINT ? PrimaryType::FixedPoint
+ : PrimaryType::Integer};
+ }
+}
+
+// This is the value to be returned by conversions when no error has occurred.
+constexpr int WRITE_OK = 0;
+// These are the printf return values for when an error has occurred. They are
+// all negative, and should be distinct.
+constexpr int FILE_WRITE_ERROR = -1;
+constexpr int FILE_STATUS_ERROR = -2;
+constexpr int NULLPTR_WRITE_ERROR = -3;
+constexpr int INT_CONVERSION_ERROR = -4;
+constexpr int FIXED_POINT_CONVERSION_ERROR = -5;
+constexpr int ALLOCATION_ERROR = -6;
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
diff --git a/libc/src/stdio/strftime_core/parser.h b/libc/src/stdio/strftime_core/parser.h
new file mode 100644
index 00000000000000..046f5b1095ad0b
--- /dev/null
+++ b/libc/src/stdio/strftime_core/parser.h
@@ -0,0 +1,125 @@
+//===-- Format string parser for printf -------------------------*- 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_STDIO_STRFTIME_CORE_PARSER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_PARSER_H
+
+#include "include/llvm-libc-macros/stdfix-macros.h"
+#include "src/__support/CPP/algorithm.h" // max
+#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/str_to_integer.h"
+#include "src/stdio/strftime_core/core_structs.h"
+// #include "src/stdio/strftime_core/printf_config.h"
+
+#include <stddef.h>
+
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#include "src/__support/fixed_point/fx_rep.h"
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+#include "src/errno/libc_errno.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+class Parser {
+ const char *__restrict str;
+ const struct tm &time;
+ size_t cur_pos = 0;
+
+public:
+ LIBC_INLINE Parser(const char *__restrict new_str, const tm &time)
+ : str(new_str), time(time) {}
+
+ // get_next_section will parse the format string until it has a fully
+ // specified format section. This can either be a raw format section with no
+ // conversion, or a format section with a conversion that has all of its
+ // variables stored in the format section.
+ LIBC_INLINE FormatSection get_next_section() {
+ FormatSection section;
+ size_t starting_pos = cur_pos;
+ if (str[cur_pos] != '%') {
+ // raw section
+ section.has_conv = false;
+ while (str[cur_pos] != '%' && str[cur_pos] != '\0')
+ ++cur_pos;
+ } else {
+ // format section
+ section.has_conv = true;
+ section.time = &time;
+ // locale-specific modifiers
+ if (str[cur_pos] == 'E')
+ section.isE = true;
+ if (str[cur_pos] == 'O')
+ section.isO = true;
+ ++cur_pos;
+ section.conv_name = str[cur_pos];
+
+ switch (str[cur_pos]) {
+ case ('%'):
+ case ('a'):
+ case ('A'):
+ case ('b'):
+ case ('B'):
+ case ('c'):
+ case ('C'):
+ case ('d'):
+ case ('D'):
+ case ('e'):
+ case ('F'):
+ case ('g'):
+ case ('G'):
+ case ('h'):
+ case ('H'):
+ case ('I'):
+ case ('j'):
+ case ('m'):
+ case ('M'):
+ case ('n'):
+ case ('p'):
+ case ('r'):
+ case ('R'):
+ case ('S'):
+ case ('t'):
+ case ('T'):
+ case ('u'):
+ case ('U'):
+ case ('V'):
+ case ('w'):
+ case ('W'):
+ case ('x'):
+ case ('X'):
+ case ('y'):
+ case ('Y'):
+ case ('z'):
+ case ('Z'):
+ section.has_conv = true;
+ break;
+ default:
+ // if the conversion is undefined, change this to a raw section.
+ section.has_conv = false;
+ break;
+ }
+ // If the end of the format section is on the '\0'. This means we need to
+ // not advance the cur_pos.
+ if (str[cur_pos] != '\0')
+ ++cur_pos;
+ }
+ section.raw_string = {str + starting_pos, cur_pos - starting_pos};
+ return section;
+ }
+};
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_PARSER_H
diff --git a/libc/src/stdio/strftime_core/time_internal_def.h b/libc/src/stdio/strftime_core/time_internal_def.h
new file mode 100644
index 00000000000000..a7672ad43704e8
--- /dev/null
+++ b/libc/src/stdio/strftime_core/time_internal_def.h
@@ -0,0 +1,43 @@
+//===-- Strftime related internals -------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/string_view.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+static constexpr int NUM_DAYS = 7;
+static constexpr int NUM_MONTHS = 12;
+static constexpr int YEAR_BASE = 1900;
+
+/* The number of days from the first day of the first ISO week of this
+ year to the year day YDAY with week day WDAY. ISO weeks start on
+ Monday; the first ISO week has the year's first Thursday. YDAY may
+ be as small as YDAY_MINIMUM. */
+static constexpr int ISO_WEEK_START_WDAY = 1; /* Monday */
+static constexpr int ISO_WEEK1_WDAY = 4; /* Thursday */
+static constexpr int YDAY_MINIMUM = -366;
+
+static constexpr cpp::array<cpp::string_view, NUM_DAYS> day_names = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"};
+
+static constexpr cpp::array<cpp::string_view, NUM_MONTHS> month_names = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"};
+
+static constexpr cpp::array<cpp::string_view, NUM_DAYS> abbreviated_day_names =
+ {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+
+static constexpr cpp::array<cpp::string_view, NUM_MONTHS>
+ abbreviated_month_names = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index e970e4cf014052..c43c24dc3459a7 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -542,4 +542,5 @@ endif()
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
+add_subdirectory(strftime_core)
add_subdirectory(testdata)
diff --git a/libc/test/src/stdio/strftime_core/CMakeLists.txt b/libc/test/src/stdio/strftime_core/CMakeLists.txt
new file mode 100644
index 00000000000000..e47cd0f8bb2f8b
--- /dev/null
+++ b/libc/test/src/stdio/strftime_core/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_libc_unittest(
+ converter_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ converter_test.cpp
+ DEPENDS
+ libc.src.stdio.strftime_core.converter
+ libc.src.stdio.printf_core.writer
+ libc.src.stdio.strftime_core.core_structs
+)
diff --git a/libc/test/src/stdio/strftime_core/converter_test.cpp b/libc/test/src/stdio/strftime_core/converter_test.cpp
new file mode 100644
index 00000000000000..53ea3694227ef4
--- /dev/null
+++ b/libc/test/src/stdio/strftime_core/converter_test.cpp
@@ -0,0 +1,320 @@
+//===-- Unittests for the printf Converter --------------------------------===//
+//
+// 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/stdio/printf_core/writer.h"
+#include "src/stdio/strftime_core/converter.h"
+#include "src/stdio/strftime_core/core_structs.h"
+
+#include "test/UnitTest/Test.h"
+
+class LlvmLibcStrftimeConverterTest : public LIBC_NAMESPACE::testing::Test {
+protected:
+ // void SetUp() override {}
+ // void TearDown() override {}
+
+ char str[60];
+ LIBC_NAMESPACE::printf_core::WriteBuffer wb =
+ LIBC_NAMESPACE::printf_core::WriteBuffer(str, sizeof(str) - 1);
+ LIBC_NAMESPACE::printf_core::Writer writer =
+ LIBC_NAMESPACE::printf_core::Writer(&wb);
+};
+
+TEST_F(LlvmLibcStrftimeConverterTest, SimpleRawConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection raw_section;
+ raw_section.has_conv = false;
+ raw_section.raw_string = "abc";
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, raw_section);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "abc");
+ ASSERT_EQ(writer.get_chars_written(), 3);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, PercentConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%%";
+ simple_conv.conv_name = '%';
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "%");
+ ASSERT_EQ(writer.get_chars_written(), 1);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, WeekdayConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_wday = 1;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%a";
+ simple_conv.conv_name = 'a';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "Monday");
+ ASSERT_EQ(writer.get_chars_written(), 6);
+}
+TEST_F(LlvmLibcStrftimeConverterTest, AbbreviatedMonthNameConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_mon = 4; // May
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%b";
+ simple_conv.conv_name = 'b';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "May");
+ ASSERT_EQ(writer.get_chars_written(), 3);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, CenturyConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_year = 122; // Represents 2022
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%C";
+ simple_conv.conv_name = 'C';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "20");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthZeroPaddedConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_mday = 7;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%d";
+ simple_conv.conv_name = 'd';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "07");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthSpacePaddedConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_mday = 7;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%e";
+ simple_conv.conv_name = 'e';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, " 7");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, FullMonthNameConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_mon = 4; // May
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%B";
+ simple_conv.conv_name = 'B';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "May");
+ ASSERT_EQ(writer.get_chars_written(), 3);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, Hour12Conversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_hour = 14;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%I";
+ simple_conv.conv_name = 'I';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "02");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, Hour24PaddedConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_hour = 9;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%H";
+ simple_conv.conv_name = 'H';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "09");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, MinuteConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_min = 45;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%M";
+ simple_conv.conv_name = 'M';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "45");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, AMPMConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_hour = 14; // 2 PM
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%p";
+ simple_conv.conv_name = 'p';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "PM");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, SecondsConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_sec = 30;
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%S";
+ simple_conv.conv_name = 'S';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "30");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, FullYearConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_year = 122; // Represents 2022 (1900 + 122)
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%Y";
+ simple_conv.conv_name = 'Y';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "2022");
+ ASSERT_EQ(writer.get_chars_written(), 4);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, TwoDigitYearConversion) {
+ LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+ LIBC_NAMESPACE::strftime_core::tm time;
+ time.tm_year = 122; // Represents 2022 (1900 + 122)
+ simple_conv.has_conv = true;
+ simple_conv.raw_string = "%y";
+ simple_conv.conv_name = 'y';
+ simple_conv.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ ASSERT_STREQ(str, "22");
+ ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionEndOfYear) {
+ LIBC_NAMESPACE::strftime_core::FormatSection iso_year;
+ LIBC_NAMESPACE::strftime_core::tm time;
+
+ // Set up the time for December 31, 2022 (a Saturday)
+ time.tm_year = 122; // Represents 2022
+ time.tm_mon = 11; // December (0-based)
+ time.tm_mday = 31; // 31st day of the month
+ time.tm_wday = 6; // Saturday (0-based, 6 is Saturday)
+
+ iso_year.has_conv = true;
+ iso_year.raw_string = "%G";
+ iso_year.conv_name = 'G';
+ iso_year.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, iso_year);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ // The ISO year for this date is 2021
+ ASSERT_STREQ(str, "2021");
+ ASSERT_EQ(writer.get_chars_written(), 4);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionStartOfYear) {
+ LIBC_NAMESPACE::strftime_core::FormatSection iso_year;
+ LIBC_NAMESPACE::strftime_core::tm time;
+
+ // Set up the time for January 1, 2023 (a Sunday)
+ time.tm_year = 123; // Represents 2023
+ time.tm_mon = 0; // January (0-based)
+ time.tm_mday = 1; // 1st day of the month
+ time.tm_wday = 0; // Sunday (0-based, 0 is Sunday)
+
+ iso_year.has_conv = true;
+ iso_year.raw_string = "%G";
+ iso_year.conv_name = 'G';
+ iso_year.time = &time;
+
+ LIBC_NAMESPACE::strftime_core::convert(&writer, iso_year);
+
+ wb.buff[wb.buff_cur] = '\0';
+
+ // The ISO year for this date is 2022, not 2023
+ ASSERT_STREQ(str, "2022");
+ ASSERT_EQ(writer.get_chars_written(), 4);
+}
\ No newline at end of file
diff --git a/libc/test/src/stdio/strftime_core/parser_test.cpp b/libc/test/src/stdio/strftime_core/parser_test.cpp
new file mode 100644
index 00000000000000..e69de29bb2d1d6
>From 8ea3b4c06bcbb107e481721cabd02d05d8aa85d0 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Sun, 11 Aug 2024 09:26:00 -0500
Subject: [PATCH 2/3] [libc] Add stubs for 'strftime' function
Summary:
This patch does not actually implement the function, but provides the
interface. This partially resovles
https://github.com/llvm/llvm-project/issues/106630 as it will allow us
to build libc++ with locale support (so long as you don't use it).
---
libc/config/gpu/entrypoints.txt | 2 ++
libc/newhdrgen/yaml/time.yaml | 20 ++++++++++++++++++++
libc/spec/stdc.td | 21 +++++++++++++++++++++
libc/src/time/CMakeLists.txt | 23 ++++++++++++++++++++++-
libc/src/time/strftime.cpp | 26 ++++++++++++++++++++++++++
libc/src/time/strftime.h | 23 +++++++++++++++++++++++
libc/src/time/strftime_l.cpp | 24 ++++++++++++++++++++++++
libc/src/time/strftime_l.h | 24 ++++++++++++++++++++++++
8 files changed, 162 insertions(+), 1 deletion(-)
create mode 100644 libc/src/time/strftime.cpp
create mode 100644 libc/src/time/strftime.h
create mode 100644 libc/src/time/strftime_l.cpp
create mode 100644 libc/src/time/strftime_l.h
diff --git a/libc/config/gpu/entrypoints.txt b/libc/config/gpu/entrypoints.txt
index b4cfe47f4505fd..af05dc57023f08 100644
--- a/libc/config/gpu/entrypoints.txt
+++ b/libc/config/gpu/entrypoints.txt
@@ -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
diff --git a/libc/newhdrgen/yaml/time.yaml b/libc/newhdrgen/yaml/time.yaml
index 69b40bef3160dd..593b52b2d5a704 100644
--- a/libc/newhdrgen/yaml/time.yaml
+++ b/libc/newhdrgen/yaml/time.yaml
@@ -8,6 +8,7 @@ types:
- type_name: time_t
- type_name: clock_t
- type_name: size_t
+ - type_name: locale_t
enums: []
objects: []
functions:
@@ -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
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 7caf543748151a..f673e0e3bc246a 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -1586,6 +1586,7 @@ def StdC : StandardSpec<"stdc"> {
StructTimeSpec,
TimeTType,
SizeTType,
+ LocaleT,
],
[], // Enumerations
[
@@ -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>
+ ]
+ >,
]
>;
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index b3318e7ca87fa5..c45c50e2e9d7c4 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -99,11 +99,32 @@ add_entrypoint_object(
HDRS
mktime.h
DEPENDS
- .time_utils
libc.include.time
libc.src.errno.errno
)
+
+add_entrypoint_object(
+ strftime
+ SRCS
+ strftime.cpp
+ HDRS
+ strftime.h
+ DEPENDS
+ libc.include.time
+)
+
+add_entrypoint_object(
+ strftime_l
+ SRCS
+ strftime_l.cpp
+ HDRS
+ strftime_l.h
+ DEPENDS
+ libc.include.time
+ libc.include.locale
+)
+
add_entrypoint_object(
time
ALIAS
diff --git a/libc/src/time/strftime.cpp b/libc/src/time/strftime.cpp
new file mode 100644
index 00000000000000..4fa8a7fc3ec7df
--- /dev/null
+++ b/libc/src/time/strftime.cpp
@@ -0,0 +1,26 @@
+//===-- 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"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using LIBC_NAMESPACE::time_utils::TimeConstants;
+
+LLVM_LIBC_FUNCTION(size_t, strftime,
+ (char *__restrict, size_t, const char *__restrict,
+ const struct tm *)) {
+ // TODO: Implement this for the default locale.
+ return -1;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime.h b/libc/src/time/strftime.h
new file mode 100644
index 00000000000000..aa30336d8957d3
--- /dev/null
+++ b/libc/src/time/strftime.h
@@ -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
diff --git a/libc/src/time/strftime_l.cpp b/libc/src/time/strftime_l.cpp
new file mode 100644
index 00000000000000..c25103b58e8363
--- /dev/null
+++ b/libc/src/time/strftime_l.cpp
@@ -0,0 +1,24 @@
+//===-- Implementation of strftime_l 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_l.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/errno/libc_errno.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(size_t, strftime_l,
+ (char *__restrict, size_t, const char *__restrict,
+ const struct tm *, locale_t)) {
+ // TODO: Implement this for the default locale.
+ return -1;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_l.h b/libc/src/time/strftime_l.h
new file mode 100644
index 00000000000000..68c48007e6ad80
--- /dev/null
+++ b/libc/src/time/strftime_l.h
@@ -0,0 +1,24 @@
+//===-- Implementation header of strftime_l ---------------------*- 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_L_H
+#define LLVM_LIBC_SRC_TIME_STRFTIME_L_H
+
+#include "hdr/types/locale_t.h"
+#include "src/__support/macros/config.h"
+#include <stddef.h>
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+size_t strftime_l(char *__restrict, size_t max, const char *__restrict format,
+ const struct tm *timeptr, locale_t locale);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_TIME_STRFTIME_L_H
>From b00a6986b17abfb731cbdf35eb502cda889455aa Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Sun, 13 Oct 2024 23:38:08 +0800
Subject: [PATCH 3/3] [libc] Implement strftime
---
libc/src/stdio/CMakeLists.txt | 1 -
libc/src/stdio/strftime_core/CMakeLists.txt | 52 -------------------
libc/src/time/CMakeLists.txt | 49 +++++++++--------
libc/src/time/strftime.cpp | 12 +++--
libc/src/time/strftime_core/CMakeLists.txt | 46 ++++++++++++++++
.../strftime_core/converter.cpp | 4 +-
.../{stdio => time}/strftime_core/converter.h | 3 +-
.../strftime_core/core_structs.h | 44 ----------------
.../{stdio => time}/strftime_core/parser.h | 19 +------
libc/src/time/strftime_core/strftime_main.cpp | 42 +++++++++++++++
libc/src/time/strftime_core/strftime_main.h | 26 ++++++++++
.../strftime_core/time_internal_def.h | 0
libc/test/src/stdio/CMakeLists.txt | 1 -
libc/test/src/time/CMakeLists.txt | 2 +
.../strftime_core/CMakeLists.txt | 6 +--
.../strftime_core/converter_test.cpp | 4 +-
.../strftime_core/parser_test.cpp | 0
17 files changed, 159 insertions(+), 152 deletions(-)
delete mode 100644 libc/src/stdio/strftime_core/CMakeLists.txt
create mode 100644 libc/src/time/strftime_core/CMakeLists.txt
rename libc/src/{stdio => time}/strftime_core/converter.cpp (98%)
rename libc/src/{stdio => time}/strftime_core/converter.h (91%)
rename libc/src/{stdio => time}/strftime_core/core_structs.h (57%)
rename libc/src/{stdio => time}/strftime_core/parser.h (80%)
create mode 100644 libc/src/time/strftime_core/strftime_main.cpp
create mode 100644 libc/src/time/strftime_core/strftime_main.h
rename libc/src/{stdio => time}/strftime_core/time_internal_def.h (100%)
rename libc/test/src/{stdio => time}/strftime_core/CMakeLists.txt (52%)
rename libc/test/src/{stdio => time}/strftime_core/converter_test.cpp (98%)
rename libc/test/src/{stdio => time}/strftime_core/parser_test.cpp (100%)
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 5338c17d6232b3..b9bc904471df9a 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -239,7 +239,6 @@ add_entrypoint_object(
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
-add_subdirectory(strftime_core)
add_entrypoint_object(
remove
diff --git a/libc/src/stdio/strftime_core/CMakeLists.txt b/libc/src/stdio/strftime_core/CMakeLists.txt
deleted file mode 100644
index e234d8d064e159..00000000000000
--- a/libc/src/stdio/strftime_core/CMakeLists.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-
-add_header_library(
- core_structs
- HDRS
- core_structs.h
- DEPENDS
- libc.src.__support.CPP.string_view
- libc.src.__support.FPUtil.fp_bits
-)
-
-add_header_library(
- parser
- HDRS
- parser.h
- DEPENDS
- .core_structs
- libc.src.__support.arg_list
- libc.src.__support.ctype_utils
- libc.src.__support.str_to_integer
- libc.src.__support.CPP.algorithm
- libc.src.__support.CPP.bit
- libc.src.__support.CPP.optional
- libc.src.__support.CPP.string_view
- libc.src.__support.CPP.type_traits
- libc.src.__support.common
-)
-
-
-add_object_library(
- converter
- SRCS
- converter.cpp
- HDRS
- converter.h
- DEPENDS
- .core_structs
- libc.src.stdio.printf_core.writer
- libc.src.__support.big_int
- libc.src.math.log10
- libc.src.__support.common
- libc.src.__support.CPP.limits
- libc.src.__support.CPP.span
- libc.src.__support.CPP.string_view
- libc.src.__support.float_to_string
- libc.src.__support.FPUtil.fenv_impl
- libc.src.__support.FPUtil.fp_bits
- libc.src.__support.FPUtil.rounding_mode
- libc.src.__support.integer_to_string
- libc.src.__support.libc_assert
- libc.src.__support.uint128
- libc.src.__support.StringUtil.error_to_string
-)
\ No newline at end of file
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index c45c50e2e9d7c4..f341a66731a8c9 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -103,28 +103,6 @@ add_entrypoint_object(
libc.src.errno.errno
)
-
-add_entrypoint_object(
- strftime
- SRCS
- strftime.cpp
- HDRS
- strftime.h
- DEPENDS
- libc.include.time
-)
-
-add_entrypoint_object(
- strftime_l
- SRCS
- strftime_l.cpp
- HDRS
- strftime_l.h
- DEPENDS
- libc.include.time
- libc.include.locale
-)
-
add_entrypoint_object(
time
ALIAS
@@ -159,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
+)
\ No newline at end of file
diff --git a/libc/src/time/strftime.cpp b/libc/src/time/strftime.cpp
index 4fa8a7fc3ec7df..85b7b636da597e 100644
--- a/libc/src/time/strftime.cpp
+++ b/libc/src/time/strftime.cpp
@@ -12,14 +12,16 @@
#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 {
-using LIBC_NAMESPACE::time_utils::TimeConstants;
+size_t strftime(char *__restrict buffer, size_t buffsz, const char *__restrict format,
+ const struct tm *timeptr) {
-LLVM_LIBC_FUNCTION(size_t, strftime,
- (char *__restrict, size_t, const char *__restrict,
- const struct tm *)) {
- // TODO: Implement this for the default locale.
+ printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+ printf_core::Writer writer(&wb);
+ strftime_core::strftime_main(&writer, format, timeptr);
return -1;
}
diff --git a/libc/src/time/strftime_core/CMakeLists.txt b/libc/src/time/strftime_core/CMakeLists.txt
new file mode 100644
index 00000000000000..dcd1329996ccd5
--- /dev/null
+++ b/libc/src/time/strftime_core/CMakeLists.txt
@@ -0,0 +1,46 @@
+
+add_header_library(
+ core_structs
+ HDRS
+ core_structs.h
+ DEPENDS
+ libc.src.__support.CPP.string_view
+)
+
+add_header_library(
+ parser
+ HDRS
+ parser.h
+)
+
+add_object_library(
+ converter
+ SRCS
+ converter.cpp
+ HDRS
+ converter.h
+ DEPENDS
+ .core_structs
+ .parser
+ libc.src.stdio.printf_core.writer
+ libc.src.__support.big_int
+ libc.src.math.log10
+ 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
+)
+
+add_object_library(
+ strftime_main
+ SRCS
+ strftime_main.cpp
+ HDRS
+ strftime_main.h
+ DEPENDS
+ .core_structs
+ .parser
+ .converter
+ libc.src.stdio.printf_core.writer
+)
\ No newline at end of file
diff --git a/libc/src/stdio/strftime_core/converter.cpp b/libc/src/time/strftime_core/converter.cpp
similarity index 98%
rename from libc/src/stdio/strftime_core/converter.cpp
rename to libc/src/time/strftime_core/converter.cpp
index 79e3727cc88f5b..7df7365b08ca98 100644
--- a/libc/src/stdio/strftime_core/converter.cpp
+++ b/libc/src/time/strftime_core/converter.cpp
@@ -15,8 +15,8 @@
#include "src/__support/macros/config.h"
#include "src/math/log10.h"
#include "src/stdio/printf_core/writer.h"
-#include "src/stdio/strftime_core/core_structs.h"
-#include "src/stdio/strftime_core/time_internal_def.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/time_internal_def.h"
#include <time.h>
namespace LIBC_NAMESPACE_DECL {
diff --git a/libc/src/stdio/strftime_core/converter.h b/libc/src/time/strftime_core/converter.h
similarity index 91%
rename from libc/src/stdio/strftime_core/converter.h
rename to libc/src/time/strftime_core/converter.h
index 57801b8e8e1262..8809a8356e5c59 100644
--- a/libc/src/stdio/strftime_core/converter.h
+++ b/libc/src/time/strftime_core/converter.h
@@ -9,9 +9,8 @@
#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
-#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/writer.h"
-#include "src/stdio/strftime_core/core_structs.h"
+#include "src/time/strftime_core/core_structs.h"
#include <stddef.h>
diff --git a/libc/src/stdio/strftime_core/core_structs.h b/libc/src/time/strftime_core/core_structs.h
similarity index 57%
rename from libc/src/stdio/strftime_core/core_structs.h
rename to libc/src/time/strftime_core/core_structs.h
index 404902bcfbf6f3..2e01ac18c99eed 100644
--- a/libc/src/stdio/strftime_core/core_structs.h
+++ b/libc/src/time/strftime_core/core_structs.h
@@ -9,14 +9,7 @@
#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
-#include "src/__support/macros/config.h"
-
#include "src/__support/CPP/string_view.h"
-#include "src/__support/CPP/type_traits.h"
-#include "src/__support/FPUtil/FPBits.h"
-
-#include <inttypes.h>
-#include <stddef.h>
namespace LIBC_NAMESPACE_DECL {
namespace strftime_core {
@@ -44,43 +37,6 @@ struct FormatSection {
const struct tm *time;
};
-enum PrimaryType : uint8_t {
- Unknown = 0,
- Float = 1,
- Pointer = 2,
- Integer = 3,
- FixedPoint = 4,
-};
-
-// TypeDesc stores the information about a type that is relevant to printf in
-// a relatively compact manner.
-struct TypeDesc {
- uint8_t size;
- PrimaryType primary_type;
- LIBC_INLINE constexpr bool operator==(const TypeDesc &other) const {
- return (size == other.size) && (primary_type == other.primary_type);
- }
-};
-
-template <typename T> LIBC_INLINE constexpr TypeDesc type_desc_from_type() {
- if constexpr (cpp::is_same_v<T, void>) {
- return TypeDesc{0, PrimaryType::Unknown};
- } else {
- constexpr bool IS_POINTER = cpp::is_pointer_v<T>;
- constexpr bool IS_FLOAT = cpp::is_floating_point_v<T>;
-#ifdef LIBC_INTERNAL_STRFTIME_HAS_FIXED_POINT
- constexpr bool IS_FIXED_POINT = cpp::is_fixed_point_v<T>;
-#else
- constexpr bool IS_FIXED_POINT = false;
-#endif // LIBC_INTERNAL_STRFTIME_HAS_FIXED_POINT
-
- return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
- : IS_FLOAT ? PrimaryType::Float
- : IS_FIXED_POINT ? PrimaryType::FixedPoint
- : PrimaryType::Integer};
- }
-}
-
// This is the value to be returned by conversions when no error has occurred.
constexpr int WRITE_OK = 0;
// These are the printf return values for when an error has occurred. They are
diff --git a/libc/src/stdio/strftime_core/parser.h b/libc/src/time/strftime_core/parser.h
similarity index 80%
rename from libc/src/stdio/strftime_core/parser.h
rename to libc/src/time/strftime_core/parser.h
index 046f5b1095ad0b..f9297677f30c6c 100644
--- a/libc/src/stdio/strftime_core/parser.h
+++ b/libc/src/time/strftime_core/parser.h
@@ -9,24 +9,7 @@
#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_PARSER_H
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_PARSER_H
-#include "include/llvm-libc-macros/stdfix-macros.h"
-#include "src/__support/CPP/algorithm.h" // max
-#include "src/__support/CPP/limits.h"
-#include "src/__support/CPP/optional.h"
-#include "src/__support/CPP/type_traits.h"
-#include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
-#include "src/stdio/strftime_core/core_structs.h"
-// #include "src/stdio/strftime_core/printf_config.h"
-
-#include <stddef.h>
-
-#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
-#include "src/__support/fixed_point/fx_rep.h"
-#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
-#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
-#include "src/errno/libc_errno.h"
-#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
+#include <time.h>
namespace LIBC_NAMESPACE_DECL {
namespace strftime_core {
diff --git a/libc/src/time/strftime_core/strftime_main.cpp b/libc/src/time/strftime_core/strftime_main.cpp
new file mode 100644
index 00000000000000..0359da0ad4fe3c
--- /dev/null
+++ b/libc/src/time/strftime_core/strftime_main.cpp
@@ -0,0 +1,42 @@
+//===-- Starting point for 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/time/strftime_core/strftime_main.h"
+
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/converter.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/parser.h"
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int strftime_main(Writer *writer, const char *__restrict str,
+ const struct tm *timeptr) {
+ Parser parser(str, timeptr);
+ int result = 0;
+ for (FormatSection cur_section = parser.get_next_section();
+ !cur_section.raw_string.empty();
+ cur_section = parser.get_next_section()) {
+ if (cur_section.has_conv)
+ result = convert(writer, cur_section);
+ else
+ result = writer->write(cur_section.raw_string);
+
+ if (result < 0)
+ return result;
+ }
+
+ return writer->get_chars_written();
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_core/strftime_main.h b/libc/src/time/strftime_core/strftime_main.h
new file mode 100644
index 00000000000000..38d7a536adb672
--- /dev/null
+++ b/libc/src/time/strftime_core/strftime_main.h
@@ -0,0 +1,26 @@
+//===-- Starting point for 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_STDIO_STRFTIME_CORE_STRFTIME_MAIN_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STRFTIME_MAIN_H
+
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/writer.h"
+
+#include <stddef.h>
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int strftime_main(printf_core::Writer *writer, const char *__restrict str, const struct tm *timeptr);
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STRFTIME_MAIN_H
diff --git a/libc/src/stdio/strftime_core/time_internal_def.h b/libc/src/time/strftime_core/time_internal_def.h
similarity index 100%
rename from libc/src/stdio/strftime_core/time_internal_def.h
rename to libc/src/time/strftime_core/time_internal_def.h
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index c43c24dc3459a7..e970e4cf014052 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -542,5 +542,4 @@ endif()
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
-add_subdirectory(strftime_core)
add_subdirectory(testdata)
diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt
index bba01f063fed27..516e1d65bf883f 100644
--- a/libc/test/src/time/CMakeLists.txt
+++ b/libc/test/src/time/CMakeLists.txt
@@ -173,3 +173,5 @@ add_libc_test(
libc.src.time.clock
libc.src.errno.errno
)
+
+add_subdirectory(strftime_core)
diff --git a/libc/test/src/stdio/strftime_core/CMakeLists.txt b/libc/test/src/time/strftime_core/CMakeLists.txt
similarity index 52%
rename from libc/test/src/stdio/strftime_core/CMakeLists.txt
rename to libc/test/src/time/strftime_core/CMakeLists.txt
index e47cd0f8bb2f8b..8b8c17e668097a 100644
--- a/libc/test/src/stdio/strftime_core/CMakeLists.txt
+++ b/libc/test/src/time/strftime_core/CMakeLists.txt
@@ -1,11 +1,11 @@
add_libc_unittest(
converter_test
SUITE
- libc_stdio_unittests
+ libc_time_unittests
SRCS
converter_test.cpp
DEPENDS
- libc.src.stdio.strftime_core.converter
+ libc.src.time.strftime_core.converter
libc.src.stdio.printf_core.writer
- libc.src.stdio.strftime_core.core_structs
+ libc.src.time.strftime_core.core_structs
)
diff --git a/libc/test/src/stdio/strftime_core/converter_test.cpp b/libc/test/src/time/strftime_core/converter_test.cpp
similarity index 98%
rename from libc/test/src/stdio/strftime_core/converter_test.cpp
rename to libc/test/src/time/strftime_core/converter_test.cpp
index 53ea3694227ef4..3d20dae5a8b21f 100644
--- a/libc/test/src/stdio/strftime_core/converter_test.cpp
+++ b/libc/test/src/time/strftime_core/converter_test.cpp
@@ -7,8 +7,8 @@
//===----------------------------------------------------------------------===//
#include "src/stdio/printf_core/writer.h"
-#include "src/stdio/strftime_core/converter.h"
-#include "src/stdio/strftime_core/core_structs.h"
+#include "src/time/strftime_core/converter.h"
+#include "src/time/strftime_core/core_structs.h"
#include "test/UnitTest/Test.h"
diff --git a/libc/test/src/stdio/strftime_core/parser_test.cpp b/libc/test/src/time/strftime_core/parser_test.cpp
similarity index 100%
rename from libc/test/src/stdio/strftime_core/parser_test.cpp
rename to libc/test/src/time/strftime_core/parser_test.cpp
More information about the libc-commits
mailing list