[libc-commits] [libc] [WIP] Basic structures for strftime (PR #111305)

Tsz Chan via libc-commits libc-commits at lists.llvm.org
Sun Oct 6 10:19:41 PDT 2024


https://github.com/tszhin-swe created https://github.com/llvm/llvm-project/pull/111305

None

>From 2f0647568467e3ac66b550aefca931b6e89d5e78 Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Mon, 7 Oct 2024 01:17:04 +0800
Subject: [PATCH] [WIP] Basic structures for strftime

---
 libc/src/stdio/CMakeLists.txt                 |   1 +
 libc/src/stdio/strftime_core/CMakeLists.txt   |  52 ++++
 libc/src/stdio/strftime_core/converter.cpp    | 186 ++++++++++++
 libc/src/stdio/strftime_core/converter.h      |  29 ++
 libc/src/stdio/strftime_core/core_structs.h   |  97 +++++++
 libc/src/stdio/strftime_core/parser.h         | 126 ++++++++
 .../stdio/strftime_core/time_internal_def.h   |  40 +++
 libc/test/src/stdio/CMakeLists.txt            |   1 +
 .../src/stdio/strftime_core/CMakeLists.txt    |  11 +
 .../stdio/strftime_core/converter_test.cpp    | 272 ++++++++++++++++++
 .../src/stdio/strftime_core/parser_test.cpp   |   0
 11 files changed, 815 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..9046f07848b242
--- /dev/null
+++ b/libc/src/stdio/strftime_core/converter.cpp
@@ -0,0 +1,186 @@
+//===-- 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/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
+
+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 + 1900, 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 + 1900) % 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 + 1900) / 100, writer);
+}
+
+LIBC_INLINE int convert_iso_year(printf_core::Writer *writer,
+                                 const FormatSection &to_conv) {
+  // TODO
+  return details::write_num_with_padding<4, '0'>(to_conv.time->tm_year + 1900, 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':  // Full year (e.g., 2024)
+    return convert_full_year(writer, to_conv);
+  case 'y':  // Two-digit year (e.g., 24 for 2024)
+    return convert_two_digit_year(writer, to_conv);
+  case 'C':  // Century (e.g., 20 for 2024)
+    return convert_century(writer, to_conv);
+  case 'G':  // ISO 8601 year
+    // TODO
+    return convert_iso_year(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..f1920462661177
--- /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/strftime_core/core_structs.h"
+#include "src/stdio/printf_core/writer.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..f8176e6d025ea2
--- /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..37a1e9019add5b
--- /dev/null
+++ b/libc/src/stdio/strftime_core/parser.h
@@ -0,0 +1,126 @@
+//===-- 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..e26035c5a30f4f
--- /dev/null
+++ b/libc/src/stdio/strftime_core/time_internal_def.h
@@ -0,0 +1,40 @@
+//===-- 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;
+
+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"
+};
+
+
+
+
+}}
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..b4aa277afbb841
--- /dev/null
+++ b/libc/test/src/stdio/strftime_core/converter_test.cpp
@@ -0,0 +1,272 @@
+//===-- 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/strftime_core/converter.h"
+#include "src/stdio/strftime_core/core_structs.h"
+#include "src/stdio/printf_core/writer.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);
+}
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



More information about the libc-commits mailing list