[libc-commits] [libc] [libc] Implement strftime (PR #111305)

Tsz Chan via libc-commits libc-commits at lists.llvm.org
Wed Oct 23 06:51:47 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/4] [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/4] [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 097d459969b1d60ba44a607548b09c19b64eb6f3 Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Sun, 20 Oct 2024 00:09:14 +0800
Subject: [PATCH 3/4] [libc] Implement strftime and strftime_l

---
 libc/src/stdio/CMakeLists.txt                 |   1 -
 libc/src/stdio/strftime_core/CMakeLists.txt   |  52 -----
 libc/src/stdio/strftime_core/core_structs.h   |  97 --------
 libc/src/time/CMakeLists.txt                  |  55 +++--
 libc/src/time/strftime.cpp                    |  14 +-
 libc/src/time/strftime_core/CMakeLists.txt    |  52 +++++
 .../strftime_core/converter.cpp               | 214 ++++++++++++++----
 .../{stdio => time}/strftime_core/converter.h |   3 +-
 libc/src/time/strftime_core/core_structs.h    |  42 ++++
 .../{stdio => time}/strftime_core/parser.h    | 111 ++++-----
 libc/src/time/strftime_core/strftime_main.cpp |  43 ++++
 libc/src/time/strftime_core/strftime_main.h   |  28 +++
 .../strftime_core/time_internal_def.h         |  20 ++
 libc/src/time/strftime_l.cpp                  |  13 +-
 libc/test/src/stdio/CMakeLists.txt            |   1 -
 .../src/stdio/strftime_core/CMakeLists.txt    |  11 -
 .../src/stdio/strftime_core/parser_test.cpp   |   0
 libc/test/src/time/CMakeLists.txt             |  12 +
 .../src/time/strftime_core/CMakeLists.txt     |  23 ++
 .../strftime_core/converter_test.cpp          | 164 ++++++++++++--
 .../src/time/strftime_core/parser_test.cpp    | 102 +++++++++
 libc/test/src/time/strftime_test.cpp          |  98 ++++++++
 22 files changed, 824 insertions(+), 332 deletions(-)
 delete mode 100644 libc/src/stdio/strftime_core/CMakeLists.txt
 delete mode 100644 libc/src/stdio/strftime_core/core_structs.h
 create mode 100644 libc/src/time/strftime_core/CMakeLists.txt
 rename libc/src/{stdio => time}/strftime_core/converter.cpp (60%)
 rename libc/src/{stdio => time}/strftime_core/converter.h (91%)
 create mode 100644 libc/src/time/strftime_core/core_structs.h
 rename libc/src/{stdio => time}/strftime_core/parser.h (53%)
 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 (71%)
 delete mode 100644 libc/test/src/stdio/strftime_core/CMakeLists.txt
 delete mode 100644 libc/test/src/stdio/strftime_core/parser_test.cpp
 create mode 100644 libc/test/src/time/strftime_core/CMakeLists.txt
 rename libc/test/src/{stdio => time}/strftime_core/converter_test.cpp (66%)
 create mode 100644 libc/test/src/time/strftime_core/parser_test.cpp
 create mode 100644 libc/test/src/time/strftime_test.cpp

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/stdio/strftime_core/core_structs.h b/libc/src/stdio/strftime_core/core_structs.h
deleted file mode 100644
index 404902bcfbf6f3..00000000000000
--- a/libc/src/stdio/strftime_core/core_structs.h
+++ /dev/null
@@ -1,97 +0,0 @@
-//===-- 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/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index c45c50e2e9d7c4..6e6c21bc657386 100644
--- a/libc/src/time/CMakeLists.txt
+++ b/libc/src/time/CMakeLists.txt
@@ -1,6 +1,6 @@
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-endif()
+# if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+#   add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+# endif()
 
 add_object_library(
   time_utils
@@ -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..7c20268cec9a30 100644
--- a/libc/src/time/strftime.cpp
+++ b/libc/src/time/strftime.cpp
@@ -12,15 +12,17 @@
 #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.
-  return -1;
+  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(&wb);
+  strftime_core::strftime_main(&writer, format, timeptr);
+  return writer.get_chars_written();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_core/CMakeLists.txt b/libc/src/time/strftime_core/CMakeLists.txt
new file mode 100644
index 00000000000000..78f37ea3ef7078
--- /dev/null
+++ b/libc/src/time/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.include.time
+)
+
+add_header_library(
+  parser
+  HDRS
+    parser.h
+  DEPENDS
+    .core_structs
+    libc.src.string.string_utils
+    libc.include.time
+
+)
+
+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.__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
+    libc.include.time
+
+)
+
+add_object_library(
+  strftime_main
+  SRCS
+    strftime_main.cpp
+  HDRS
+    strftime_main.h
+  DEPENDS
+    .core_structs
+    .parser
+    .converter
+    libc.src.stdio.printf_core.writer
+    libc.include.time
+)
\ 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 60%
rename from libc/src/stdio/strftime_core/converter.cpp
rename to libc/src/time/strftime_core/converter.cpp
index 79e3727cc88f5b..d7b3e57f19ce8a 100644
--- a/libc/src/stdio/strftime_core/converter.cpp
+++ b/libc/src/time/strftime_core/converter.cpp
@@ -13,15 +13,20 @@
 #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>
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/time_internal_def.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
+#define RET_IF_RESULT_NEGATIVE(func)                                           \
+  {                                                                            \
+    int result = (func);                                                       \
+    if (result < 0)                                                            \
+      return result;                                                           \
+  }
+
 namespace details {
 
 LIBC_INLINE cpp::optional<cpp::string_view>
@@ -35,18 +40,29 @@ LIBC_INLINE int write_num(uintmax_t num, printf_core::Writer *writer) {
   return writer->write(*num_to_strview(num, buf));
 }
 
+template <typename T> int count_digits(T num) {
+  if (num == 0)
+    return 1;
+  int digits = 0;
+  while (num > 0) {
+    num /= 10;
+    digits++;
+  }
+  return digits;
+}
+
 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;
+  int digits = count_digits(num);
+  int padding_needed = width - digits;
+
   for (int _ = 0; _ < padding_needed; _++) {
-    char_written += writer->write(padding);
+    RET_IF_RESULT_NEGATIVE(writer->write(padding));
   }
-  char_written += writer->write(*num_to_strview(num, buf));
-  return char_written;
+
+  return writer->write(*num_to_strview(num, buf));
 }
 
 } // namespace details
@@ -54,12 +70,19 @@ LIBC_INLINE int write_num_with_padding(uintmax_t num,
 /* 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));
+  return ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0));
 }
 
+// Conversion functions
+
 LIBC_INLINE int convert_weekday(printf_core::Writer *writer,
                                 const FormatSection &to_conv) {
-  return writer->write(day_names[to_conv.time->tm_wday]);
+  return writer->write(safe_day_name(to_conv.time->tm_wday));
+}
+
+LIBC_INLINE int convert_abbreviated_weekday(printf_core::Writer *writer,
+                                            const FormatSection &to_conv) {
+  return writer->write(safe_abbreviated_day_name(to_conv.time->tm_wday));
 }
 
 LIBC_INLINE int convert_zero_padded_day_of_year(printf_core::Writer *writer,
@@ -108,12 +131,12 @@ LIBC_INLINE int convert_week_number_monday(printf_core::Writer *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]);
+  return writer->write(safe_month_name(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]);
+  return writer->write(safe_abbreviated_month_name(to_conv.time->tm_mon));
 }
 
 LIBC_INLINE int convert_zero_padded_month(printf_core::Writer *writer,
@@ -124,20 +147,100 @@ LIBC_INLINE int convert_zero_padded_month(printf_core::Writer *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);
+  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 + YEAR_BASE) % 100, writer);
+      (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 + YEAR_BASE) / 100, writer);
+      (to_conv.time->tm_year + 1900) / 100, 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;
+  if (hour == 0)
+    hour = 12;
+  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);
+}
+
+LIBC_INLINE int convert_MM_DD_YY(printf_core::Writer *writer,
+                                 const FormatSection &to_conv) {
+  RET_IF_RESULT_NEGATIVE(convert_zero_padded_month(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write("/"));
+  RET_IF_RESULT_NEGATIVE(convert_zero_padded_day_of_month(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write("/"));
+  return convert_two_digit_year(writer, to_conv);
+}
+
+LIBC_INLINE int convert_YYYY_MM_DD(printf_core::Writer *writer,
+                                   const FormatSection &to_conv) {
+  RET_IF_RESULT_NEGATIVE(convert_full_year(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write("-"));
+  RET_IF_RESULT_NEGATIVE(convert_zero_padded_month(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write("-"));
+  return convert_zero_padded_day_of_month(writer, to_conv);
+}
+
+LIBC_INLINE int convert_hour_minute(printf_core::Writer *writer,
+                                    const FormatSection &to_conv) {
+  RET_IF_RESULT_NEGATIVE(convert_hour_24(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(":"));
+  return convert_minute(writer, to_conv);
+}
+
+LIBC_INLINE int convert_date_time(printf_core::Writer *writer,
+                                  const FormatSection &to_conv) {
+  RET_IF_RESULT_NEGATIVE(convert_abbreviated_weekday(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(" "));
+  RET_IF_RESULT_NEGATIVE(convert_abbreviated_month(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(" "));
+  RET_IF_RESULT_NEGATIVE(convert_space_padded_day_of_month(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(" "));
+  RET_IF_RESULT_NEGATIVE(convert_full_year(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(" "));
+  RET_IF_RESULT_NEGATIVE(convert_hour_24(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(":"));
+  RET_IF_RESULT_NEGATIVE(convert_minute(writer, to_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(":"));
+  return convert_second(writer, to_conv);
+}
+
+LIBC_INLINE int convert_time_zone(printf_core::Writer *writer) {
+  return writer->write("UTC");
+}
+
+LIBC_INLINE int convert_time_zone_offset(printf_core::Writer *writer) {
+  return writer->write("+0000");
 }
 
 static int iso_week_days(int yday, int wday) {
@@ -170,43 +273,39 @@ LIBC_INLINE int convert_iso_year(printf_core::Writer *writer,
   return details::write_num<4>(year, writer);
 }
 
-LIBC_INLINE int convert_hour_24(printf_core::Writer *writer,
+LIBC_INLINE int convert_iso_day(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);
-}
+  int year = to_conv.time->tm_year + YEAR_BASE;
+  int days = iso_week_days(to_conv.time->tm_yday, to_conv.time->tm_wday);
 
-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);
+  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_with_padding<2, '0'>(days / 7 + 1, 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) {
+  case '%':
+    return writer->write("%");
   // day of the week
-  case 'a':
+  case 'A':
     return convert_weekday(writer, to_conv);
+  case 'a':
+    return convert_abbreviated_weekday(writer, to_conv);
   case 'w':
     return convert_decimal_weekday(writer, to_conv);
   case 'u':
@@ -223,7 +322,8 @@ int convert(printf_core::Writer *writer, const FormatSection &to_conv) {
     return convert_week_number_sunday(writer, to_conv);
   case 'W':
     return convert_week_number_monday(writer, to_conv);
-  case 'V': // TODO: ISO 8061
+  case 'V':
+    return convert_iso_day(writer, to_conv);
   // month
   case 'B':
     return convert_full_month(writer, to_conv);
@@ -240,7 +340,6 @@ int convert(printf_core::Writer *writer, const FormatSection &to_conv) {
   case 'C':
     return convert_century(writer, to_conv);
   case 'G':
-    // TODO
     return convert_iso_year(writer, to_conv);
   // hours
   case 'p':
@@ -255,6 +354,27 @@ int convert(printf_core::Writer *writer, const FormatSection &to_conv) {
   // seconds
   case 'S':
     return convert_second(writer, to_conv);
+    // Date and Time
+  case 'c':
+    return convert_date_time(writer, to_conv);
+
+  // Timezone
+  case 'Z':
+    return convert_time_zone(writer);
+  case 'z':
+    return convert_time_zone_offset(writer);
+
+  // Custom formats: MM/DD/YY, YYYY-MM-DD
+  case 'D':
+    return convert_MM_DD_YY(writer, to_conv); // Equivalent to %m/%d/%y
+  case 'F':
+    return convert_YYYY_MM_DD(writer, to_conv); // Equivalent to %Y-%m-%d
+
+  // 24-hour time without seconds (HH:MM)
+  case 'R':
+    return convert_hour_minute(writer, to_conv); // Equivalent to %H:%M
+  default:
+    writer->write(to_conv.raw_string);
   }
   return 0;
 }
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/time/strftime_core/core_structs.h b/libc/src/time/strftime_core/core_structs.h
new file mode 100644
index 00000000000000..d6cd91884b8a67
--- /dev/null
+++ b/libc/src/time/strftime_core/core_structs.h
@@ -0,0 +1,42 @@
+//===-- 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/CPP/string_view.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+struct FormatSection {
+  bool has_conv{false};
+  bool isE{false};
+  bool isO{false};
+  cpp::string_view raw_string{};
+  char conv_name;
+  const struct tm *time;
+  int min_width{0};
+  char padding;
+};
+
+// 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/time/strftime_core/parser.h
similarity index 53%
rename from libc/src/stdio/strftime_core/parser.h
rename to libc/src/time/strftime_core/parser.h
index 046f5b1095ad0b..59bba59588a9e3 100644
--- a/libc/src/stdio/strftime_core/parser.h
+++ b/libc/src/time/strftime_core/parser.h
@@ -9,35 +9,42 @@
 #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 "core_structs.h"
+#include "src/__support/CPP/string_view.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 "src/string/string_utils.h"
+#include <time.h>
 
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
+static constexpr cpp::string_view valid_conversions_after_E = "cCxXyY";
+static constexpr cpp::string_view valid_conversions_after_O =
+    "dHeHIOmMSuUVwWyY";
+static constexpr cpp::string_view all_valid_conversions =
+    "%aAbBcCdDeFgGhHIjmMnprRSuUVwWxXyYzZ";
+
+int min_width(char conv) {
+  if (internal::strchr_implementation("CdegHImMSUVWy", conv))
+    return 2;
+  if (conv == 'j')
+    return 3;
+  return 0;
+}
+
+char get_padding(char conv) {
+  if (internal::strchr_implementation("CdgHIjmMSUVWy", conv))
+    return '0';
+  return ' ';
+}
+
 class Parser {
-  const char *__restrict str;
+  const char *str;
   const struct tm &time;
   size_t cur_pos = 0;
 
 public:
-  LIBC_INLINE Parser(const char *__restrict new_str, const tm &time)
+  LIBC_INLINE Parser(const char *new_str, const struct tm &time)
       : str(new_str), time(time) {}
 
   // get_next_section will parse the format string until it has a fully
@@ -56,59 +63,33 @@ class Parser {
       // format section
       section.has_conv = true;
       section.time = &time;
+      ++cur_pos;
       // locale-specific modifiers
-      if (str[cur_pos] == 'E')
+      if (str[cur_pos] == 'E') {
         section.isE = true;
-      if (str[cur_pos] == 'O')
+        ++cur_pos;
+      }
+      if (str[cur_pos] == 'O') {
         section.isO = true;
-      ++cur_pos;
+        ++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.
+      // Check if modifiers are valid
+      if ((section.isE &&
+           !internal::strchr_implementation(valid_conversions_after_E.data(),
+                                            str[cur_pos])) ||
+          (section.isO &&
+           !internal::strchr_implementation(valid_conversions_after_O.data(),
+                                            str[cur_pos])) ||
+          (!internal::strchr_implementation(all_valid_conversions.data(),
+                                            str[cur_pos]))) {
         section.has_conv = false;
-        break;
       }
+
+      section.min_width = min_width(str[cur_pos]);
+      section.padding = get_padding(str[cur_pos]);
+
       // 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')
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..ab79d0a8ce5de9
--- /dev/null
+++ b/libc/src/time/strftime_core/strftime_main.cpp
@@ -0,0 +1,43 @@
+//===-- 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>
+#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) {
+  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..fa1d0ad1a4e2d1
--- /dev/null
+++ b/libc/src/time/strftime_core/strftime_main.h
@@ -0,0 +1,28 @@
+//===-- 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 71%
rename from libc/src/stdio/strftime_core/time_internal_def.h
rename to libc/src/time/strftime_core/time_internal_def.h
index a7672ad43704e8..198897ef9186b9 100644
--- a/libc/src/stdio/strftime_core/time_internal_def.h
+++ b/libc/src/time/strftime_core/time_internal_def.h
@@ -39,5 +39,25 @@ static constexpr cpp::array<cpp::string_view, NUM_MONTHS>
     abbreviated_month_names = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
 
+static constexpr cpp::string_view out_of_bound_str =
+    "?"; // From glibc output ? when days out of range
+
+LIBC_INLINE cpp::string_view safe_day_name(int day) {
+  return (day < 0 || day > 6) ? out_of_bound_str : day_names[day];
+}
+
+LIBC_INLINE cpp::string_view safe_abbreviated_day_name(int day) {
+  return (day < 0 || day > 6) ? out_of_bound_str : abbreviated_day_names[day];
+}
+
+LIBC_INLINE cpp::string_view safe_month_name(int month) {
+  return (month < 0 || month > 11) ? out_of_bound_str : month_names[month];
+}
+
+LIBC_INLINE cpp::string_view safe_abbreviated_month_name(int month) {
+  return (month < 0 || month > 11) ? out_of_bound_str
+                                   : abbreviated_month_names[month];
+}
+
 } // namespace strftime_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_l.cpp b/libc/src/time/strftime_l.cpp
index c25103b58e8363..a6dc28dd62e370 100644
--- a/libc/src/time/strftime_l.cpp
+++ b/libc/src/time/strftime_l.cpp
@@ -12,13 +12,18 @@
 #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 {
 
 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;
+                   (char *__restrict buffer, size_t buffsz,
+                    const char *__restrict format, const struct tm *timeptr,
+                    locale_t)) {
+  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(&wb);
+  strftime_core::strftime_main(&writer, format, timeptr);
+  return writer.get_chars_written();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
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/stdio/strftime_core/CMakeLists.txt b/libc/test/src/stdio/strftime_core/CMakeLists.txt
deleted file mode 100644
index e47cd0f8bb2f8b..00000000000000
--- a/libc/test/src/stdio/strftime_core/CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-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/parser_test.cpp b/libc/test/src/stdio/strftime_core/parser_test.cpp
deleted file mode 100644
index e69de29bb2d1d6..00000000000000
diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt
index bba01f063fed27..ac6f88e3541554 100644
--- a/libc/test/src/time/CMakeLists.txt
+++ b/libc/test/src/time/CMakeLists.txt
@@ -173,3 +173,15 @@ add_libc_test(
     libc.src.time.clock
     libc.src.errno.errno
 )
+
+add_subdirectory(strftime_core)
+
+add_libc_test(
+  strftime_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    strftime_test.cpp
+  DEPENDS
+    libc.src.time.strftime_core.strftime_main
+)
\ No newline at end of file
diff --git a/libc/test/src/time/strftime_core/CMakeLists.txt b/libc/test/src/time/strftime_core/CMakeLists.txt
new file mode 100644
index 00000000000000..d07f2806ab3831
--- /dev/null
+++ b/libc/test/src/time/strftime_core/CMakeLists.txt
@@ -0,0 +1,23 @@
+add_libc_unittest(
+  converter_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    converter_test.cpp
+  DEPENDS
+    libc.src.time.strftime_core.converter
+    libc.src.stdio.printf_core.writer
+    libc.src.time.strftime_core.core_structs
+)
+
+add_libc_unittest(
+  parser_test
+  SUITE
+    libc_time_unittests
+  SRCS
+    parser_test.cpp
+  DEPENDS
+    libc.src.time.strftime_core.parser
+    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 66%
rename from libc/test/src/stdio/strftime_core/converter_test.cpp
rename to libc/test/src/time/strftime_core/converter_test.cpp
index 53ea3694227ef4..9be44ef39fce78 100644
--- a/libc/test/src/stdio/strftime_core/converter_test.cpp
+++ b/libc/test/src/time/strftime_core/converter_test.cpp
@@ -7,10 +7,10 @@
 //===----------------------------------------------------------------------===//
 
 #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"
+#include <time.h>
 
 class LlvmLibcStrftimeConverterTest : public LIBC_NAMESPACE::testing::Test {
 protected:
@@ -53,7 +53,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, PercentConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, WeekdayConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_wday = 1;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%a";
@@ -64,12 +64,12 @@ TEST_F(LlvmLibcStrftimeConverterTest, WeekdayConversion) {
 
   wb.buff[wb.buff_cur] = '\0';
 
-  ASSERT_STREQ(str, "Monday");
-  ASSERT_EQ(writer.get_chars_written(), 6);
+  ASSERT_STREQ(str, "Mon");
+  ASSERT_EQ(writer.get_chars_written(), 3);
 }
 TEST_F(LlvmLibcStrftimeConverterTest, AbbreviatedMonthNameConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_mon = 4; // May
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%b";
@@ -86,7 +86,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, AbbreviatedMonthNameConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, CenturyConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_year = 122; // Represents 2022
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%C";
@@ -103,7 +103,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, CenturyConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthZeroPaddedConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_mday = 7;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%d";
@@ -120,7 +120,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthZeroPaddedConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthSpacePaddedConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_mday = 7;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%e";
@@ -137,7 +137,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthSpacePaddedConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, FullMonthNameConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_mon = 4; // May
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%B";
@@ -154,7 +154,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, FullMonthNameConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, Hour12Conversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_hour = 14;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%I";
@@ -171,7 +171,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, Hour12Conversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, Hour24PaddedConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_hour = 9;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%H";
@@ -188,7 +188,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, Hour24PaddedConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, MinuteConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_min = 45;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%M";
@@ -205,7 +205,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, MinuteConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, AMPMConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_hour = 14; // 2 PM
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%p";
@@ -222,7 +222,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, AMPMConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, SecondsConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_sec = 30;
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%S";
@@ -239,7 +239,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, SecondsConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, FullYearConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_year = 122; // Represents 2022 (1900 + 122)
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%Y";
@@ -256,7 +256,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, FullYearConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, TwoDigitYearConversion) {
   LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
   time.tm_year = 122; // Represents 2022 (1900 + 122)
   simple_conv.has_conv = true;
   simple_conv.raw_string = "%y";
@@ -273,7 +273,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, TwoDigitYearConversion) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionEndOfYear) {
   LIBC_NAMESPACE::strftime_core::FormatSection iso_year;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
 
   // Set up the time for December 31, 2022 (a Saturday)
   time.tm_year = 122; // Represents 2022
@@ -297,7 +297,7 @@ TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionEndOfYear) {
 
 TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionStartOfYear) {
   LIBC_NAMESPACE::strftime_core::FormatSection iso_year;
-  LIBC_NAMESPACE::strftime_core::tm time;
+  tm time;
 
   // Set up the time for January 1, 2023 (a Sunday)
   time.tm_year = 123; // Represents 2023
@@ -317,4 +317,126 @@ TEST_F(LlvmLibcStrftimeConverterTest, IsoYearEdgeCaseConversionStartOfYear) {
   // 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
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, WeekNumberSundayFirstDayConversion) {
+  LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+  tm time;
+
+  // Set the time for a known Sunday (2023-05-07, which is a Sunday)
+  time.tm_year = 123; // Represents 2023
+  time.tm_mon = 4;    // May (0-based)
+  time.tm_mday = 7;   // 7th day of the month
+  time.tm_wday = 0;   // Sunday (0-based, 0 is Sunday)
+  time.tm_yday = 126; // 126th day of the year
+
+  simple_conv.has_conv = true;
+  simple_conv.raw_string = "%U"; // Week number (Sunday is first day of week)
+  simple_conv.conv_name = 'U';
+  simple_conv.time = &time;
+
+  LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+  wb.buff[wb.buff_cur] = '\0';
+
+  // The week number for May 7, 2023 (Sunday as first day) should be 19
+  ASSERT_STREQ(str, "19");
+  ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, WeekNumberMondayFirstDayConversion) {
+  LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+  tm time;
+
+  // Set the time for a known Monday (2023-05-08, which is a Monday)
+  time.tm_year = 123; // Represents 2023
+  time.tm_mon = 4;    // May (0-based)
+  time.tm_mday = 8;   // 8th day of the month
+  time.tm_wday = 1;   // Monday (0-based, 1 is Monday)
+  time.tm_yday = 127; // 127th day of the year
+
+  simple_conv.has_conv = true;
+  simple_conv.raw_string = "%W"; // Week number (Monday is first day of week)
+  simple_conv.conv_name = 'W';
+  simple_conv.time = &time;
+
+  LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+  wb.buff[wb.buff_cur] = '\0';
+
+  // The week number for May 8, 2023 (Monday as first day) should be 19
+  ASSERT_STREQ(str, "19");
+  ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, ISO8601WeekNumberConversion) {
+  LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+  tm time;
+
+  // Set the time for a known date (2023-05-10)
+  time.tm_year = 123; // Represents 2023
+  time.tm_mon = 4;    // May (0-based)
+  time.tm_mday = 10;  // 10th day of the month
+  time.tm_wday = 3;   // Wednesday (0-based, 3 is Wednesday)
+  time.tm_yday = 129; // 129th day of the year
+
+  simple_conv.has_conv = true;
+  simple_conv.raw_string = "%V"; // ISO 8601 week number
+  simple_conv.conv_name = 'V';
+  simple_conv.time = &time;
+
+  LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+  wb.buff[wb.buff_cur] = '\0';
+
+  // The ISO week number for May 10, 2023 should be 19
+  ASSERT_STREQ(str, "19");
+  ASSERT_EQ(writer.get_chars_written(), 2);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, DayOfYearConversion) {
+  LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+  tm time;
+
+  // Set the time for a known date (2023-02-25)
+  time.tm_year = 123; // Represents 2023
+  time.tm_mon = 1;    // February (0-based)
+  time.tm_mday = 25;  // 25th day of the month
+  time.tm_yday = 55;  // 55th day of the year
+
+  simple_conv.has_conv = true;
+  simple_conv.raw_string = "%j"; // Day of the year
+  simple_conv.conv_name = 'j';
+  simple_conv.time = &time;
+
+  LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+  wb.buff[wb.buff_cur] = '\0';
+
+  // The day of the year for February 25, 2023 should be 056 (padded)
+  ASSERT_STREQ(str, "056");
+  ASSERT_EQ(writer.get_chars_written(), 3);
+}
+
+TEST_F(LlvmLibcStrftimeConverterTest, ISO8601DateConversion) {
+  LIBC_NAMESPACE::strftime_core::FormatSection simple_conv;
+  tm time;
+
+  // Set the time for a known date (2023-05-10)
+  time.tm_year = 123; // Represents 2023
+  time.tm_mon = 4;    // May (0-based)
+  time.tm_mday = 10;  // 10th day of the month
+
+  simple_conv.has_conv = true;
+  simple_conv.raw_string = "%F"; // ISO 8601 date format
+  simple_conv.conv_name = 'F';
+  simple_conv.time = &time;
+
+  LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
+
+  wb.buff[wb.buff_cur] = '\0';
+
+  // The ISO date format for May 10, 2023 should be 2023-05-10
+  ASSERT_STREQ(str, "2023-05-10");
+  ASSERT_EQ(writer.get_chars_written(), 10);
+}
diff --git a/libc/test/src/time/strftime_core/parser_test.cpp b/libc/test/src/time/strftime_core/parser_test.cpp
new file mode 100644
index 00000000000000..bb18702fb57d0b
--- /dev/null
+++ b/libc/test/src/time/strftime_core/parser_test.cpp
@@ -0,0 +1,102 @@
+//===-- Unittests for the printf Converter --------------------------------===//
+//
+
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/parser.h"
+
+#include "test/UnitTest/Test.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using namespace strftime_core;
+
+class LlvmLibcStrftimeParserTest : public LIBC_NAMESPACE::testing::Test {
+protected:
+protected:
+  struct tm test_time;
+
+  void SetUp() override {
+    test_time = {};
+    test_time.tm_year = 123;
+    test_time.tm_mon = 4;
+    test_time.tm_mday = 10;
+    test_time.tm_hour = 14;
+    test_time.tm_min = 30;
+    test_time.tm_sec = 0;
+    test_time.tm_isdst = 0;
+  }
+};
+
+TEST_F(LlvmLibcStrftimeParserTest, ParseRawSection) {
+  const char *format = "Today is %Y-%m-%d";
+  Parser parser(format, test_time);
+
+  FormatSection section = parser.get_next_section();
+
+  ASSERT_FALSE(section.has_conv);
+  ASSERT_EQ(section.raw_string, cpp::string_view("Today is "));
+}
+
+TEST_F(LlvmLibcStrftimeParserTest, ParseConversionSection) {
+  const char *format = "%Y is the year";
+  Parser parser(format, test_time);
+
+  FormatSection section = parser.get_next_section();
+
+  ASSERT_TRUE(section.has_conv);
+  ASSERT_EQ(section.conv_name, 'Y');
+  ASSERT_EQ(section.time->tm_year, test_time.tm_year);
+  ASSERT_EQ(section.raw_string, cpp::string_view("%Y"));
+}
+
+TEST_F(LlvmLibcStrftimeParserTest, ParseConversionSectionWithModifiers) {
+  const char *format = "%Od";
+  Parser parser(format, test_time);
+
+  FormatSection section = parser.get_next_section();
+
+  ASSERT_TRUE(section.has_conv);
+  ASSERT_EQ(section.conv_name, 'd');
+  ASSERT_TRUE(section.isO);
+  ASSERT_FALSE(section.isE);
+}
+
+TEST_F(LlvmLibcStrftimeParserTest, HandleInvalidConversion) {
+  const char *format = "%k";
+  Parser parser(format, test_time);
+
+  FormatSection section = parser.get_next_section();
+
+  ASSERT_FALSE(section.has_conv);
+  ASSERT_EQ(section.raw_string, cpp::string_view("%k"));
+}
+
+TEST_F(LlvmLibcStrftimeParserTest, HandleMultipleSections) {
+  const char *format = "%Y-%m-%d %H:%M:%S";
+  Parser parser(format, test_time);
+
+  FormatSection section1 = parser.get_next_section();
+  ASSERT_TRUE(section1.has_conv);
+  ASSERT_EQ(section1.conv_name, 'Y');
+
+  FormatSection section2 = parser.get_next_section();
+  ASSERT_FALSE(section2.has_conv);
+  ASSERT_EQ(section2.raw_string, cpp::string_view("-"));
+
+  FormatSection section3 = parser.get_next_section();
+  ASSERT_TRUE(section3.has_conv);
+  ASSERT_EQ(section3.conv_name, 'm');
+
+  FormatSection section4 = parser.get_next_section();
+  ASSERT_FALSE(section4.has_conv);
+  ASSERT_EQ(section4.raw_string, cpp::string_view("-"));
+
+  FormatSection section5 = parser.get_next_section();
+  ASSERT_TRUE(section5.has_conv);
+  ASSERT_EQ(section5.conv_name, 'd');
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/time/strftime_test.cpp b/libc/test/src/time/strftime_test.cpp
new file mode 100644
index 00000000000000..6565c6e737307a
--- /dev/null
+++ b/libc/test/src/time/strftime_test.cpp
@@ -0,0 +1,98 @@
+
+//===-- Unittests for ctime -----------------------------------------------===//
+//
+// 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/errno/libc_errno.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/strftime_main.h"
+#include "test/UnitTest/Test.h"
+#include <time.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+using namespace strftime_core;
+size_t call_strftime(char *__restrict buffer, size_t buffsz,
+                     const char *__restrict format, const struct tm *timeptr) {
+
+  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(&wb);
+  strftime_core::strftime_main(&writer, format, timeptr);
+  return writer.get_chars_written();
+}
+
+TEST(LlvmLibcStrftimeTest, FormatsYearMonthDayCorrectly) {
+  struct tm time;
+  time.tm_year = 122; // Year since 1900, so 2022
+  time.tm_mon = 9;    // October (0-indexed)
+  time.tm_mday = 15;  // 15th day
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%Y-%m-%d", &time);
+  EXPECT_STREQ(buffer, "2022-10-15");
+}
+
+TEST(LlvmLibcStrftimeTest, FormatsTimeCorrectly) {
+  struct tm time;
+  time.tm_hour = 14; // 2:00 PM
+  time.tm_min = 30;  // 30 minutes
+  time.tm_sec = 45;  // 45 seconds
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%H:%M:%S", &time);
+  EXPECT_STREQ(buffer, "14:30:45");
+}
+
+TEST(LlvmLibcStrftimeTest, FormatsAmPmCorrectly) {
+  struct tm time;
+  time.tm_hour = 13; // 1:00 PM
+  time.tm_min = 0;
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%I:%M %p", &time);
+  EXPECT_STREQ(buffer, "01:00 PM");
+}
+
+TEST(LlvmLibcStrftimeTest, HandlesLeapYear) {
+  struct tm time;
+  time.tm_year = 120; // Year 2020
+  time.tm_mon = 1;    // February
+  time.tm_mday = 29;  // 29th day
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%Y-%m-%d", &time);
+  EXPECT_STREQ(buffer, "2020-02-29");
+}
+
+TEST(LlvmLibcStrftimeTest, HandlesEndOfYear) {
+  struct tm time;
+  time.tm_year = 121; // Year 2021
+  time.tm_mon = 11;   // December
+  time.tm_mday = 31;  // 31st day
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%Y-%m-%d", &time);
+  EXPECT_STREQ(buffer, "2021-12-31");
+}
+
+TEST(LlvmLibcStrftimeTest, FormatsTimezoneCorrectly) {
+  struct tm time;
+  time.tm_year = 122; // Year 2022
+  time.tm_mon = 9;    // October
+  time.tm_mday = 15;
+  time.tm_hour = 12;
+  time.tm_min = 0;
+  time.tm_sec = 0;
+  time.tm_isdst = -1; // Use system's daylight saving time information
+
+  char buffer[100];
+  call_strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %Z", &time);
+  EXPECT_STRNE(buffer, "");
+}
+
+} // namespace LIBC_NAMESPACE_DECL

>From e036872caf486f4c80c159f60c862b80606ff6a1 Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Sun, 20 Oct 2024 02:02:27 +0800
Subject: [PATCH 4/4] [libc] Refactor converter.cpp

---
 libc/src/time/strftime_core/CMakeLists.txt    |   5 +
 .../time/strftime_core/composite_converter.h  | 124 ++++++
 libc/src/time/strftime_core/converter.cpp     | 382 ++----------------
 libc/src/time/strftime_core/core_structs.h    |  17 +-
 libc/src/time/strftime_core/num_converter.h   | 208 ++++++++++
 libc/src/time/strftime_core/str_converter.h   |  55 +++
 .../time/strftime_core/time_internal_def.h    |  14 +
 .../src/time/strftime_core/converter_test.cpp |  39 ++
 8 files changed, 489 insertions(+), 355 deletions(-)
 create mode 100644 libc/src/time/strftime_core/composite_converter.h
 create mode 100644 libc/src/time/strftime_core/num_converter.h
 create mode 100644 libc/src/time/strftime_core/str_converter.h

diff --git a/libc/src/time/strftime_core/CMakeLists.txt b/libc/src/time/strftime_core/CMakeLists.txt
index 78f37ea3ef7078..79e2eafc6a98de 100644
--- a/libc/src/time/strftime_core/CMakeLists.txt
+++ b/libc/src/time/strftime_core/CMakeLists.txt
@@ -24,9 +24,14 @@ add_object_library(
     converter.cpp
   HDRS
     converter.h
+    num_converter.h
+    str_converter.h
+    composite_converter.h
   DEPENDS
     .core_structs
+    libc.src.__support.arg_list
     libc.src.stdio.printf_core.writer
+    libc.src.stdio.printf_core.printf_main
     libc.src.__support.big_int
     libc.src.__support.CPP.string_view
     libc.src.__support.float_to_string
diff --git a/libc/src/time/strftime_core/composite_converter.h b/libc/src/time/strftime_core/composite_converter.h
new file mode 100644
index 00000000000000..cf317988de0757
--- /dev/null
+++ b/libc/src/time/strftime_core/composite_converter.h
@@ -0,0 +1,124 @@
+//===-- 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_COMPOSITE_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_COMPOSITE_CONVERTER_H
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/integer_to_string.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/time_internal_def.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+namespace details {
+int snprintf_impl(char *__restrict buffer, size_t buffsz,
+                  const char *__restrict format, ...) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist);
+  va_end(vlist);
+  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(&wb);
+
+  int ret_val = printf_core::printf_main(&writer, format, args);
+  if (buffsz > 0)
+    wb.buff[wb.buff_cur] = '\0';
+  return ret_val;
+}
+} // namespace details
+
+int write_composite(printf_core::Writer *writer, const FormatSection &to_conv) {
+  char buffer[100];
+  auto &time = *to_conv.time;
+
+  switch (to_conv.conv_name) {
+  // Full date and time representation (e.g., equivalent to %a %b %e %T %Y)
+  case 'c': {
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%s %s %02d %02d:%02d:%02d %d",
+        safe_abbreviated_day_name(time.tm_wday),
+        safe_abbreviated_month_name(time.tm_mon), time.tm_mday, time.tm_hour,
+        time.tm_min, time.tm_sec, time.tm_year + 1900));
+    break;
+  }
+
+  // Zero-padded day of the month (equivalent to %m/%d/%y)
+  case 'D': {
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%02d/%02d/%02d", time.tm_mon + 1, time.tm_mday,
+        (time.tm_year + 1900) % 100));
+    break;
+  }
+
+  // ISO 8601 date representation in YYYY-MM-DD (equivalent to %Y-%m-%d)
+  case 'F': {
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%04d-%02d-%02d", time.tm_year + 1900,
+        time.tm_mon + 1, time.tm_mday));
+    break;
+  }
+
+  // 12-hour clock time with seconds and AM/PM (equivalent to %I:%M:%S %p)
+  case 'r': {
+    int hour12 = time.tm_hour % 12;
+    if (hour12 == 0)
+      hour12 = 12;
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%02d:%02d:%02d %s", hour12, time.tm_min,
+        time.tm_sec,
+        to_conv.time->tm_hour >= 12 ? default_PM_str : default_AM_str));
+    break;
+  }
+
+  // 24-hour time without seconds (equivalent to %H:%M)
+  case 'R': {
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%02d:%02d", time.tm_hour, time.tm_min));
+    break;
+  }
+
+  // Time with seconds (equivalent to %H:%M:%S)
+  case 'T': {
+    RET_IF_RESULT_NEGATIVE(
+        details::snprintf_impl(buffer, sizeof(buffer), "%02d:%02d:%02d",
+                               time.tm_hour, time.tm_min, time.tm_sec));
+    break;
+  }
+
+  // Locale's date representation (often equivalent to %m/%d/%y)
+  case 'x': {
+    RET_IF_RESULT_NEGATIVE(details::snprintf_impl(
+        buffer, sizeof(buffer), "%02d/%02d/%02d", time.tm_mon + 1, time.tm_mday,
+        (time.tm_year + 1900) % 100));
+    break;
+  }
+
+  // Locale's time representation (equivalent to %H:%M:%S)
+  case 'X': {
+    RET_IF_RESULT_NEGATIVE(
+        details::snprintf_impl(buffer, sizeof(buffer), "%02d:%02d:%02d",
+                               time.tm_hour, time.tm_min, time.tm_sec));
+    break;
+  }
+
+  default:
+    return writer->write(to_conv.raw_string);
+  }
+  return writer->write(buffer);
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif
diff --git a/libc/src/time/strftime_core/converter.cpp b/libc/src/time/strftime_core/converter.cpp
index d7b3e57f19ce8a..a9ab38ef0b2a60 100644
--- a/libc/src/time/strftime_core/converter.cpp
+++ b/libc/src/time/strftime_core/converter.cpp
@@ -9,370 +9,62 @@
 #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 "composite_converter.h"
+#include "num_converter.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/integer_to_string.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/writer.h"
 #include "src/time/strftime_core/core_structs.h"
 #include "src/time/strftime_core/time_internal_def.h"
+#include "str_converter.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
-#define RET_IF_RESULT_NEGATIVE(func)                                           \
-  {                                                                            \
-    int result = (func);                                                       \
-    if (result < 0)                                                            \
-      return result;                                                           \
-  }
-
-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 <typename T> int count_digits(T num) {
-  if (num == 0)
-    return 1;
-  int digits = 0;
-  while (num > 0) {
-    num /= 10;
-    digits++;
-  }
-  return digits;
-}
-
-template <int width, char padding>
-LIBC_INLINE int write_num_with_padding(uintmax_t num,
-                                       printf_core::Writer *writer) {
-  cpp::array<char, width> buf;
-  int digits = count_digits(num);
-  int padding_needed = width - digits;
-
-  for (int _ = 0; _ < padding_needed; _++) {
-    RET_IF_RESULT_NEGATIVE(writer->write(padding));
-  }
-
-  return writer->write(*num_to_strview(num, buf));
-}
-
-} // 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));
-}
-
-// Conversion functions
-
-LIBC_INLINE int convert_weekday(printf_core::Writer *writer,
-                                const FormatSection &to_conv) {
-  return writer->write(safe_day_name(to_conv.time->tm_wday));
-}
-
-LIBC_INLINE int convert_abbreviated_weekday(printf_core::Writer *writer,
-                                            const FormatSection &to_conv) {
-  return writer->write(safe_abbreviated_day_name(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(safe_month_name(to_conv.time->tm_mon));
-}
-
-LIBC_INLINE int convert_abbreviated_month(printf_core::Writer *writer,
-                                          const FormatSection &to_conv) {
-  return writer->write(safe_abbreviated_month_name(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_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;
-  if (hour == 0)
-    hour = 12;
-  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);
-}
-
-LIBC_INLINE int convert_MM_DD_YY(printf_core::Writer *writer,
-                                 const FormatSection &to_conv) {
-  RET_IF_RESULT_NEGATIVE(convert_zero_padded_month(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write("/"));
-  RET_IF_RESULT_NEGATIVE(convert_zero_padded_day_of_month(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write("/"));
-  return convert_two_digit_year(writer, to_conv);
-}
-
-LIBC_INLINE int convert_YYYY_MM_DD(printf_core::Writer *writer,
-                                   const FormatSection &to_conv) {
-  RET_IF_RESULT_NEGATIVE(convert_full_year(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write("-"));
-  RET_IF_RESULT_NEGATIVE(convert_zero_padded_month(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write("-"));
-  return convert_zero_padded_day_of_month(writer, to_conv);
-}
-
-LIBC_INLINE int convert_hour_minute(printf_core::Writer *writer,
-                                    const FormatSection &to_conv) {
-  RET_IF_RESULT_NEGATIVE(convert_hour_24(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(":"));
-  return convert_minute(writer, to_conv);
-}
-
-LIBC_INLINE int convert_date_time(printf_core::Writer *writer,
-                                  const FormatSection &to_conv) {
-  RET_IF_RESULT_NEGATIVE(convert_abbreviated_weekday(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(" "));
-  RET_IF_RESULT_NEGATIVE(convert_abbreviated_month(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(" "));
-  RET_IF_RESULT_NEGATIVE(convert_space_padded_day_of_month(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(" "));
-  RET_IF_RESULT_NEGATIVE(convert_full_year(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(" "));
-  RET_IF_RESULT_NEGATIVE(convert_hour_24(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(":"));
-  RET_IF_RESULT_NEGATIVE(convert_minute(writer, to_conv));
-  RET_IF_RESULT_NEGATIVE(writer->write(":"));
-  return convert_second(writer, to_conv);
-}
-
-LIBC_INLINE int convert_time_zone(printf_core::Writer *writer) {
-  return writer->write("UTC");
-}
-
-LIBC_INLINE int convert_time_zone_offset(printf_core::Writer *writer) {
-  return writer->write("+0000");
-}
-
-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_iso_day(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_with_padding<2, '0'>(days / 7 + 1, 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) {
   case '%':
     return writer->write("%");
-  // day of the week
-  case 'A':
-    return convert_weekday(writer, to_conv);
-  case 'a':
-    return convert_abbreviated_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':
-    return convert_iso_day(writer, to_conv);
-  // 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':
-    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);
-    // Date and Time
+  case 'C': // Century (C)
+  case 'Y': // Full year (Y)
+  case 'y': // Two-digit year (y)
+  case 'j': // Day of the year (j)
+  case 'm': // Month (m)
+  case 'd': // Day of the month (d)
+  case 'e': // Day of the month (e)
+  case 'H': // 24-hour format (H)
+  case 'I': // 12-hour format (I)
+  case 'M': // Minute (M)
+  case 'S': // Second (S)
+  case 'U': // Week number starting on Sunday (U)
+  case 'W': // Week number starting on Monday (W)
+  case 'V': // ISO week number (V)
+  case 'G': // ISO year (G)
+  case 'w': // Decimal weekday (w)
+  case 'u': // ISO weekday (u)
+    write_num(writer, to_conv);
+    break;
+  case 'a': // Abbreviated weekday name (a)
+  case 'A': // Full weekday name (A)
+  case 'b': // Abbreviated month name (b)
+  case 'B': // Full month name (B)
+  case 'p': // AM/PM designation (p)
+  case 'z': // Timezone offset (z)
+  case 'Z': // Timezone name (Z)
+    write_str(writer, to_conv);
+    break;
   case 'c':
-    return convert_date_time(writer, to_conv);
-
-  // Timezone
-  case 'Z':
-    return convert_time_zone(writer);
-  case 'z':
-    return convert_time_zone_offset(writer);
-
-  // Custom formats: MM/DD/YY, YYYY-MM-DD
-  case 'D':
-    return convert_MM_DD_YY(writer, to_conv); // Equivalent to %m/%d/%y
   case 'F':
-    return convert_YYYY_MM_DD(writer, to_conv); // Equivalent to %Y-%m-%d
-
-  // 24-hour time without seconds (HH:MM)
+  case 'r':
   case 'R':
-    return convert_hour_minute(writer, to_conv); // Equivalent to %H:%M
+  case 'T':
+  case 'x':
+  case 'X':
+    write_composite(writer, to_conv);
+    break;
   default:
     writer->write(to_conv.raw_string);
   }
diff --git a/libc/src/time/strftime_core/core_structs.h b/libc/src/time/strftime_core/core_structs.h
index d6cd91884b8a67..5c01638f132da4 100644
--- a/libc/src/time/strftime_core/core_structs.h
+++ b/libc/src/time/strftime_core/core_structs.h
@@ -26,16 +26,13 @@ struct FormatSection {
   char padding;
 };
 
-// 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;
+#define RET_IF_RESULT_NEGATIVE(func)                                           \
+  {                                                                            \
+    int result = (func);                                                       \
+    if (result < 0)                                                            \
+      return result;                                                           \
+  }
+
 } // namespace strftime_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/time/strftime_core/num_converter.h b/libc/src/time/strftime_core/num_converter.h
new file mode 100644
index 00000000000000..baad78a281b657
--- /dev/null
+++ b/libc/src/time/strftime_core/num_converter.h
@@ -0,0 +1,208 @@
+//===-- 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_NUM_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_NUM_CONVERTER_H
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/integer_to_string.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/time_internal_def.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 <typename T> int count_digits(T num) {
+  if (num == 0)
+    return 1;
+  int digits = 0;
+  while (num > 0) {
+    num /= 10;
+    digits++;
+  }
+  return digits;
+}
+
+LIBC_INLINE int write_num_with_padding(int width, char padding, uintmax_t num,
+                                       printf_core::Writer *writer) {
+  cpp::array<char, IntegerToString<uintmax_t>::buffer_size()> buf;
+  int digits = count_digits(num);
+  int padding_needed = width - digits;
+
+  for (int _ = 0; _ < padding_needed; _++) {
+    RET_IF_RESULT_NEGATIVE(writer->write(padding));
+  }
+
+  return writer->write(*num_to_strview(num, buf));
+}
+
+} // namespace details
+
+namespace iso {
+
+/* 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));
+}
+
+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);
+}
+
+enum class IsoData {
+  GET_DATE,
+  GET_YEAR,
+};
+
+template <IsoData get_date_or_year>
+LIBC_INLINE int convert_iso(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;
+    }
+  }
+
+  if constexpr (get_date_or_year == IsoData::GET_YEAR) {
+    return year;
+  } else {
+    return days / 7 + 1;
+  }
+}
+} // namespace iso
+
+int write_num(printf_core::Writer *writer, const FormatSection &to_conv) {
+  int num = 0;
+  auto &time = *to_conv.time;
+
+  // Handle numeric conversions based on the format specifier (conv_name)
+  switch (to_conv.conv_name) {
+  // Century (C) - the first two digits of the year
+  case 'C':
+    num = (time.tm_year + 1900) / 100;
+    break;
+
+  // Full year (Y) - the full four-digit year
+  case 'Y':
+    num = time.tm_year + 1900;
+    break;
+
+  // Two-digit year (y) - the last two digits of the year
+  case 'y':
+    num = (time.tm_year + 1900) % 100;
+    break;
+
+  // Day of the year (j) - the day number within the year (1-366)
+  case 'j':
+    num = time.tm_yday + 1;
+    break;
+
+  // Zero-padded month (m) - month as a zero-padded number (01-12)
+  case 'm':
+    num = time.tm_mon + 1;
+    break;
+
+  // Day of the month (d) - zero-padded day of the month (01-31)
+  case 'd':
+  case 'e':
+    num = time.tm_mday;
+    break;
+
+  // 24-hour format (H) - zero-padded hour (00-23)
+  case 'H':
+    num = time.tm_hour;
+    break;
+
+  // 12-hour format (I) - zero-padded hour (01-12)
+  case 'I':
+    num = time.tm_hour % 12;
+    if (num == 0)
+      num = 12; // Convert 0 to 12 for 12-hour format
+    break;
+
+  // Minute (M) - zero-padded minute (00-59)
+  case 'M':
+    num = time.tm_min;
+    break;
+
+  // Second (S) - zero-padded second (00-59)
+  case 'S':
+    num = time.tm_sec;
+    break;
+
+  // Week number starting on Sunday (U) - week number of the year (Sunday as the
+  // start of the week)
+  case 'U': {
+    int wday = time.tm_wday;
+    num = (time.tm_yday - wday + 7) / 7;
+    break;
+  }
+
+  // Week number starting on Monday (W) - week number of the year (Monday as the
+  // start of the week)
+  case 'W': {
+    int wday = (time.tm_wday + 6) % 7; // Adjust to Monday as the first day
+    num = (time.tm_yday - wday + 7) / 7;
+    break;
+  }
+
+  // ISO week day (V) - week number following ISO 8601
+  case 'V':
+    num = iso::convert_iso<iso::IsoData::GET_DATE>(to_conv);
+    break;
+
+  case 'G':
+    num = iso::convert_iso<iso::IsoData::GET_YEAR>(to_conv);
+    break;
+  // Decimal weekday (w) - day of the week (Sunday = 0, Monday = 1, etc.)
+  case 'w':
+    num = time.tm_wday;
+    break;
+
+  // ISO weekday (u) - day of the week (Monday = 1, Sunday = 7)
+  case 'u':
+    num = (time.tm_wday == 0) ? 7 : time.tm_wday;
+    break;
+
+  default:
+    return writer->write(
+        to_conv.raw_string); // Default: write raw string if no match
+  }
+
+  return details::write_num_with_padding(to_conv.min_width, to_conv.padding,
+                                         num, writer);
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif
diff --git a/libc/src/time/strftime_core/str_converter.h b/libc/src/time/strftime_core/str_converter.h
new file mode 100644
index 00000000000000..1221650b3f2aba
--- /dev/null
+++ b/libc/src/time/strftime_core/str_converter.h
@@ -0,0 +1,55 @@
+//===-- 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_STR_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STR_CONVERTER_H
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/integer_to_string.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/time_internal_def.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int write_str(printf_core::Writer *writer, const FormatSection &to_conv) {
+  cpp::string_view str;
+  auto &time = *to_conv.time;
+  switch (to_conv.conv_name) {
+  case 'a':
+    str = safe_abbreviated_day_name(time.tm_wday);
+    break;
+  case 'A':
+    str = safe_day_name(time.tm_wday);
+    break;
+  case 'b':
+    str = safe_abbreviated_month_name(time.tm_mon);
+    break;
+  case 'B':
+    str = safe_month_name(time.tm_mon);
+    break;
+  case 'p':
+    str = to_conv.time->tm_hour >= 12 ? default_PM_str : default_AM_str;
+    break;
+  case 'z':
+    str = default_timezone_offset;
+    break;
+  case 'Z':
+    str = default_timezone_name;
+    break;
+  default:
+    return writer->write(to_conv.raw_string);
+  }
+  return writer->write(str);
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif
diff --git a/libc/src/time/strftime_core/time_internal_def.h b/libc/src/time/strftime_core/time_internal_def.h
index 198897ef9186b9..66fd02f9229782 100644
--- a/libc/src/time/strftime_core/time_internal_def.h
+++ b/libc/src/time/strftime_core/time_internal_def.h
@@ -6,6 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 
+#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_TIME_DEF_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_TIME_DEF_H
+
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/string_view.h"
 
@@ -59,5 +62,16 @@ LIBC_INLINE cpp::string_view safe_abbreviated_month_name(int month) {
                                    : abbreviated_month_names[month];
 }
 
+static constexpr cpp::string_view default_timezone_name = "UTC";
+
+// TODO
+static constexpr cpp::string_view default_timezone_offset = "+0000";
+
+static constexpr cpp::string_view default_PM_str = "PM";
+
+static constexpr cpp::string_view default_AM_str = "AM";
+
 } // namespace strftime_core
 } // namespace LIBC_NAMESPACE_DECL
+
+#endif
diff --git a/libc/test/src/time/strftime_core/converter_test.cpp b/libc/test/src/time/strftime_core/converter_test.cpp
index 9be44ef39fce78..7c4820bbd208b9 100644
--- a/libc/test/src/time/strftime_core/converter_test.cpp
+++ b/libc/test/src/time/strftime_core/converter_test.cpp
@@ -9,9 +9,12 @@
 #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 "test/UnitTest/Test.h"
 #include <time.h>
 
+using namespace LIBC_NAMESPACE;
+
 class LlvmLibcStrftimeConverterTest : public LIBC_NAMESPACE::testing::Test {
 protected:
   // void SetUp() override {}
@@ -59,6 +62,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, WeekdayConversion) {
   simple_conv.raw_string = "%a";
   simple_conv.conv_name = 'a';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -75,6 +80,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, AbbreviatedMonthNameConversion) {
   simple_conv.raw_string = "%b";
   simple_conv.conv_name = 'b';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -92,6 +99,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, CenturyConversion) {
   simple_conv.raw_string = "%C";
   simple_conv.conv_name = 'C';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -109,6 +118,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthZeroPaddedConversion) {
   simple_conv.raw_string = "%d";
   simple_conv.conv_name = 'd';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -126,6 +137,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, DayOfMonthSpacePaddedConversion) {
   simple_conv.raw_string = "%e";
   simple_conv.conv_name = 'e';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -143,6 +156,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, FullMonthNameConversion) {
   simple_conv.raw_string = "%B";
   simple_conv.conv_name = 'B';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -160,6 +175,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, Hour12Conversion) {
   simple_conv.raw_string = "%I";
   simple_conv.conv_name = 'I';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -177,6 +194,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, Hour24PaddedConversion) {
   simple_conv.raw_string = "%H";
   simple_conv.conv_name = 'H';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -194,6 +213,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, MinuteConversion) {
   simple_conv.raw_string = "%M";
   simple_conv.conv_name = 'M';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -211,6 +232,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, AMPMConversion) {
   simple_conv.raw_string = "%p";
   simple_conv.conv_name = 'p';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -228,6 +251,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, SecondsConversion) {
   simple_conv.raw_string = "%S";
   simple_conv.conv_name = 'S';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -245,6 +270,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, FullYearConversion) {
   simple_conv.raw_string = "%Y";
   simple_conv.conv_name = 'Y';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -262,6 +289,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, TwoDigitYearConversion) {
   simple_conv.raw_string = "%y";
   simple_conv.conv_name = 'y';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -334,6 +363,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, WeekNumberSundayFirstDayConversion) {
   simple_conv.raw_string = "%U"; // Week number (Sunday is first day of week)
   simple_conv.conv_name = 'U';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -359,6 +390,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, WeekNumberMondayFirstDayConversion) {
   simple_conv.raw_string = "%W"; // Week number (Monday is first day of week)
   simple_conv.conv_name = 'W';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -384,6 +417,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, ISO8601WeekNumberConversion) {
   simple_conv.raw_string = "%V"; // ISO 8601 week number
   simple_conv.conv_name = 'V';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -408,6 +443,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, DayOfYearConversion) {
   simple_conv.raw_string = "%j"; // Day of the year
   simple_conv.conv_name = 'j';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 
@@ -431,6 +468,8 @@ TEST_F(LlvmLibcStrftimeConverterTest, ISO8601DateConversion) {
   simple_conv.raw_string = "%F"; // ISO 8601 date format
   simple_conv.conv_name = 'F';
   simple_conv.time = &time;
+  simple_conv.min_width = strftime_core::min_width(simple_conv.conv_name);
+  simple_conv.padding = strftime_core::get_padding(simple_conv.conv_name);
 
   LIBC_NAMESPACE::strftime_core::convert(&writer, simple_conv);
 



More information about the libc-commits mailing list