Project import generated by Copybara.

GitOrigin-RevId: b10ddfafcacf9d8f9cfa7e23f65730be60261554
Change-Id: I58dc9c2e8f75209fd971deadcfe45b3dfb258716
diff --git a/include/__llvm-libc-common.h b/include/__llvm-libc-common.h
index a0fa506..c6fd33a 100644
--- a/include/__llvm-libc-common.h
+++ b/include/__llvm-libc-common.h
@@ -6,8 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef LLVM_LIBC_COMMON_H
-#define LLVM_LIBC_COMMON_H
+#ifndef _LLVM_LIBC_COMMON_H
+#define _LLVM_LIBC_COMMON_H
 
 #define __LLVM_LIBC__ 1
 
@@ -47,6 +47,11 @@
 #define __NOEXCEPT throw()
 #endif
 
+// This macro serves as a generic cast implementation for use in both C and C++,
+// similar to `__BIONIC_CAST` in Android.
+#undef __LLVM_LIBC_CAST
+#define __LLVM_LIBC_CAST(cast, type, value) (cast<type>(value))
+
 #else // not __cplusplus
 
 #undef __BEGIN_C_DECLS
@@ -85,6 +90,9 @@
 #undef _Returns_twice
 #define _Returns_twice __attribute__((returns_twice))
 
+#undef __LLVM_LIBC_CAST
+#define __LLVM_LIBC_CAST(cast, type, value) ((type)(value))
+
 #endif // __cplusplus
 
-#endif // LLVM_LIBC_COMMON_H
+#endif // _LLVM_LIBC_COMMON_H
diff --git a/include/llvm-libc-macros/endian-macros.h b/include/llvm-libc-macros/endian-macros.h
index e1e105d..52d95dc 100644
--- a/include/llvm-libc-macros/endian-macros.h
+++ b/include/llvm-libc-macros/endian-macros.h
@@ -20,27 +20,27 @@
 #define htobe16(x) __builtin_bswap16((x))
 #define htobe32(x) __builtin_bswap32((x))
 #define htobe64(x) __builtin_bswap64((x))
-#define htole16(x) ((uint16_t)(x))
-#define htole32(x) ((uint32_t)(x))
-#define htole64(x) ((uint64_t)(x))
+#define htole16(x) __LLVM_LIBC_CAST(static_cast, uint16_t, x)
+#define htole32(x) __LLVM_LIBC_CAST(static_cast, uint32_t, x)
+#define htole64(x) __LLVM_LIBC_CAST(static_cast, uint64_t, x)
 #define be16toh(x) __builtin_bswap16((x))
 #define be32toh(x) __builtin_bswap32((x))
 #define be64toh(x) __builtin_bswap64((x))
-#define le16toh(x) ((uint16_t)(x))
-#define le32toh(x) ((uint32_t)(x))
-#define le64toh(x) ((uint64_t)(x))
+#define le16toh(x) __LLVM_LIBC_CAST(static_cast, uint16_t, x)
+#define le32toh(x) __LLVM_LIBC_CAST(static_cast, uint32_t, x)
+#define le64toh(x) __LLVM_LIBC_CAST(static_cast, uint64_t, x)
 
 #else
 
-#define htobe16(x) ((uint16_t)(x))
-#define htobe32(x) ((uint32_t)(x))
-#define htobe64(x) ((uint64_t)(x))
+#define htobe16(x) __LLVM_LIBC_CAST(static_cast, uint16_t, x)
+#define htobe32(x) __LLVM_LIBC_CAST(static_cast, uint32_t, x)
+#define htobe64(x) __LLVM_LIBC_CAST(static_cast, uint64_t, x)
 #define htole16(x) __builtin_bswap16((x))
 #define htole32(x) __builtin_bswap32((x))
 #define htole64(x) __builtin_bswap64((x))
-#define be16toh(x) ((uint16_t)(x))
-#define be32toh(x) ((uint32_t)(x))
-#define be64toh(x) ((uint64_t)(x))
+#define be16toh(x) __LLVM_LIBC_CAST(static_cast, uint16_t, x)
+#define be32toh(x) __LLVM_LIBC_CAST(static_cast, uint32_t, x)
+#define be64toh(x) __LLVM_LIBC_CAST(static_cast, uint64_t, x)
 #define le16toh(x) __builtin_bswap16((x))
 #define le32toh(x) __builtin_bswap32((x))
 #define le64toh(x) __builtin_bswap64((x))
diff --git a/src/__support/GPU/utils.h b/src/__support/GPU/utils.h
index 323c003..0fd3a64 100644
--- a/src/__support/GPU/utils.h
+++ b/src/__support/GPU/utils.h
@@ -92,6 +92,14 @@
   return __gpu_shuffle_idx_u32(lane_mask, idx, x, width);
 }
 
+LIBC_INLINE uint64_t match_any(uint64_t lane_mask, uint32_t x) {
+  return __gpu_match_any_u32(lane_mask, x);
+}
+
+LIBC_INLINE uint64_t match_all(uint64_t lane_mask, uint32_t x) {
+  return __gpu_match_all_u32(lane_mask, x);
+}
+
 [[noreturn]] LIBC_INLINE void end_program() { __gpu_exit(); }
 
 LIBC_INLINE bool is_first_lane(uint64_t lane_mask) {
diff --git a/src/math/generic/sqrtf128.cpp b/src/math/generic/sqrtf128.cpp
index c844d3a..3aa7db8 100644
--- a/src/math/generic/sqrtf128.cpp
+++ b/src/math/generic/sqrtf128.cpp
@@ -383,25 +383,26 @@
       // 1 so just need to add shifted m and 1.
       Int128 t1 = t0;
       Int128 sgn = t0 >> 127; // sign of the difference
-      t1 -= (m << 1) ^ sgn;
-      t1 += 1 + sgn;
+      Int128 m_xor_sgn = static_cast<Int128>(m << 1) ^ sgn;
+      t1 -= m_xor_sgn;
+      t1 += Int128(1) + sgn;
 
       Int128 sgn1 = t1 >> 127;
       if (LIBC_UNLIKELY(sgn == sgn1)) {
         t0 = t1;
         v -= sgn << 15;
-        t1 -= (m << 1) ^ sgn;
-        t1 += 1 + sgn;
+        t1 -= m_xor_sgn;
+        t1 += Int128(1) + sgn;
       }
 
       if (t1 == 0) {
         // 1 ulp offset brings again an exact root
-        v = (m - (2 * sgn + 1)) << 15;
+        v = (m - static_cast<UInt128>((sgn << 1) + 1)) << 15;
       } else {
         t1 += t0;
         Int128 side = t1 >> 127; // select what is closer m or m+-1
         v &= ~UInt128(0) << 15;  // wipe the fractional bits
-        v -= ((sgn & side) | (~sgn & 1)) << (15 + side);
+        v -= ((sgn & side) | (~sgn & 1)) << (15 + static_cast<int>(side));
         v |= 1; // add sticky bit since we cannot have an exact mid-point
                 // situation
       }
diff --git a/src/stdio/scanf_core/reader.cpp b/src/stdio/scanf_core/reader.cpp
deleted file mode 100644
index ec1f5c0..0000000
--- a/src/stdio/scanf_core/reader.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-//===-- Reader definition for scanf -----------------------------*- 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/stdio/scanf_core/reader.h"
-#include "src/__support/macros/config.h"
-#include <stddef.h>
-
-namespace LIBC_NAMESPACE_DECL {
-namespace scanf_core {
-
-void Reader::ungetc(char c) {
-  --cur_chars_read;
-  if (rb != nullptr && rb->buff_cur > 0) {
-    // While technically c should be written back to the buffer, in scanf we
-    // always write the character that was already there. Additionally, the
-    // buffer is most likely to contain a string that isn't part of a file,
-    // which may not be writable.
-    --(rb->buff_cur);
-    return;
-  }
-  stream_ungetc(static_cast<int>(c), input_stream);
-}
-} // namespace scanf_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/src/stdio/scanf_core/reader.h b/src/stdio/scanf_core/reader.h
index f984fd9..1f8ec96 100644
--- a/src/stdio/scanf_core/reader.h
+++ b/src/stdio/scanf_core/reader.h
@@ -9,15 +9,73 @@
 #ifndef LLVM_LIBC_SRC_STDIO_SCANF_CORE_READER_H
 #define LLVM_LIBC_SRC_STDIO_SCANF_CORE_READER_H
 
+#include "hdr/types/FILE.h"
+
+#ifndef LIBC_COPT_STDIO_USE_SYSTEM_FILE
+#include "src/__support/File/file.h"
+#endif
+
+#if defined(LIBC_TARGET_ARCH_IS_GPU)
+#include "src/stdio/getc.h"
+#include "src/stdio/ungetc.h"
+#endif
+
 #include "src/__support/macros/attributes.h" // For LIBC_INLINE
 #include "src/__support/macros/config.h"
+
 #include <stddef.h>
 
 namespace LIBC_NAMESPACE_DECL {
 namespace scanf_core {
+// We use the name "reader_internal" over "internal" because
+// "internal" causes name lookups in files that include the current header to be
+// ambigious i.e. `internal::foo` in those files, will try to lookup in
+// `LIBC_NAMESPACE::scanf_core::internal` over `LIBC_NAMESPACE::internal` for
+// e.g., `internal::ArgList` in `libc/src/stdio/scanf_core/scanf_main.h`
+namespace reader_internal {
 
-using StreamGetc = int (*)(void *);
-using StreamUngetc = void (*)(int, void *);
+#if defined(LIBC_TARGET_ARCH_IS_GPU)
+// The GPU build provides FILE access through the host operating system's
+// library. So here we simply use the public entrypoints like in the SYSTEM_FILE
+// interface. Entrypoints should normally not call others, this is an exception.
+// FIXME: We do not acquire any locks here, so this is not thread safe.
+LIBC_INLINE int getc(void *f) {
+  return LIBC_NAMESPACE::getc(reinterpret_cast<::FILE *>(f));
+}
+
+LIBC_INLINE void ungetc(int c, void *f) {
+  LIBC_NAMESPACE::ungetc(c, reinterpret_cast<::FILE *>(f));
+}
+
+#elif !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE)
+
+LIBC_INLINE int getc(void *f) {
+  unsigned char c;
+  auto result =
+      reinterpret_cast<LIBC_NAMESPACE::File *>(f)->read_unlocked(&c, 1);
+  size_t r = result.value;
+  if (result.has_error() || r != 1)
+    return '\0';
+
+  return c;
+}
+
+LIBC_INLINE void ungetc(int c, void *f) {
+  reinterpret_cast<LIBC_NAMESPACE::File *>(f)->ungetc_unlocked(c);
+}
+
+#else  // defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE)
+
+// Since ungetc_unlocked isn't always available, we don't acquire the lock for
+// system files.
+LIBC_INLINE int getc(void *f) { return ::getc(reinterpret_cast<::FILE *>(f)); }
+
+LIBC_INLINE void ungetc(int c, void *f) {
+  ::ungetc(c, reinterpret_cast<::FILE *>(f));
+}
+#endif // LIBC_COPT_STDIO_USE_SYSTEM_FILE
+
+} // namespace reader_internal
 
 // This is intended to be either a raw string or a buffer syncronized with the
 // file's internal buffer.
@@ -29,24 +87,15 @@
 
 class Reader {
   ReadBuffer *rb;
-
   void *input_stream = nullptr;
-
-  // TODO: Remove these unnecessary function pointers
-  StreamGetc stream_getc = nullptr;
-  StreamUngetc stream_ungetc = nullptr;
-
   size_t cur_chars_read = 0;
 
 public:
   // TODO: Set buff_len with a proper constant
   LIBC_INLINE Reader(ReadBuffer *string_buffer) : rb(string_buffer) {}
 
-  LIBC_INLINE Reader(void *stream, StreamGetc stream_getc_in,
-                     StreamUngetc stream_ungetc_in,
-                     ReadBuffer *stream_buffer = nullptr)
-      : rb(stream_buffer), input_stream(stream), stream_getc(stream_getc_in),
-        stream_ungetc(stream_ungetc_in) {}
+  LIBC_INLINE Reader(void *stream, ReadBuffer *stream_buffer = nullptr)
+      : rb(stream_buffer), input_stream(stream) {}
 
   // This returns the next character from the input and advances it by one
   // character. When it hits the end of the string or file it returns '\0' to
@@ -59,12 +108,23 @@
       return output;
     }
     // This should reset the buffer if applicable.
-    return static_cast<char>(stream_getc(input_stream));
+    return static_cast<char>(reader_internal::getc(input_stream));
   }
 
   // This moves the input back by one character, placing c into the buffer if
   // this is a file reader, else c is ignored.
-  void ungetc(char c);
+  LIBC_INLINE void ungetc(char c) {
+    --cur_chars_read;
+    if (rb != nullptr && rb->buff_cur > 0) {
+      // While technically c should be written back to the buffer, in scanf we
+      // always write the character that was already there. Additionally, the
+      // buffer is most likely to contain a string that isn't part of a file,
+      // which may not be writable.
+      --(rb->buff_cur);
+      return;
+    }
+    reader_internal::ungetc(static_cast<int>(c), input_stream);
+  }
 
   LIBC_INLINE size_t chars_read() { return cur_chars_read; }
 };
diff --git a/src/stdio/scanf_core/vfscanf_internal.h b/src/stdio/scanf_core/vfscanf_internal.h
index 6712643..4e20fa3 100644
--- a/src/stdio/scanf_core/vfscanf_internal.h
+++ b/src/stdio/scanf_core/vfscanf_internal.h
@@ -18,8 +18,6 @@
 
 #if defined(LIBC_TARGET_ARCH_IS_GPU)
 #include "src/stdio/ferror.h"
-#include "src/stdio/getc.h"
-#include "src/stdio/ungetc.h"
 #endif
 
 #include "hdr/types/FILE.h"
@@ -38,14 +36,6 @@
 
 LIBC_INLINE void funlockfile(::FILE *) { return; }
 
-LIBC_INLINE int getc(void *f) {
-  return LIBC_NAMESPACE::getc(reinterpret_cast<::FILE *>(f));
-}
-
-LIBC_INLINE void ungetc(int c, void *f) {
-  LIBC_NAMESPACE::ungetc(c, reinterpret_cast<::FILE *>(f));
-}
-
 LIBC_INLINE int ferror_unlocked(::FILE *f) { return LIBC_NAMESPACE::ferror(f); }
 
 #elif !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE)
@@ -58,21 +48,6 @@
   reinterpret_cast<LIBC_NAMESPACE::File *>(f)->unlock();
 }
 
-LIBC_INLINE int getc(void *f) {
-  unsigned char c;
-  auto result =
-      reinterpret_cast<LIBC_NAMESPACE::File *>(f)->read_unlocked(&c, 1);
-  size_t r = result.value;
-  if (result.has_error() || r != 1)
-    return '\0';
-
-  return c;
-}
-
-LIBC_INLINE void ungetc(int c, void *f) {
-  reinterpret_cast<LIBC_NAMESPACE::File *>(f)->ungetc_unlocked(c);
-}
-
 LIBC_INLINE int ferror_unlocked(FILE *f) {
   return reinterpret_cast<LIBC_NAMESPACE::File *>(f)->error_unlocked();
 }
@@ -85,12 +60,6 @@
 
 LIBC_INLINE void funlockfile(::FILE *) { return; }
 
-LIBC_INLINE int getc(void *f) { return ::getc(reinterpret_cast<::FILE *>(f)); }
-
-LIBC_INLINE void ungetc(int c, void *f) {
-  ::ungetc(c, reinterpret_cast<::FILE *>(f));
-}
-
 LIBC_INLINE int ferror_unlocked(::FILE *f) { return ::ferror(f); }
 
 #endif // LIBC_COPT_STDIO_USE_SYSTEM_FILE
@@ -103,7 +72,7 @@
                                  const char *__restrict format,
                                  internal::ArgList &args) {
   internal::flockfile(stream);
-  scanf_core::Reader reader(stream, &internal::getc, internal::ungetc);
+  scanf_core::Reader reader(stream);
   int retval = scanf_core::scanf_main(&reader, format, args);
   if (retval == 0 && internal::ferror_unlocked(stream))
     retval = EOF;
diff --git a/src/time/strftime.cpp b/src/time/strftime.cpp
new file mode 100644
index 0000000..4b89bf2
--- /dev/null
+++ b/src/time/strftime.cpp
@@ -0,0 +1,31 @@
+//===-- 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 "hdr/types/size_t.h"
+#include "hdr/types/struct_tm.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.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,
+                   (char *__restrict buffer, size_t buffsz,
+                    const char *__restrict format, const tm *timeptr)) {
+
+  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(&wb);
+  int ret = strftime_core::strftime_main(&writer, format, timeptr);
+  if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
+    wb.buff[wb.buff_cur] = '\0';
+  return (ret < 0 || static_cast<size_t>(ret) > buffsz) ? 0 : ret;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/src/time/strftime.h b/src/time/strftime.h
new file mode 100644
index 0000000..dadd046
--- /dev/null
+++ b/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 "hdr/types/size_t.h"
+#include "hdr/types/struct_tm.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+size_t strftime(char *__restrict, size_t max, const char *__restrict format,
+                const tm *timeptr);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_TIME_STRFTIME_H
diff --git a/src/time/strftime_core/composite_converter.h b/src/time/strftime_core/composite_converter.h
new file mode 100644
index 0000000..3530075
--- /dev/null
+++ b/src/time/strftime_core/composite_converter.h
@@ -0,0 +1,237 @@
+//===-- Composite converter for strftime ------------------------*- 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 "hdr/types/struct_tm.h"
+#include "src/__support/CPP/string_view.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/num_converter.h"
+#include "src/time/strftime_core/str_converter.h"
+#include "src/time/time_constants.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+LIBC_INLINE IntFormatSection
+get_specific_int_format(const tm *timeptr, const FormatSection &base_to_conv,
+                        char new_conv_name, int TRAILING_CONV_LEN = -1) {
+  // a negative padding will be treated as the default
+  const int NEW_MIN_WIDTH =
+      TRAILING_CONV_LEN > 0 ? base_to_conv.min_width - TRAILING_CONV_LEN : 0;
+  FormatSection new_conv = base_to_conv;
+  new_conv.conv_name = new_conv_name;
+  new_conv.min_width = NEW_MIN_WIDTH;
+
+  IntFormatSection result = get_int_format(new_conv, timeptr);
+
+  // If the user set the padding, but it's below the width of the trailing
+  // conversions, then there should be no padding.
+  if (base_to_conv.min_width > 0 && NEW_MIN_WIDTH < 0)
+    result.pad_to_len = 0;
+
+  return result;
+}
+
+LIBC_INLINE int convert_date_us(printf_core::Writer *writer,
+                                const FormatSection &to_conv,
+                                const tm *timeptr) {
+  // format is %m/%d/%y (month/day/year)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof("/01/02")
+  IntFormatSection year_conv;
+  IntFormatSection mon_conv;
+  IntFormatSection mday_conv;
+
+  mon_conv = get_specific_int_format(timeptr, to_conv, 'm', TRAILING_CONV_LEN);
+  mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
+  year_conv = get_specific_int_format(timeptr, to_conv, 'y');
+
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mon_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write('/'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mday_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write('/'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, year_conv));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_date_iso(printf_core::Writer *writer,
+                                 const FormatSection &to_conv,
+                                 const tm *timeptr) {
+  // format is "%Y-%m-%d" (year-month-day)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof("-01-02")
+  IntFormatSection year_conv;
+  IntFormatSection mon_conv;
+  IntFormatSection mday_conv;
+
+  year_conv = get_specific_int_format(timeptr, to_conv, 'Y', TRAILING_CONV_LEN);
+  mon_conv = get_specific_int_format(timeptr, to_conv, 'm');
+  mday_conv = get_specific_int_format(timeptr, to_conv, 'd');
+
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, year_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write('-'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mon_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write('-'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mday_conv));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_time_am_pm(printf_core::Writer *writer,
+                                   const FormatSection &to_conv,
+                                   const tm *timeptr) {
+  // format is "%I:%M:%S %p" (hour:minute:second AM/PM)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  constexpr int TRAILING_CONV_LEN =
+      1 + 2 + 1 + 2 + 1 + 2; // sizeof(":01:02 AM")
+  IntFormatSection hour_conv;
+  IntFormatSection min_conv;
+  IntFormatSection sec_conv;
+
+  const time_utils::TMReader time_reader(timeptr);
+
+  hour_conv = get_specific_int_format(timeptr, to_conv, 'I', TRAILING_CONV_LEN);
+  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+  sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
+
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(':'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, min_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(':'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, sec_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(' '));
+  RET_IF_RESULT_NEGATIVE(writer->write(time_reader.get_am_pm()));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_time_minute(printf_core::Writer *writer,
+                                    const FormatSection &to_conv,
+                                    const tm *timeptr) {
+  // format is "%H:%M" (hour:minute)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  constexpr int TRAILING_CONV_LEN = 1 + 2; // sizeof(":01")
+  IntFormatSection hour_conv;
+  IntFormatSection min_conv;
+
+  hour_conv = get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
+  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(':'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, min_conv));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_time_second(printf_core::Writer *writer,
+                                    const FormatSection &to_conv,
+                                    const tm *timeptr) {
+  // format is "%H:%M:%S" (hour:minute:second)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  constexpr int TRAILING_CONV_LEN = 1 + 2 + 1 + 2; // sizeof(":01:02")
+  IntFormatSection hour_conv;
+  IntFormatSection min_conv;
+  IntFormatSection sec_conv;
+
+  hour_conv = get_specific_int_format(timeptr, to_conv, 'H', TRAILING_CONV_LEN);
+  min_conv = get_specific_int_format(timeptr, to_conv, 'M');
+  sec_conv = get_specific_int_format(timeptr, to_conv, 'S');
+
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, hour_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(':'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, min_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(':'));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, sec_conv));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_full_date_time(printf_core::Writer *writer,
+                                       const FormatSection &to_conv,
+                                       const tm *timeptr) {
+  const time_utils::TMReader time_reader(timeptr);
+  // format is "%a %b %e %T %Y" (weekday month mday [time] year)
+  // we only pad the first conversion, and we assume all the other values are in
+  // their valid ranges.
+  // sizeof("Sun Jan 12 03:45:06 2025")
+  constexpr int FULL_CONV_LEN = 3 + 1 + 3 + 1 + 2 + 1 + 8 + 1 + 4;
+  // use the full conv len because this isn't being passed to a proper converter
+  // that will handle the width of the leading conversion. Instead it has to be
+  // handled below.
+  const int requested_padding = to_conv.min_width - FULL_CONV_LEN;
+
+  cpp::string_view wday_str = unwrap_opt(time_reader.get_weekday_short_name());
+  cpp::string_view month_str = unwrap_opt(time_reader.get_month_short_name());
+  IntFormatSection mday_conv;
+  IntFormatSection year_conv;
+
+  mday_conv = get_specific_int_format(timeptr, to_conv, 'e');
+  year_conv = get_specific_int_format(timeptr, to_conv, 'Y');
+
+  FormatSection raw_time_conv = to_conv;
+  raw_time_conv.conv_name = 'T';
+  raw_time_conv.min_width = 0;
+
+  if (requested_padding > 0)
+    RET_IF_RESULT_NEGATIVE(writer->write(' ', requested_padding));
+  RET_IF_RESULT_NEGATIVE(writer->write(wday_str));
+  RET_IF_RESULT_NEGATIVE(writer->write(' '));
+  RET_IF_RESULT_NEGATIVE(writer->write(month_str));
+  RET_IF_RESULT_NEGATIVE(writer->write(' '));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, mday_conv));
+  RET_IF_RESULT_NEGATIVE(writer->write(' '));
+  RET_IF_RESULT_NEGATIVE(convert_time_second(writer, raw_time_conv, timeptr));
+  RET_IF_RESULT_NEGATIVE(writer->write(' '));
+  RET_IF_RESULT_NEGATIVE(write_padded_int(writer, year_conv));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE int convert_composite(printf_core::Writer *writer,
+                                  const FormatSection &to_conv,
+                                  const tm *timeptr) {
+  switch (to_conv.conv_name) {
+  case 'c': // locale specified date and time
+            // in default locale Equivalent to %a %b %e %T %Y.
+    return convert_full_date_time(writer, to_conv, timeptr);
+  case 'D': // %m/%d/%y (month/day/year)
+    return convert_date_us(writer, to_conv, timeptr);
+  case 'F': // %Y-%m-%d (year-month-day)
+    return convert_date_iso(writer, to_conv, timeptr);
+  case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
+    return convert_time_am_pm(writer, to_conv, timeptr);
+  case 'R': // %H:%M (hour:minute)
+    return convert_time_minute(writer, to_conv, timeptr);
+  case 'T': // %H:%M:%S (hour:minute:second)
+    return convert_time_second(writer, to_conv, timeptr);
+  case 'x': // locale specified date
+            // in default locale Equivalent to %m/%d/%y. (same as %D)
+    return convert_date_us(writer, to_conv, timeptr);
+  case 'X': // locale specified time
+            // in default locale Equivalent to %T.
+    return convert_time_second(writer, to_conv, timeptr);
+  default:
+    __builtin_trap(); // this should be unreachable, but trap if you hit it.
+  }
+}
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_COMPOSITE_CONVERTER_H
diff --git a/src/time/strftime_core/converter.cpp b/src/time/strftime_core/converter.cpp
new file mode 100644
index 0000000..e9263af
--- /dev/null
+++ b/src/time/strftime_core/converter.cpp
@@ -0,0 +1,96 @@
+//===-- Format specifier converter implmentation for strftime -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/struct_tm.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.h"
+
+#include "composite_converter.h"
+#include "num_converter.h"
+#include "str_converter.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int convert(printf_core::Writer *writer, const FormatSection &to_conv,
+            const tm *timeptr) {
+  // TODO: Implement the locale support.
+  // Currently locale flags are ignored, as described by the posix standard for
+  // the default locale.
+
+  if (!to_conv.has_conv)
+    return writer->write(to_conv.raw_string);
+  switch (to_conv.conv_name) {
+    // The cases are grouped by type, then alphabetized with lowercase before
+    // uppercase.
+
+    // raw conversions
+  case '%':
+    return writer->write("%");
+  case 'n':
+    return writer->write("\n");
+  case 't':
+    return writer->write("\t");
+
+    // numeric conversions
+  case 'C': // Century [00-99]
+  case 'd': // Day of the month [01-31]
+  case 'e': // Day of the month [1-31]
+  case 'g': // last 2 digits of ISO year [00-99]
+  case 'G': // ISO year
+  case 'H': // 24-hour format [00-23]
+  case 'I': // 12-hour format [01-12]
+  case 'j': // Day of the year [001-366]
+  case 'm': // Month of the year [01-12]
+  case 'M': // Minute of the hour [00-59]
+  case 's': // Seconds since the epoch
+  case 'S': // Second of the minute [00-60]
+  case 'u': // ISO day of the week ([1-7] starting Monday)
+  case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
+  case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
+  case 'w': // Day of week ([0-6] starting Sunday)
+  case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
+  case 'y': // Year of the Century [00-99]
+  case 'Y': // Full year
+    return convert_int(writer, to_conv, timeptr);
+
+    // string conversions
+  case 'a': // Abbreviated weekday name
+  case 'A': // Full weekday name
+  case 'b': // Abbreviated month name
+  case 'B': // Full month name
+  case 'h': // same as %b
+  case 'p': // AM/PM designation
+    return convert_str(writer, to_conv, timeptr);
+
+    // composite conversions
+  case 'c': // locale specified date and time
+  case 'D': // %m/%d/%y (month/day/year)
+  case 'F': // %Y-%m-%d (year-month-day)
+  case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
+  case 'R': // %H:%M (hour:minute)
+  case 'T': // %H:%M:%S (hour:minute:second)
+  case 'x': // locale specified date
+  case 'X': // locale specified time
+    return convert_composite(writer, to_conv, timeptr);
+
+    // timezone conversions
+  case 'z': // Timezone offset (+/-hhmm) (num conv)
+  case 'Z': // Timezone name (string conv)
+    // the standard says if no time zone is determinable, write no characters.
+    // Leave this here until time zones are implemented.
+    return 0;
+  default:
+    return writer->write(to_conv.raw_string);
+  }
+  return 0;
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/src/time/strftime_core/converter.h b/src/time/strftime_core/converter.h
new file mode 100644
index 0000000..154ee38d
--- /dev/null
+++ b/src/time/strftime_core/converter.h
@@ -0,0 +1,28 @@
+//===-- Format specifier converter 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_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
+
+#include "hdr/types/struct_tm.h"
+#include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/core_structs.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,
+            const tm *timeptr);
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
diff --git a/src/time/strftime_core/core_structs.h b/src/time/strftime_core/core_structs.h
new file mode 100644
index 0000000..25bf5e6
--- /dev/null
+++ b/src/time/strftime_core/core_structs.h
@@ -0,0 +1,53 @@
+//===-- Core Structures 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_CORE_STRUCTS_H
+#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
+
+#include "hdr/types/struct_tm.h"
+#include "src/__support/CPP/string_view.h"
+
+#include <stdint.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+enum class ConvModifier { none, E, O };
+
+// These flags intentionally have different values from the ones used by printf.
+// They have different meanings.
+enum FormatFlags : uint8_t {
+  FORCE_SIGN = 0x01,     // +
+  LEADING_ZEROES = 0x02, // 0
+  // TODO: look into the glibc extension flags ('_', '-', '^', and '#')
+};
+
+struct FormatSection {
+  bool has_conv = false;
+  cpp::string_view raw_string = {};
+
+  FormatFlags flags = FormatFlags(0);
+  ConvModifier modifier = ConvModifier::none;
+  char conv_name = '\0';
+  int min_width = 0;
+};
+
+// TODO: Move this to a better spot
+#define RET_IF_RESULT_NEGATIVE(func)                                           \
+  {                                                                            \
+    int result = (func);                                                       \
+    if (result < 0)                                                            \
+      return result;                                                           \
+  }
+
+constexpr int WRITE_OK = 0;
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CORE_STRUCTS_H
diff --git a/src/time/strftime_core/num_converter.h b/src/time/strftime_core/num_converter.h
new file mode 100644
index 0000000..aef9ddb
--- /dev/null
+++ b/src/time/strftime_core/num_converter.h
@@ -0,0 +1,199 @@
+//===-- Numeric converter for strftime --------------------------*- 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 "hdr/types/struct_tm.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/time_constants.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+using DecFmt = IntegerToString<uintmax_t>;
+
+struct IntFormatSection {
+  uintmax_t num = 0;
+  char sign_char = '\0';
+  size_t pad_to_len = 0;
+  char padding_char = '0';
+};
+
+LIBC_INLINE int write_padded_int(printf_core::Writer *writer,
+                                 const IntFormatSection &num_info) {
+
+  DecFmt d(num_info.num);
+  auto str = d.view();
+
+  size_t digits_written = str.size();
+
+  // one less digit of padding if there's a sign char
+  int zeroes = static_cast<int>(num_info.pad_to_len - digits_written -
+                                (num_info.sign_char == 0 ? 0 : 1));
+
+  // Format is (sign) (padding) digits
+  if (num_info.sign_char != 0)
+    RET_IF_RESULT_NEGATIVE(writer->write(num_info.sign_char));
+  if (zeroes > 0)
+    RET_IF_RESULT_NEGATIVE(writer->write(num_info.padding_char, zeroes))
+  RET_IF_RESULT_NEGATIVE(writer->write(str));
+
+  return WRITE_OK;
+}
+
+LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
+                                            const tm *timeptr) {
+  const time_utils::TMReader time_reader(timeptr);
+
+  intmax_t raw_num;
+
+  IntFormatSection result = {0, 0, 0, '0'};
+
+  // gets_plus_sign is only true for year conversions where the year would be
+  // positive and more than 4 digits, including leading spaces. Both the
+  // FORCE_SIGN flag and gets_plus_sign must be true for a plus sign to be
+  // output.
+  bool gets_plus_sign = false;
+
+  switch (to_conv.conv_name) {
+  case 'C': // Century [00-99]
+    raw_num = time_reader.get_year() / 100;
+    gets_plus_sign = raw_num > 99 || to_conv.min_width > 2;
+    result.pad_to_len = 2;
+    break;
+  case 'd':                           // Day of the month [01-31]
+    raw_num = time_reader.get_mday(); // get_mday is 1 indexed
+    result.pad_to_len = 2;
+    break;
+  case 'e':                           // Day of the month [1-31]
+    raw_num = time_reader.get_mday(); // get_mday is 1 indexed
+    result.pad_to_len = 2;
+    result.padding_char = ' ';
+    break;
+  case 'g': // last 2 digits of ISO year [00-99]
+    raw_num = time_reader.get_iso_year() % 100;
+    result.pad_to_len = 2;
+    break;
+  case 'G': // ISO year
+    raw_num = time_reader.get_iso_year();
+    gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;
+    result.pad_to_len = 4;
+    break;
+  case 'H': // 24-hour format [00-23]
+    raw_num = time_reader.get_hour();
+    result.pad_to_len = 2;
+    break;
+  case 'I': // 12-hour format [01-12]
+    raw_num = ((time_reader.get_hour() + 11) % 12) + 1;
+    result.pad_to_len = 2;
+    break;
+  case 'j':                               // Day of the year [001-366]
+    raw_num = time_reader.get_yday() + 1; // get_yday is 0 indexed
+    result.pad_to_len = 3;
+    break;
+  case 'm':                              // Month of the year [01-12]
+    raw_num = time_reader.get_mon() + 1; // get_mon is 0 indexed
+    result.pad_to_len = 2;
+    break;
+  case 'M': // Minute of the hour [00-59]
+    raw_num = time_reader.get_min();
+    result.pad_to_len = 2;
+    break;
+  case 's': // Seconds since the epoch
+    raw_num = time_reader.get_epoch();
+    result.pad_to_len = 0;
+    break;
+  case 'S': // Second of the minute [00-60]
+    raw_num = time_reader.get_sec();
+    result.pad_to_len = 2;
+    break;
+  case 'u': // ISO day of the week ([1-7] starting Monday)
+    raw_num = time_reader.get_iso_wday() + 1;
+    // need to add 1 because get_iso_wday returns the weekday [0-6].
+    result.pad_to_len = 1;
+    break;
+  case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
+    // This doesn't actually end up using tm_year, despite the standard saying
+    // it's needed. The end of the current year doesn't really matter, so leap
+    // years aren't relevant. If this is wrong, please tell me what I'm missing.
+    raw_num = time_reader.get_week(time_constants::SUNDAY);
+    result.pad_to_len = 2;
+    break;
+  case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
+    // This does need to know the year, since it may affect what the week of the
+    // previous year it underflows to.
+    raw_num = time_reader.get_iso_week();
+    result.pad_to_len = 2;
+    break;
+  case 'w': // Day of week ([0-6] starting Sunday)
+    raw_num = time_reader.get_wday();
+    result.pad_to_len = 1;
+    break;
+  case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
+    raw_num = time_reader.get_week(time_constants::MONDAY);
+    result.pad_to_len = 2;
+    break;
+  case 'y': // Year of the Century [00-99]
+    raw_num = time_reader.get_year() % 100;
+    result.pad_to_len = 2;
+    break;
+  case 'Y': // Full year
+    raw_num = time_reader.get_year();
+    gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;
+    result.pad_to_len = 4;
+    break;
+  case 'z': // Timezone offset [+/-HHMM]
+    raw_num = time_reader.get_timezone_offset();
+    result.sign_char = '+'; // force the '+' sign iff raw_num is non-negative
+    result.pad_to_len = 5;  // 4 + 1 for the sign
+    break;
+  default:
+    __builtin_trap(); // this should be unreachable, but trap if you hit it.
+  }
+
+  result.num = static_cast<uintmax_t>(raw_num < 0 ? -raw_num : raw_num);
+  const bool is_negative = raw_num < 0;
+
+  // TODO: Handle locale modifiers
+
+  if ((to_conv.flags & FormatFlags::LEADING_ZEROES) ==
+      FormatFlags::LEADING_ZEROES)
+    result.padding_char = '0';
+
+  if (is_negative)
+    result.sign_char = '-';
+  else if ((to_conv.flags & FormatFlags::FORCE_SIGN) ==
+               FormatFlags::FORCE_SIGN &&
+           gets_plus_sign)
+    result.sign_char = '+';
+
+  // sign isn't a problem because we're taking the max. The result is always
+  // non-negative. Also min_width can only be 0 if it's defaulted, since 0 is a
+  // flag.
+  if (to_conv.min_width > 0)
+    result.pad_to_len = to_conv.min_width;
+
+  return result;
+}
+
+LIBC_INLINE int convert_int(printf_core::Writer *writer,
+                            const FormatSection &to_conv, const tm *timeptr) {
+
+  return write_padded_int(writer, get_int_format(to_conv, timeptr));
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif
diff --git a/src/time/strftime_core/parser.h b/src/time/strftime_core/parser.h
new file mode 100644
index 0000000..659587a
--- /dev/null
+++ b/src/time/strftime_core/parser.h
@@ -0,0 +1,111 @@
+//===-- 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 "core_structs.h"
+#include "hdr/types/struct_tm.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/str_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+class Parser {
+  const char *str;
+  size_t cur_pos = 0;
+
+public:
+  LIBC_INLINE Parser(const char *new_str) : str(new_str) {}
+
+  // 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;
+      section.raw_string = {str + starting_pos, cur_pos - starting_pos};
+      return section;
+    }
+
+    // format section
+    section.has_conv = true;
+    ++cur_pos;
+
+    // flags
+    section.flags = parse_flags(&cur_pos);
+
+    // handle width
+    section.min_width = 0;
+    if (internal::isdigit(str[cur_pos])) {
+      auto result = internal::strtointeger<int>(str + cur_pos, 10);
+      section.min_width = result.value;
+      cur_pos = cur_pos + result.parsed_len;
+    }
+
+    // modifiers
+    switch (str[cur_pos]) {
+    case ('E'):
+      section.modifier = ConvModifier::E;
+      ++cur_pos;
+      break;
+    case ('O'):
+      section.modifier = ConvModifier::O;
+      ++cur_pos;
+      break;
+    default:
+      section.modifier = ConvModifier::none;
+    }
+
+    section.conv_name = 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')
+      ++cur_pos;
+
+    section.raw_string = {str + starting_pos, cur_pos - starting_pos};
+    return section;
+  }
+
+private:
+  LIBC_INLINE FormatFlags parse_flags(size_t *local_pos) {
+    bool found_flag = true;
+    FormatFlags flags = FormatFlags(0);
+    while (found_flag) {
+      switch (str[*local_pos]) {
+      case '+':
+        flags = static_cast<FormatFlags>(flags | FormatFlags::FORCE_SIGN);
+        break;
+      case '0':
+        flags = static_cast<FormatFlags>(flags | FormatFlags::LEADING_ZEROES);
+        break;
+      default:
+        found_flag = false;
+      }
+      if (found_flag)
+        ++*local_pos;
+    }
+    return flags;
+  }
+};
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_PARSER_H
diff --git a/src/time/strftime_core/str_converter.h b/src/time/strftime_core/str_converter.h
new file mode 100644
index 0000000..f0d5bf5
--- /dev/null
+++ b/src/time/strftime_core/str_converter.h
@@ -0,0 +1,76 @@
+//===-- String converter for strftime ---------------------------*- 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 "hdr/types/struct_tm.h"
+#include "src/__support/CPP/string_view.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/time_constants.h"
+#include "src/time/time_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+static constexpr cpp::string_view OUT_OF_BOUNDS_STR = "?";
+
+LIBC_INLINE cpp::string_view
+unwrap_opt(cpp::optional<cpp::string_view> str_opt) {
+  return str_opt.has_value() ? *str_opt : OUT_OF_BOUNDS_STR;
+}
+
+LIBC_INLINE int convert_str(printf_core::Writer *writer,
+                            const FormatSection &to_conv, const tm *timeptr) {
+  cpp::string_view str;
+  cpp::optional<cpp::string_view> str_opt;
+  const time_utils::TMReader time_reader(timeptr);
+
+  switch (to_conv.conv_name) {
+  case 'a': // Abbreviated weekday name
+    str_opt = time_reader.get_weekday_short_name();
+    str = unwrap_opt(str_opt);
+    break;
+  case 'A': // Full weekday name
+    str_opt = time_reader.get_weekday_full_name();
+    str = unwrap_opt(str_opt);
+    break;
+  case 'b': // Abbreviated month name
+  case 'h': // same as 'b'
+    str_opt = time_reader.get_month_short_name();
+    str = unwrap_opt(str_opt);
+    break;
+  case 'B': // Full month name
+    str_opt = time_reader.get_month_full_name();
+    str = unwrap_opt(str_opt);
+    break;
+  case 'p': // AM/PM designation
+    str = time_reader.get_am_pm();
+    break;
+  case 'Z': // Timezone name
+    // the standard says if no time zone is determinable, write no characters.
+    return WRITE_OK;
+    // str = time_reader.get_timezone_name();
+    break;
+  default:
+    __builtin_trap(); // this should be unreachable, but trap if you hit it.
+  }
+
+  int spaces = to_conv.min_width - static_cast<int>(str.size());
+  if (spaces > 0)
+    RET_IF_RESULT_NEGATIVE(writer->write(' ', spaces));
+  RET_IF_RESULT_NEGATIVE(writer->write(str));
+
+  return WRITE_OK;
+}
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STR_CONVERTER_H
diff --git a/src/time/strftime_core/strftime_main.cpp b/src/time/strftime_core/strftime_main.cpp
new file mode 100644
index 0000000..00839e5
--- /dev/null
+++ b/src/time/strftime_core/strftime_main.cpp
@@ -0,0 +1,40 @@
+//===-- Starting point for strftime ---------------------------------------===//
+//
+// 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 "hdr/types/struct_tm.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"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int strftime_main(printf_core::Writer *writer, const char *__restrict str,
+                  const tm *timeptr) {
+  Parser parser(str);
+  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, timeptr);
+    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/src/time/strftime_core/strftime_main.h b/src/time/strftime_core/strftime_main.h
new file mode 100644
index 0000000..ae70682
--- /dev/null
+++ b/src/time/strftime_core/strftime_main.h
@@ -0,0 +1,25 @@
+//===-- 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 "hdr/types/struct_tm.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/writer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace strftime_core {
+
+int strftime_main(printf_core::Writer *writer, const char *__restrict str,
+                  const tm *timeptr);
+
+} // namespace strftime_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_STRFTIME_MAIN_H
diff --git a/src/time/time_constants.h b/src/time/time_constants.h
index bcf19ff..0fcb7ff 100644
--- a/src/time/time_constants.h
+++ b/src/time/time_constants.h
@@ -48,6 +48,7 @@
 constexpr int DAYS_PER_WEEK = 7;
 constexpr int WEEKS_PER_YEAR = 52;
 constexpr int MONTHS_PER_YEAR = 12;
+constexpr int MAX_DAYS_PER_MONTH = 31;
 constexpr int DAYS_PER_NON_LEAP_YEAR = 365;
 constexpr int DAYS_PER_LEAP_YEAR = 366;
 
diff --git a/src/unistd/getsid.h b/src/unistd/getsid.h
new file mode 100644
index 0000000..e788b5d
--- /dev/null
+++ b/src/unistd/getsid.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for getsid ------------------------*- 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_UNISTD_GETSID_H
+#define LLVM_LIBC_SRC_UNISTD_GETSID_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+pid_t getsid(pid_t);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UNISTD_GETSID_H
diff --git a/src/unistd/linux/getsid.cpp b/src/unistd/linux/getsid.cpp
new file mode 100644
index 0000000..5977c5b
--- /dev/null
+++ b/src/unistd/linux/getsid.cpp
@@ -0,0 +1,29 @@
+//===-- Linux implementation of getsid-------------------------------------===//
+//
+// 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/unistd/getsid.h"
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/errno/libc_errno.h"
+#include <sys/syscall.h> // For syscall numbers.
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(pid_t, getsid, (pid_t pid)) {
+  pid_t ret = LIBC_NAMESPACE::syscall_impl<pid_t>(SYS_getsid, pid);
+  if (ret < 0) {
+    libc_errno = static_cast<int>(-ret);
+    return -1;
+  }
+  return ret;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/test/integration/src/__support/GPU/match.cpp b/test/integration/src/__support/GPU/match.cpp
new file mode 100644
index 0000000..0eadb13
--- /dev/null
+++ b/test/integration/src/__support/GPU/match.cpp
@@ -0,0 +1,35 @@
+//===-- Test for the shuffle operations on the GPU ------------------------===//
+//
+// 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/bit.h"
+#include "src/__support/GPU/utils.h"
+#include "test/IntegrationTest/test.h"
+
+using namespace LIBC_NAMESPACE;
+
+// Test to ensure that match any / match all work.
+static void test_match() {
+  uint64_t mask = gpu::get_lane_mask();
+  EXPECT_EQ(1ull << gpu::get_lane_id(),
+            gpu::match_any(mask, gpu::get_lane_id()));
+  EXPECT_EQ(mask, gpu::match_any(mask, 1));
+
+  uint64_t expected = gpu::get_lane_id() < 16 ? 0xffff : 0xffff0000;
+  EXPECT_EQ(expected, gpu::match_any(mask, gpu::get_lane_id() < 16));
+  EXPECT_EQ(mask, gpu::match_all(mask, 1));
+  EXPECT_EQ(0ull, gpu::match_all(mask, gpu::get_lane_id()));
+}
+
+TEST_MAIN(int argc, char **argv, char **envp) {
+  if (gpu::get_thread_id() >= gpu::get_lane_size())
+    return 0;
+
+  test_match();
+
+  return 0;
+}
diff --git a/test/integration/src/__support/GPU/scan_reduce.cpp b/test/integration/src/__support/GPU/scan_reduce.cpp
index bc621c33..1d50e1f 100644
--- a/test/integration/src/__support/GPU/scan_reduce.cpp
+++ b/test/integration/src/__support/GPU/scan_reduce.cpp
@@ -53,10 +53,59 @@
   EXPECT_EQ(z, gpu::get_lane_id() % 2 ? gpu::get_lane_id() / 2 + 1 : 0);
 }
 
+static uint32_t random(uint64_t *rand_next) {
+  uint64_t x = *rand_next;
+  x ^= x >> 12;
+  x ^= x << 25;
+  x ^= x >> 27;
+  *rand_next = x;
+  return static_cast<uint32_t>((x * 0x2545F4914F6CDD1Dul) >> 32);
+}
+
+// Scan operations can break down under thread divergence, make sure that the
+// function works under some random divergence. We do this by trivially
+// implementing a scan with shared scratch memory and then comparing the
+// results.
+static void test_scan_divergent() {
+  static uint32_t input[64] = {0};
+  static uint32_t result[64] = {0};
+  uint64_t state = gpu::processor_clock() + __gpu_lane_id();
+
+  for (int i = 0; i < 64; ++i) {
+    uint64_t lanemask = gpu::get_lane_mask();
+    if (random(&state) & (1ull << gpu::get_lane_id())) {
+      uint64_t divergent = gpu::get_lane_mask();
+      uint32_t value = random(&state) % 256;
+      input[gpu::get_lane_id()] = value;
+
+      if (gpu::is_first_lane(divergent)) {
+        uint32_t accumulator = 0;
+        for (uint32_t lane = 0; lane < gpu::get_lane_size(); ++lane) {
+          uint32_t tmp = input[lane];
+          result[lane] = tmp + accumulator;
+          accumulator += tmp;
+        }
+      }
+      gpu::sync_lane(divergent);
+
+      uint32_t scan = gpu::scan(divergent, value);
+      EXPECT_EQ(scan, result[gpu::get_lane_id()]);
+    }
+    if (gpu::is_first_lane(lanemask))
+      __builtin_memset(input, 0, sizeof(input));
+    gpu::sync_lane(lanemask);
+  }
+}
+
 TEST_MAIN(int argc, char **argv, char **envp) {
+  if (gpu::get_thread_id() >= gpu::get_lane_size())
+    return 0;
+
   test_reduce();
 
   test_scan();
 
+  test_scan_divergent();
+
   return 0;
 }
diff --git a/test/src/time/strftime_test.cpp b/test/src/time/strftime_test.cpp
new file mode 100644
index 0000000..4fbd115
--- /dev/null
+++ b/test/src/time/strftime_test.cpp
@@ -0,0 +1,2330 @@
+//===-- Unittests for strftime --------------------------------------------===//
+//
+// 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 "hdr/types/struct_tm.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/integer_to_string.h"
+#include "src/time/strftime.h"
+#include "src/time/time_constants.h"
+#include "test/UnitTest/Test.h"
+
+// Copied from sprintf_test.cpp.
+// TODO: put this somewhere more reusable, it's handy.
+// Subtract 1 from sizeof(expected_str) to account for the null byte.
+#define EXPECT_STREQ_LEN(actual_written, actual_str, expected_str)             \
+  EXPECT_EQ(actual_written, sizeof(expected_str) - 1);                         \
+  EXPECT_STREQ(actual_str, expected_str);
+
+constexpr int get_adjusted_year(int year) {
+  // tm_year counts years since 1900, so subtract 1900 to get the tm_year for a
+  // given raw year.
+  return year - LIBC_NAMESPACE::time_constants::TIME_YEAR_BASE;
+}
+
+// TODO: Move this somewhere it can be reused. It seems like a useful tool to
+// have.
+// A helper class to generate simple padded numbers. It places the result in its
+// internal buffer, which is cleared on every call.
+class SimplePaddedNum {
+  static constexpr size_t BUFF_LEN = 16;
+  char buff[BUFF_LEN];
+  size_t cur_len; // length of string currently in buff
+
+  void clear_buff() {
+    // TODO: builtin_memset?
+    for (size_t i = 0; i < BUFF_LEN; ++i)
+      buff[i] = '\0';
+  }
+
+public:
+  SimplePaddedNum() = default;
+
+  // PRECONDITIONS: 0 < num < 2**31, min_width < 16
+  // Returns: Pointer to the start of the padded number as a string, stored in
+  // the internal buffer.
+  char *get_padded_num(int num, size_t min_width, char padding_char = '0') {
+    clear_buff();
+
+    // we're not handling the negative sign here, so padding on negative numbers
+    // will be incorrect. For this use case I consider that to be a reasonable
+    // tradeoff for simplicity. This is more meant for the cases where we can
+    // loop through all the possibilities, and for time those are all positive.
+    LIBC_NAMESPACE::IntegerToString<int> raw(num);
+    auto str = raw.view();
+    int leading_zeroes = min_width - raw.size();
+
+    size_t i = 0;
+    for (; static_cast<int>(i) < leading_zeroes; ++i)
+      buff[i] = padding_char;
+    for (size_t str_cur = 0, e = str.size(); str_cur < e; ++i, ++str_cur)
+      buff[i] = str[str_cur];
+    cur_len = i;
+    return buff;
+  }
+
+  size_t get_str_len() { return cur_len; }
+};
+
+TEST(LlvmLibcStrftimeTest, ConstantConversions) {
+  // this tests %n, %t, and %%, which read nothing.
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%n", &time);
+  EXPECT_STREQ_LEN(written, buffer, "\n");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%t", &time);
+  EXPECT_STREQ_LEN(written, buffer, "\t");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%%", &time);
+  EXPECT_STREQ_LEN(written, buffer, "%");
+}
+
+TEST(LlvmLibcStrftimeTest, CenturyTests) {
+  // this tests %C, which reads: [tm_year]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // basic tests
+  time.tm_year = get_adjusted_year(2022);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "20");
+
+  time.tm_year = get_adjusted_year(11900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "119");
+
+  time.tm_year = get_adjusted_year(1900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "19");
+
+  time.tm_year = get_adjusted_year(900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "09");
+
+  time.tm_year = get_adjusted_year(0);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00");
+
+  // This case does not match what glibc does.
+  // Both the C standard and Posix say %C is "Replaced by the year divided by
+  // 100 and truncated to an integer, as a decimal number."
+  // What glibc does is it returns the century for the provided year.
+  // The difference is that glibc returns "-1" as the century for year -1, and
+  // "-2" for year -101.
+  // This case demonstrates that LLVM-libc instead just divides by 100, and
+  // returns the result. "00" for year -1, and "-1" for year -101.
+  // Personally, neither of these really feels right. Posix has a table of
+  // examples where it treats "%C%y" as identical to "%Y". Neither of these
+  // behaviors would handle that properly, you'd either get "-199" or "0099"
+  // (since %y always returns a number in the range [00-99]).
+  time.tm_year = get_adjusted_year(-1);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00");
+
+  time.tm_year = get_adjusted_year(-101);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-1");
+
+  time.tm_year = get_adjusted_year(-9001);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-90");
+
+  time.tm_year = get_adjusted_year(-10001);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-100");
+
+  // width tests (with the 0 flag, since the default padding is undefined).
+  time.tm_year = get_adjusted_year(2023);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "20");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "20");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00020");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000000020");
+
+  time.tm_year = get_adjusted_year(900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "9");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00009");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000000009");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000000123");
+
+  time.tm_year = get_adjusted_year(-123);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-1");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-1");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-0001");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-000000001");
+
+  // '+' flag tests
+  time.tm_year = get_adjusted_year(2023);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "20");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+2C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "20");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0020");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000000020");
+
+  time.tm_year = get_adjusted_year(900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "9");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+2C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0009");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000000009");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+2C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000000123");
+
+  time.tm_year = get_adjusted_year(-123);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-1");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+2C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-1");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-0001");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-000000001");
+
+  // Posix specified tests:
+  time.tm_year = get_adjusted_year(17);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00");
+
+  time.tm_year = get_adjusted_year(270);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "02");
+
+  time.tm_year = get_adjusted_year(270);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+3C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+02");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+3C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+123");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%04C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0123");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+123");
+
+  time.tm_year = get_adjusted_year(123456);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%06C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "001234");
+
+  time.tm_year = get_adjusted_year(123456);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+6C", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+01234");
+}
+
+TEST(LlvmLibcStrftimeTest, TwoDigitDayOfMonth) {
+  using LIBC_NAMESPACE::time_constants::MAX_DAYS_PER_MONTH;
+  // this tests %d, which reads: [tm_mday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 1; i <= MAX_DAYS_PER_MONTH; ++i) {
+    time.tm_mday = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%d", &time);
+    char *result = spn.get_padded_num(i, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, size_t(2));
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_mday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_mday = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05d", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00031");
+}
+
+TEST(LlvmLibcStrftimeTest, MinDigitDayOfMonth) {
+  // this tests %e, which reads: [tm_mday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 1; i < 32; ++i) {
+    time.tm_mday = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%e", &time);
+    char *result = spn.get_padded_num(i, 2, ' ');
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_mday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_mday = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05e", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00031");
+}
+
+TEST(LlvmLibcStrftimeTest, ISOYearOfCentury) {
+  // this tests %g, which reads: [tm_year, tm_wday, tm_yday]
+
+  // A brief primer on ISO dates:
+  // 1) ISO weeks start on Monday and end on Sunday
+  // 2) ISO years start on the Monday of the 1st ISO week of the year
+  // 3) The 1st ISO week of the ISO year has the 4th day of the Gregorian year.
+
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // a sunday in the middle of the year. No need to worry about rounding
+  time.tm_wday = 0;
+  time.tm_yday = 100;
+
+  // Test the easy cases
+  for (size_t i = 0; i < 102; ++i) {
+    time.tm_year = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%g", &time);
+    char *result = spn.get_padded_num(i % 100, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // Test the harder to round cases
+
+  // not a leap year. Not relevant for the start-of-year tests, but it does
+  // matter for the end-of-year tests.
+  time.tm_year = 99;
+
+  /*
+This table has an X for each day that should be in the previous year,
+everywhere else should be in the current year.
+
+       yday
+      0123456
+  i 1         Monday
+  s 2         Tuesday
+  o 3         Wednesday
+  w 4         Thursday
+  d 5 X       Friday
+  a 6 XX      Saturday
+  y 7 XXX     Sunday
+*/
+
+  // check the first days of the year
+  for (size_t yday = 0; yday < 5; ++yday) {
+    for (size_t iso_wday = LIBC_NAMESPACE::time_constants::MONDAY; iso_wday < 8;
+         ++iso_wday) {
+      // start with monday, to match the ISO week.
+      time.tm_wday = iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      time.tm_yday = yday;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%g", &time);
+
+      if (iso_wday <= LIBC_NAMESPACE::time_constants::THURSDAY || yday >= 3) {
+        // monday - thursday are never in the previous year, nor are the 4th and
+        // after.
+        EXPECT_STREQ_LEN(written, buffer, "99");
+      } else {
+        // iso_wday is 5, 6, or 7 and yday is 0, 1, or 2.
+        // days_since_thursday is therefor 1, 2, or 3.
+        const size_t days_since_thursday =
+            iso_wday - LIBC_NAMESPACE::time_constants::THURSDAY;
+
+        if (days_since_thursday > yday) {
+          EXPECT_STREQ_LEN(written, buffer, "98");
+        } else {
+          EXPECT_STREQ_LEN(written, buffer, "99");
+        }
+      }
+    }
+  }
+
+  /*
+  Similar to above, but the Xs represent being in the NEXT year. Also the
+  top counts down until the end of the year.
+
+    year end - yday
+        6543210
+    i 1     XXX Monday
+    s 2      XX Tuesday
+    o 3       X Wednesday
+    w 4         Thursday
+    d 5         Friday
+    a 6         Saturday
+    y 7         Sunday
+
+
+  If we place the charts next to each other, you can more easily see the
+  pattern:
+
+year end - yday yday
+        6543210 0123456
+    i 1     XXX         Monday
+    s 2      XX         Tuesday
+    o 3       X         Wednesday
+    w 4                 Thursday
+    d 5         X       Friday
+    a 6         XX      Saturday
+    y 7         XXX     Sunday
+
+    From this we can see that thursday is always in the same ISO and regular
+    year, because the ISO year starts on the week with the 4th. Since Thursday
+    is at least 3 days from either edge of the ISO week, the first thursday of
+    the year is always in the first ISO week of the year.
+  */
+
+  // set up all the extra stuff to cover leap years.
+  struct tm time_leap_year;
+  char buffer_leap_year[100];
+  size_t written_leap_year = 0;
+  time_leap_year = time;
+  time_leap_year.tm_year = 100; // 2000 is a leap year.
+
+  // check the last days of the year. Checking 5 to make sure all the leap year
+  // cases are covered as well.
+  for (size_t days_left = 0; days_left < 5; ++days_left) {
+    for (size_t iso_wday = LIBC_NAMESPACE::time_constants::MONDAY; iso_wday < 8;
+         ++iso_wday) {
+      // start with monday, to match the ISO week.
+      time.tm_wday = iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      // subtract 1 from the max yday to handle yday being 0-indexed.
+      time.tm_yday = LIBC_NAMESPACE::time_constants::DAYS_PER_NON_LEAP_YEAR -
+                     1 - days_left;
+
+      time_leap_year.tm_wday =
+          iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      time_leap_year.tm_yday =
+          LIBC_NAMESPACE::time_constants::LAST_DAY_OF_LEAP_YEAR - days_left;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%g", &time);
+      written_leap_year = LIBC_NAMESPACE::strftime(
+          buffer_leap_year, sizeof(buffer_leap_year), "%g", &time_leap_year);
+
+      if (iso_wday >= LIBC_NAMESPACE::time_constants::THURSDAY ||
+          days_left >= 3) {
+        // thursday - sunday are never in the next year, nor are days more than
+        // 3 days before the end.
+        EXPECT_STREQ_LEN(written, buffer, "99");
+        EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "00");
+      } else {
+        // iso_wday is 1, 2 or 3 and days_left is 0, 1, or 2
+        if (iso_wday + days_left <= 3) {
+          EXPECT_STREQ_LEN(written, buffer, "00");
+          EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "01");
+        } else {
+          EXPECT_STREQ_LEN(written, buffer, "99");
+          EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "00");
+        }
+      }
+    }
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_year = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_year = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05g", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00031");
+}
+
+TEST(LlvmLibcStrftimeTest, ISOYear) {
+  // this tests %G, which reads: [tm_year, tm_wday, tm_yday]
+
+  // This stuff is all the same as above, but for brevity I'm not going to
+  // duplicate all the comments explaining exactly how ISO years work. The
+  // general comments are still here though.
+
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // a sunday in the middle of the year. No need to worry about rounding
+  time.tm_wday = 0;
+  time.tm_yday = 100;
+
+  // Test the easy cases
+  for (int i = 1; i < 10000; ++i) {
+    time.tm_year = get_adjusted_year(i);
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%G", &time);
+    char *result = spn.get_padded_num(i, 4);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // also check it handles years with extra digits properly
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  // Test the harder to round cases
+
+  // not a leap year. Not relevant for the start-of-year tests, but it does
+  // matter for the end-of-year tests.
+  time.tm_year = get_adjusted_year(1999);
+
+  // check the first days of the year
+  for (size_t yday = 0; yday < 5; ++yday) {
+    for (size_t iso_wday = 1; iso_wday < 8; ++iso_wday) {
+      // start with monday, to match the ISO week.
+      time.tm_wday = iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      time.tm_yday = yday;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%G", &time);
+
+      if (iso_wday <= LIBC_NAMESPACE::time_constants::THURSDAY || yday >= 4) {
+        // monday - thursday are never in the previous year, nor are the 4th and
+        // after.
+        EXPECT_STREQ_LEN(written, buffer, "1999");
+      } else {
+        // iso_wday is 5, 6, or 7 and yday is 0, 1, or 2.
+        // days_since_thursday is therefor 1, 2, or 3.
+        const size_t days_since_thursday =
+            iso_wday - LIBC_NAMESPACE::time_constants::THURSDAY;
+
+        if (days_since_thursday > yday) {
+          EXPECT_STREQ_LEN(written, buffer, "1998");
+        } else {
+          EXPECT_STREQ_LEN(written, buffer, "1999");
+        }
+      }
+    }
+  }
+
+  // set up all the extra stuff to cover leap years.
+  struct tm time_leap_year;
+  char buffer_leap_year[100];
+  size_t written_leap_year = 0;
+  time_leap_year = time;
+  time_leap_year.tm_year = 100; // 2000 is a leap year.
+
+  // check the last days of the year. Checking 5 to make sure all the leap year
+  // cases are covered as well.
+  for (size_t days_left = 0; days_left < 5; ++days_left) {
+    for (size_t iso_wday = 1; iso_wday < 8; ++iso_wday) {
+      // start with monday, to match the ISO week.
+      time.tm_wday = iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      // subtract 1 from the max yday to handle yday being 0-indexed.
+      time.tm_yday =
+          LIBC_NAMESPACE::time_constants::LAST_DAY_OF_NON_LEAP_YEAR - days_left;
+
+      time_leap_year.tm_wday =
+          iso_wday % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      time_leap_year.tm_yday =
+          LIBC_NAMESPACE::time_constants::LAST_DAY_OF_LEAP_YEAR - days_left;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%G", &time);
+      written_leap_year = LIBC_NAMESPACE::strftime(
+          buffer_leap_year, sizeof(buffer_leap_year), "%G", &time_leap_year);
+
+      if (iso_wday >= 4 || days_left >= 3) {
+        // thursday - sunday are never in the next year, nor are days more than
+        // 3 days before the end.
+        EXPECT_STREQ_LEN(written, buffer, "1999");
+        EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "2000");
+      } else {
+        // iso_wday is 1, 2 or 3 and days_left is 0, 1, or 2
+        if (iso_wday + days_left <= 3) {
+          EXPECT_STREQ_LEN(written, buffer, "2000");
+          EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "2001");
+        } else {
+          EXPECT_STREQ_LEN(written, buffer, "1999");
+          EXPECT_STREQ_LEN(written_leap_year, buffer_leap_year, "2000");
+        }
+      }
+    }
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_year = get_adjusted_year(5);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_year = get_adjusted_year(31);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00031");
+
+  time.tm_year = get_adjusted_year(2001);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2001");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2001");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05G", &time);
+  EXPECT_STREQ_LEN(written, buffer, "02001");
+}
+
+TEST(LlvmLibcStrftimeTest, TwentyFourHour) {
+  // this tests %H, which reads: [tm_hour]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 0; i < 24; ++i) {
+    time.tm_hour = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%H", &time);
+    char *result = spn.get_padded_num(i, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_hour = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_hour = 23;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "23");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "23");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05H", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00023");
+}
+
+TEST(LlvmLibcStrftimeTest, TwelveHour) {
+  // this tests %I, which reads: [tm_hour]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  time.tm_hour = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  // Tests on all the well defined values, except 0 since it was easier to
+  // special case it.
+  for (size_t i = 1; i <= 12; ++i) {
+    char *result = spn.get_padded_num(i, 2);
+
+    time.tm_hour = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%I", &time);
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+
+    // hour + 12 should give the same result
+    time.tm_hour = i + 12;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%I", &time);
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_hour = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_hour = 23;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05I", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00011");
+}
+
+TEST(LlvmLibcStrftimeTest, DayOfYear) {
+  // this tests %j, which reads: [tm_yday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 0; i < LIBC_NAMESPACE::time_constants::DAYS_PER_LEAP_YEAR;
+       ++i) {
+    time.tm_yday = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%j", &time);
+    char *result = spn.get_padded_num(i + 1, 3);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_yday = 5 - 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_yday = 123 - 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05j", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00123");
+}
+
+TEST(LlvmLibcStrftimeTest, MonthOfYear) {
+  // this tests %m, which reads: [tm_mon]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 0; i < LIBC_NAMESPACE::time_constants::MONTHS_PER_YEAR; ++i) {
+    time.tm_mon = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%m", &time);
+    // %m is 1 indexed, so add 1 to the number we're comparing to.
+    char *result = spn.get_padded_num(i + 1, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_mon = 5 - 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_mon = 11 - 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05m", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00011");
+}
+
+TEST(LlvmLibcStrftimeTest, MinuteOfHour) {
+  // this tests %M, which reads: [tm_min]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 0; i < LIBC_NAMESPACE::time_constants::MINUTES_PER_HOUR;
+       ++i) {
+    time.tm_min = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%M", &time);
+    char *result = spn.get_padded_num(i, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_min = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_min = 11;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05M", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00011");
+}
+
+TEST(LlvmLibcStrftimeTest, SecondsSinceEpoch) {
+  // this tests %s, which reads: [tm_year, tm_mon, tm_mday, tm_hour, tm_min,
+  // tm_sec, tm_isdst]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  time.tm_year = get_adjusted_year(1970);
+  // yday is not used, the day of the year is calculated from the month and mday
+  time.tm_mon = 0;
+  time.tm_mday = 1; // the only 1-indexed member
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  time.tm_sec = 1;
+  time.tm_isdst = 0;
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "1");
+
+  // The time as of writing this test
+  time.tm_year = get_adjusted_year(2025);
+  time.tm_mon = 1;
+  time.tm_mday = 4;
+  time.tm_hour = 11;
+  time.tm_min = 8;
+  time.tm_sec = 41;
+  time.tm_isdst = 0;
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%s", &time);
+  // if you run your system's strftime to compare you will likely get a slightly
+  // different result because it's supposed to respect timezones.
+  EXPECT_STREQ_LEN(written, buffer, "1738667321");
+
+  // Thorough testing of the mktime mechanism is done in the mktime tests, so
+  // they aren't duplicated here.
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_year = get_adjusted_year(1970);
+  time.tm_mon = 0;
+  time.tm_mday = 1;
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  time.tm_sec = 5;
+  time.tm_isdst = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_min = 11;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "665");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "665");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05s", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00665");
+}
+
+TEST(LlvmLibcStrftimeTest, SecondOfMinute) {
+  // this tests %S, which reads: [tm_sec]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values
+  for (size_t i = 0; i < LIBC_NAMESPACE::time_constants::SECONDS_PER_MIN; ++i) {
+    time.tm_sec = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%S", &time);
+    char *result = spn.get_padded_num(i, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_sec = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+
+  time.tm_sec = 11;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05S", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00011");
+}
+
+TEST(LlvmLibcStrftimeTest, ISODayOfWeek) {
+  // this tests %u, which reads: [tm_wday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SUNDAY;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%u", &time);
+  EXPECT_STREQ_LEN(written, buffer, "7");
+
+  // Tests on all the well defined values except for sunday, which is 0 in
+  // normal weekdays but 7 here.
+  for (size_t i = LIBC_NAMESPACE::time_constants::MONDAY;
+       i <= LIBC_NAMESPACE::time_constants::SATURDAY; ++i) {
+    time.tm_wday = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%u", &time);
+    char *result = spn.get_padded_num(i, 1);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01u", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02u", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05u", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+}
+
+TEST(LlvmLibcStrftimeTest, WeekOfYearStartingSunday) {
+  // this tests %U, which reads: [tm_year, tm_wday, tm_yday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // setting the year to a leap year, but it doesn't actually matter. This
+  // conversion doesn't end up checking the year at all.
+  time.tm_year = get_adjusted_year(2000);
+
+  const int WEEK_START = LIBC_NAMESPACE::time_constants::SUNDAY;
+
+  for (size_t first_weekday = LIBC_NAMESPACE::time_constants::SUNDAY;
+       first_weekday <= LIBC_NAMESPACE::time_constants::SATURDAY;
+       ++first_weekday) {
+    time.tm_wday = first_weekday;
+    size_t cur_week = 0;
+
+    // iterate through the year, starting on first_weekday.
+    for (size_t yday = 0;
+         yday < LIBC_NAMESPACE::time_constants::DAYS_PER_LEAP_YEAR; ++yday) {
+      time.tm_yday = yday;
+      // If the week just ended, move to the next week.
+      if (time.tm_wday == WEEK_START)
+        ++cur_week;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%U", &time);
+      char *result = spn.get_padded_num(cur_week, 2);
+
+      ASSERT_STREQ(buffer, result);
+      ASSERT_EQ(written, spn.get_str_len());
+
+      // a day has passed, move to the next weekday, looping as necessary.
+      time.tm_wday =
+          (time.tm_wday + 1) % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+    }
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SUNDAY;
+  time.tm_yday = 22;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00004");
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SUNDAY;
+  time.tm_yday = 78;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00012");
+}
+
+TEST(LlvmLibcStrftimeTest, ISOWeekOfYear) {
+  // this tests %V, which reads: [tm_year, tm_wday, tm_yday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  const int starting_year = get_adjusted_year(1999);
+
+  // we're going to check the days from 1999 to 2001 to cover all the
+  // transitions to and from leap years and non-leap years (the start of 1999
+  // and end of 2001 cover the non-leap years).
+  const int days_to_check = // 1096
+      LIBC_NAMESPACE::time_constants::DAYS_PER_NON_LEAP_YEAR +
+      LIBC_NAMESPACE::time_constants::DAYS_PER_LEAP_YEAR +
+      LIBC_NAMESPACE::time_constants::DAYS_PER_NON_LEAP_YEAR;
+
+  const int WEEK_START = LIBC_NAMESPACE::time_constants::MONDAY;
+
+  for (size_t first_weekday = LIBC_NAMESPACE::time_constants::SUNDAY;
+       first_weekday <= LIBC_NAMESPACE::time_constants::SATURDAY;
+       ++first_weekday) {
+    time.tm_year = starting_year;
+    time.tm_wday = first_weekday;
+    time.tm_yday = 0;
+    size_t cur_week = 1;
+    if (first_weekday == LIBC_NAMESPACE::time_constants::SUNDAY ||
+        first_weekday == LIBC_NAMESPACE::time_constants::SATURDAY)
+      cur_week = 52;
+    else if (first_weekday == LIBC_NAMESPACE::time_constants::FRIDAY)
+      cur_week = 53;
+
+    // iterate through the year, starting on first_weekday.
+    for (size_t cur_day = 0; cur_day < days_to_check; ++cur_day) {
+      // If the week just ended, move to the next week.
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%V", &time);
+      char *result = spn.get_padded_num(cur_week, 2);
+
+      ASSERT_STREQ(buffer, result);
+      ASSERT_EQ(written, spn.get_str_len());
+
+      // a day has passed, increment the counters.
+      ++time.tm_yday;
+      if (time.tm_yday ==
+          (time.tm_year == get_adjusted_year(2000)
+               ? LIBC_NAMESPACE::time_constants::DAYS_PER_LEAP_YEAR
+               : LIBC_NAMESPACE::time_constants::DAYS_PER_NON_LEAP_YEAR)) {
+        time.tm_yday = 0;
+        ++time.tm_year;
+      }
+
+      time.tm_wday =
+          (time.tm_wday + 1) % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+      if (time.tm_wday == WEEK_START) {
+        ++cur_week;
+        const int days_left_in_year =
+            (time.tm_year == get_adjusted_year(2000)
+                 ? LIBC_NAMESPACE::time_constants::LAST_DAY_OF_LEAP_YEAR
+                 : LIBC_NAMESPACE::time_constants::LAST_DAY_OF_NON_LEAP_YEAR) -
+            time.tm_yday;
+
+        // if the week we're currently in is in the next year, or if the year
+        // has turned over, reset the week.
+        if (days_left_in_year < 3 || (cur_week > 51 && time.tm_yday < 10))
+          cur_week = 1;
+      }
+    }
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SUNDAY;
+  time.tm_yday = 22;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00004");
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SUNDAY;
+  time.tm_yday = 78;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05U", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00012");
+}
+
+TEST(LlvmLibcStrftimeTest, DayOfWeek) {
+  // this tests %w, which reads: [tm_wday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Tests on all the well defined values.
+  for (size_t i = LIBC_NAMESPACE::time_constants::SUNDAY;
+       i <= LIBC_NAMESPACE::time_constants::SATURDAY; ++i) {
+    time.tm_wday = i;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%w", &time);
+    char *result = spn.get_padded_num(i, 1);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01w", &time);
+  EXPECT_STREQ_LEN(written, buffer, "5");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02w", &time);
+  EXPECT_STREQ_LEN(written, buffer, "05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05w", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00005");
+}
+
+TEST(LlvmLibcStrftimeTest, WeekOfYearStartingMonday) {
+  // this tests %W, which reads: [tm_year, tm_wday, tm_yday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // setting the year to a leap year, but it doesn't actually matter. This
+  // conversion doesn't end up checking the year at all.
+  time.tm_year = get_adjusted_year(2000);
+
+  const int WEEK_START = LIBC_NAMESPACE::time_constants::MONDAY;
+
+  for (size_t first_weekday = LIBC_NAMESPACE::time_constants::SUNDAY;
+       first_weekday <= LIBC_NAMESPACE::time_constants::SATURDAY;
+       ++first_weekday) {
+    time.tm_wday = first_weekday;
+    size_t cur_week = 0;
+
+    // iterate through the year, starting on first_weekday.
+    for (size_t yday = 0;
+         yday < LIBC_NAMESPACE::time_constants::DAYS_PER_LEAP_YEAR; ++yday) {
+      time.tm_yday = yday;
+      // If the week just ended, move to the next week.
+      if (time.tm_wday == WEEK_START)
+        ++cur_week;
+
+      written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%W", &time);
+      char *result = spn.get_padded_num(cur_week, 2);
+
+      ASSERT_STREQ(buffer, result);
+      ASSERT_EQ(written, spn.get_str_len());
+
+      // a day has passed, move to the next weekday, looping as necessary.
+      time.tm_wday =
+          (time.tm_wday + 1) % LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK;
+    }
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = LIBC_NAMESPACE::time_constants::MONDAY;
+  time.tm_yday = 22;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00004");
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::MONDAY;
+  time.tm_yday = 78;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05W", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00012");
+}
+
+TEST(LlvmLibcStrftimeTest, YearOfCentury) {
+  // this tests %y, which reads: [tm_year]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  time.tm_year = get_adjusted_year(2000);
+
+  // iterate through the year, starting on first_weekday.
+  for (size_t year = 1900; year < 2001; ++year) {
+    time.tm_year = get_adjusted_year(year);
+
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%y", &time);
+    char *result = spn.get_padded_num(year % 100, 2);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_year = get_adjusted_year(2004);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00004");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "45");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%02y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "45");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00045");
+}
+
+TEST(LlvmLibcStrftimeTest, FullYearTests) {
+  // this tests %Y, which reads: [tm_year]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+  SimplePaddedNum spn;
+
+  // Test the easy cases
+  for (int i = 1; i < 10000; ++i) {
+    time.tm_year = get_adjusted_year(i);
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+    char *result = spn.get_padded_num(i, 4);
+
+    ASSERT_STREQ(buffer, result);
+    ASSERT_EQ(written, spn.get_str_len());
+  }
+
+  time.tm_year = get_adjusted_year(11900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "11900");
+
+  time.tm_year = get_adjusted_year(0);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000");
+
+  time.tm_year = get_adjusted_year(-1);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  // TODO: should this be what we standardize? Posix doesn't specify what to do
+  // about negative numbers
+  EXPECT_STREQ_LEN(written, buffer, "-001");
+
+  time.tm_year = get_adjusted_year(-9001);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-9001");
+
+  time.tm_year = get_adjusted_year(-10001);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-10001");
+
+  // width tests (with the 0 flag, since the default padding is undefined).
+  time.tm_year = get_adjusted_year(2023);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%04Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "02023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000002023");
+
+  time.tm_year = get_adjusted_year(900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%04Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000000900");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%04Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0000012345");
+
+  time.tm_year = get_adjusted_year(-123);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%04Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-0123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-000000123");
+
+  // '+' flag tests
+  time.tm_year = get_adjusted_year(2023);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+2023");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000002023");
+
+  time.tm_year = get_adjusted_year(900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0900");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000000900");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+000012345");
+
+  time.tm_year = get_adjusted_year(-123);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-0123");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-000000123");
+
+  // Posix specified tests:
+  time.tm_year = get_adjusted_year(1970);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "1970");
+
+  time.tm_year = get_adjusted_year(1970);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "1970");
+
+  time.tm_year = get_adjusted_year(27);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0027");
+
+  time.tm_year = get_adjusted_year(270);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0270");
+
+  time.tm_year = get_adjusted_year(270);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0270");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+4Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345");
+
+  time.tm_year = get_adjusted_year(270);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0270");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+5Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%06Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "012345");
+
+  time.tm_year = get_adjusted_year(12345);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+6Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345");
+
+  time.tm_year = get_adjusted_year(123456);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%08Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00123456");
+
+  time.tm_year = get_adjusted_year(123456);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+8Y", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+0123456");
+}
+
+// String conversions
+
+struct num_str_pair {
+  int num;
+  LIBC_NAMESPACE::cpp::string_view str;
+};
+
+TEST(LlvmLibcStrftimeTest, ShortWeekdayName) {
+  // this tests %a, which reads: [tm_wday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  constexpr LIBC_NAMESPACE::cpp::array<
+      num_str_pair, LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK>
+      WEEKDAY_PAIRS = {{
+          {LIBC_NAMESPACE::time_constants::SUNDAY, "Sun"},
+          {LIBC_NAMESPACE::time_constants::MONDAY, "Mon"},
+          {LIBC_NAMESPACE::time_constants::TUESDAY, "Tue"},
+          {LIBC_NAMESPACE::time_constants::WEDNESDAY, "Wed"},
+          {LIBC_NAMESPACE::time_constants::THURSDAY, "Thu"},
+          {LIBC_NAMESPACE::time_constants::FRIDAY, "Fri"},
+          {LIBC_NAMESPACE::time_constants::SATURDAY, "Sat"},
+      }};
+
+  for (size_t i = 0; i < WEEKDAY_PAIRS.size(); ++i) {
+    time.tm_wday = WEEKDAY_PAIRS[i].num;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%a", &time);
+    EXPECT_STREQ(buffer, WEEKDAY_PAIRS[i].str.data());
+    EXPECT_EQ(written, WEEKDAY_PAIRS[i].str.size());
+  }
+
+  // check invalid weekdays
+  time.tm_wday = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SATURDAY + 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = LIBC_NAMESPACE::time_constants::THURSDAY;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Thu");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Thu");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "       Thu");
+
+  time.tm_wday = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  ?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10a", &time);
+  EXPECT_STREQ_LEN(written, buffer, "         ?");
+}
+
+TEST(LlvmLibcStrftimeTest, FullWeekdayName) {
+  // this tests %a, which reads: [tm_wday]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  constexpr LIBC_NAMESPACE::cpp::array<
+      num_str_pair, LIBC_NAMESPACE::time_constants::DAYS_PER_WEEK>
+      WEEKDAY_PAIRS = {{
+          {LIBC_NAMESPACE::time_constants::SUNDAY, "Sunday"},
+          {LIBC_NAMESPACE::time_constants::MONDAY, "Monday"},
+          {LIBC_NAMESPACE::time_constants::TUESDAY, "Tuesday"},
+          {LIBC_NAMESPACE::time_constants::WEDNESDAY, "Wednesday"},
+          {LIBC_NAMESPACE::time_constants::THURSDAY, "Thursday"},
+          {LIBC_NAMESPACE::time_constants::FRIDAY, "Friday"},
+          {LIBC_NAMESPACE::time_constants::SATURDAY, "Saturday"},
+      }};
+
+  for (size_t i = 0; i < WEEKDAY_PAIRS.size(); ++i) {
+    time.tm_wday = WEEKDAY_PAIRS[i].num;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%A", &time);
+    EXPECT_STREQ(buffer, WEEKDAY_PAIRS[i].str.data());
+    EXPECT_EQ(written, WEEKDAY_PAIRS[i].str.size());
+  }
+
+  // check invalid weekdays
+  time.tm_wday = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  time.tm_wday = LIBC_NAMESPACE::time_constants::SATURDAY + 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_wday = LIBC_NAMESPACE::time_constants::THURSDAY;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Thursday");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Thursday");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  Thursday");
+
+  time.tm_wday = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  ?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10A", &time);
+  EXPECT_STREQ_LEN(written, buffer, "         ?");
+}
+
+TEST(LlvmLibcStrftimeTest, ShortMonthName) {
+  // this tests %b, which reads: [tm_mon]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  constexpr LIBC_NAMESPACE::cpp::array<
+      num_str_pair, LIBC_NAMESPACE::time_constants::MONTHS_PER_YEAR>
+      MONTH_PAIRS = {{
+          {LIBC_NAMESPACE::time_constants::JANUARY, "Jan"},
+          {LIBC_NAMESPACE::time_constants::FEBRUARY, "Feb"},
+          {LIBC_NAMESPACE::time_constants::MARCH, "Mar"},
+          {LIBC_NAMESPACE::time_constants::APRIL, "Apr"},
+          {LIBC_NAMESPACE::time_constants::MAY, "May"},
+          {LIBC_NAMESPACE::time_constants::JUNE, "Jun"},
+          {LIBC_NAMESPACE::time_constants::JULY, "Jul"},
+          {LIBC_NAMESPACE::time_constants::AUGUST, "Aug"},
+          {LIBC_NAMESPACE::time_constants::SEPTEMBER, "Sep"},
+          {LIBC_NAMESPACE::time_constants::OCTOBER, "Oct"},
+          {LIBC_NAMESPACE::time_constants::NOVEMBER, "Nov"},
+          {LIBC_NAMESPACE::time_constants::DECEMBER, "Dec"},
+      }};
+
+  for (size_t i = 0; i < MONTH_PAIRS.size(); ++i) {
+    time.tm_mon = MONTH_PAIRS[i].num;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%b", &time);
+    EXPECT_STREQ(buffer, MONTH_PAIRS[i].str.data());
+    EXPECT_EQ(written, MONTH_PAIRS[i].str.size());
+  }
+
+  // check invalid weekdays
+  time.tm_mon = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  time.tm_mon = LIBC_NAMESPACE::time_constants::DECEMBER + 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  // Also test %h, which is identical to %b
+  time.tm_mon = LIBC_NAMESPACE::time_constants::OCTOBER;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%h", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Oct");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_mon = LIBC_NAMESPACE::time_constants::OCTOBER;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Oct");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Oct");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "       Oct");
+
+  time.tm_mon = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  ?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10b", &time);
+  EXPECT_STREQ_LEN(written, buffer, "         ?");
+}
+
+TEST(LlvmLibcStrftimeTest, FullMonthName) {
+  // this tests %B, which reads: [tm_mon]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  constexpr LIBC_NAMESPACE::cpp::array<
+      num_str_pair, LIBC_NAMESPACE::time_constants::MONTHS_PER_YEAR>
+      MONTH_PAIRS = {{
+          {LIBC_NAMESPACE::time_constants::JANUARY, "January"},
+          {LIBC_NAMESPACE::time_constants::FEBRUARY, "February"},
+          {LIBC_NAMESPACE::time_constants::MARCH, "March"},
+          {LIBC_NAMESPACE::time_constants::APRIL, "April"},
+          {LIBC_NAMESPACE::time_constants::MAY, "May"},
+          {LIBC_NAMESPACE::time_constants::JUNE, "June"},
+          {LIBC_NAMESPACE::time_constants::JULY, "July"},
+          {LIBC_NAMESPACE::time_constants::AUGUST, "August"},
+          {LIBC_NAMESPACE::time_constants::SEPTEMBER, "September"},
+          {LIBC_NAMESPACE::time_constants::OCTOBER, "October"},
+          {LIBC_NAMESPACE::time_constants::NOVEMBER, "November"},
+          {LIBC_NAMESPACE::time_constants::DECEMBER, "December"},
+      }};
+
+  for (size_t i = 0; i < MONTH_PAIRS.size(); ++i) {
+    time.tm_mon = MONTH_PAIRS[i].num;
+    written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%B", &time);
+    EXPECT_STREQ(buffer, MONTH_PAIRS[i].str.data());
+    EXPECT_EQ(written, MONTH_PAIRS[i].str.size());
+  }
+
+  // check invalid weekdays
+  time.tm_mon = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  time.tm_mon = LIBC_NAMESPACE::time_constants::DECEMBER + 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_mon = LIBC_NAMESPACE::time_constants::OCTOBER;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "October");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "October");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "   October");
+
+  time.tm_mon = -1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%3B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  ?");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10B", &time);
+  EXPECT_STREQ_LEN(written, buffer, "         ?");
+}
+
+TEST(LlvmLibcStrftimeTest, AM_PM) {
+  // this tests %p, which reads: [tm_hour]
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  time.tm_hour = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "AM");
+
+  time.tm_hour = 6;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "AM");
+
+  time.tm_hour = 12;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "PM");
+
+  time.tm_hour = 18;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "PM");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  time.tm_hour = 6;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "AM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%2p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "AM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "        AM");
+
+  time.tm_hour = 18;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "PM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%2p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "PM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%10p", &time);
+  EXPECT_STREQ_LEN(written, buffer, "        PM");
+}
+
+TEST(LlvmLibcStrftimeTest, DateFormatUS) {
+  // this tests %D, which reads: [tm_mon, tm_mday, tm_year]
+  // This is equivalent to "%m/%d/%y"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of %m, %d, and %y have their own tests, so this test won't cover all
+  // values of those. Instead it will do basic tests and focus on the specific
+  // padding behavior.
+
+  time.tm_mon = 0;  // 0 indexed, so 0 is january
+  time.tm_mday = 2; // 1 indexed, so 2 is the 2nd
+  time.tm_year = get_adjusted_year(1903);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "01/02/03");
+
+  time.tm_mon = 11;
+  time.tm_mday = 31;
+  time.tm_year = get_adjusted_year(1999);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12/31/99");
+
+  // The day LLVM-libc started
+  time.tm_mon = 8;
+  time.tm_mday = 16;
+  time.tm_year = get_adjusted_year(2019);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "09/16/19");
+
+  // %x is equivalent to %D in default locale
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%x", &time);
+  EXPECT_STREQ_LEN(written, buffer, "09/16/19");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  // Padding is handled in the same way as POSIX describes for %F
+  time.tm_mon = 1;
+  time.tm_mday = 5;
+  time.tm_year = get_adjusted_year(2025);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2/05/25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%07D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2/05/25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0002/05/25");
+
+  time.tm_mon = 9;
+  time.tm_mday = 2;
+  time.tm_year = get_adjusted_year(2000);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10/02/00");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%07D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10/02/00");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010D", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0010/02/00");
+}
+
+TEST(LlvmLibcStrftimeTest, DateFormatISO) {
+  // this tests %F, which reads: [tm_year, tm_mon, tm_mday]
+  // This is equivalent to "%Y-%m-%d"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of %Y, %m, and %d have their own tests, so this test won't cover all
+  // values of those. Instead it will do basic tests and focus on the specific
+  // padding behavior.
+
+  time.tm_year = get_adjusted_year(1901);
+  time.tm_mon = 1;  // 0 indexed, so 1 is february
+  time.tm_mday = 3; // 1 indexed, so 2 is the 2nd
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "1901-02-03");
+
+  time.tm_year = get_adjusted_year(1999);
+  time.tm_mon = 11;
+  time.tm_mday = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "1999-12-31");
+
+  time.tm_year = get_adjusted_year(2019);
+  time.tm_mon = 8;
+  time.tm_mday = 16;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2019-09-16");
+
+  time.tm_year = get_adjusted_year(123);
+  time.tm_mon = 3;
+  time.tm_mday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0123-04-05");
+
+  time.tm_year = get_adjusted_year(67);
+  time.tm_mon = 7;
+  time.tm_mday = 9;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0067-08-09");
+
+  time.tm_year = get_adjusted_year(2);
+  time.tm_mon = 1;
+  time.tm_mday = 14;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0002-02-14");
+
+  time.tm_year = get_adjusted_year(-543);
+  time.tm_mon = 1;
+  time.tm_mday = 1;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-543-02-01");
+
+  // padding tests
+  time.tm_year = get_adjusted_year(2025);
+  time.tm_mon = 1;
+  time.tm_mday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2025-02-05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2025-02-05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%012F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "002025-02-05");
+
+  time.tm_year = get_adjusted_year(12345);
+  time.tm_mon = 11;
+  time.tm_mday = 25;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345-12-25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12345-12-25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%012F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "012345-12-25");
+
+  time.tm_year = get_adjusted_year(476);
+  time.tm_mon = 8;
+  time.tm_mday = 4;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "476-09-04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0476-09-04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%012F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "000476-09-04");
+
+  time.tm_year = get_adjusted_year(-100);
+  time.tm_mon = 9;
+  time.tm_mday = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-100-10-31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-100-10-31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%012F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-00100-10-31");
+
+  // '+' flag tests
+  time.tm_year = get_adjusted_year(2025);
+  time.tm_mon = 1;
+  time.tm_mday = 5;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2025-02-05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "2025-02-05");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+12F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+02025-02-05");
+
+  time.tm_year = get_adjusted_year(12345);
+  time.tm_mon = 11;
+  time.tm_mday = 25;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345-12-25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345-12-25");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+12F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+12345-12-25");
+
+  time.tm_year = get_adjusted_year(476);
+  time.tm_mon = 8;
+  time.tm_mday = 4;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "476-09-04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0476-09-04");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+12F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "+00476-09-04");
+
+  time.tm_year = get_adjusted_year(-100);
+  time.tm_mon = 9;
+  time.tm_mday = 31;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+1F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-100-10-31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+10F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-100-10-31");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%+12F", &time);
+  EXPECT_STREQ_LEN(written, buffer, "-00100-10-31");
+}
+
+TEST(LlvmLibcStrftimeTest, TimeFormatAMPM) {
+  // this tests %r, which reads: [tm_hour, tm_min, tm_sec]
+  // This is equivalent to "%I:%M:%S %p"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of %I, %M, %S, and %p have their own tests, so this test won't cover
+  // all values of those. Instead it will do basic tests and focus on the
+  // specific padding behavior.
+
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  time.tm_sec = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "12:00:00 AM");
+
+  time.tm_hour = 1;
+  time.tm_min = 23;
+  time.tm_sec = 45;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "01:23:45 AM");
+
+  time.tm_hour = 18;
+  time.tm_min = 6;
+  time.tm_sec = 2;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "06:06:02 PM");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  // Padding is handled in the same way as POSIX describes for %F
+  time.tm_hour = 10;
+  time.tm_min = 9;
+  time.tm_sec = 59;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09:59 AM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%011r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09:59 AM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%013r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0010:09:59 AM");
+
+  time.tm_hour = 16;
+  time.tm_min = 56;
+  time.tm_sec = 9;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4:56:09 PM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%011r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04:56:09 PM");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%013r", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0004:56:09 PM");
+}
+
+TEST(LlvmLibcStrftimeTest, TimeFormatMinute) {
+  // this tests %R, which reads: [tm_hour, tm_min]
+  // This is equivalent to "%H:%M"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of %H and %M have their own tests, so this test won't cover
+  // all values of those. Instead it will do basic tests and focus on the
+  // specific padding behavior.
+
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00:00");
+
+  time.tm_hour = 1;
+  time.tm_min = 23;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "01:23");
+
+  time.tm_hour = 18;
+  time.tm_min = 6;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "18:06");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  // Padding is handled in the same way as POSIX describes for %F
+  time.tm_hour = 10;
+  time.tm_min = 9;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%07R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0010:09");
+
+  time.tm_hour = 4;
+  time.tm_min = 56;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4:56");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%05R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04:56");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%07R", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0004:56");
+}
+
+TEST(LlvmLibcStrftimeTest, TimeFormatSecond) {
+  // this tests %T, which reads: [tm_hour, tm_min, tm_sec]
+  // This is equivalent to "%H:%M:%S"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of %H, %M, and %S have their own tests, so this test won't cover
+  // all values of those. Instead it will do basic tests and focus on the
+  // specific padding behavior.
+
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  time.tm_sec = 0;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "00:00:00");
+
+  time.tm_hour = 1;
+  time.tm_min = 23;
+  time.tm_sec = 45;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "01:23:45");
+
+  time.tm_hour = 18;
+  time.tm_min = 6;
+  time.tm_sec = 2;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "18:06:02");
+
+  // %X is equivalent to %T in default locale
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%X", &time);
+  EXPECT_STREQ_LEN(written, buffer, "18:06:02");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  // Padding is handled in the same way as POSIX describes for %F
+  time.tm_hour = 10;
+  time.tm_min = 9;
+  time.tm_sec = 59;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09:59");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%08T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "10:09:59");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0010:09:59");
+
+  time.tm_hour = 4;
+  time.tm_min = 56;
+  time.tm_sec = 9;
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%01T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "4:56:09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%08T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "04:56:09");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%010T", &time);
+  EXPECT_STREQ_LEN(written, buffer, "0004:56:09");
+}
+
+TEST(LlvmLibcStrftimeTest, TimeFormatFullDateTime) {
+  // this tests %c, which reads:
+  //  [tm_wday, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_year]
+  // This is equivalent to "%a %b %e %T %Y"
+  struct tm time;
+  char buffer[100];
+  size_t written = 0;
+
+  // each of the individual conversions have their own tests, so this test won't
+  // cover all values of those. Instead it will do basic tests and focus on the
+  // specific padding behavior.
+
+  time.tm_wday = 0;
+  time.tm_mon = 0;
+  time.tm_mday = 1;
+  time.tm_hour = 0;
+  time.tm_min = 0;
+  time.tm_sec = 0;
+  time.tm_year = get_adjusted_year(1900);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Sun Jan  1 00:00:00 1900");
+
+  time.tm_wday = 3;
+  time.tm_mon = 5;
+  time.tm_mday = 15;
+  time.tm_hour = 14;
+  time.tm_min = 13;
+  time.tm_sec = 12;
+  time.tm_year = get_adjusted_year(2011);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Wed Jun 15 14:13:12 2011");
+
+  // now, as of the writing of this test
+  time.tm_wday = 4;
+  time.tm_mon = 1;
+  time.tm_mday = 6;
+  time.tm_hour = 12;
+  time.tm_min = 57;
+  time.tm_sec = 50;
+  time.tm_year = get_adjusted_year(2025);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Thu Feb  6 12:57:50 2025");
+
+  time.tm_wday = 5;
+  time.tm_mon = 8;
+  time.tm_mday = 4;
+  time.tm_hour = 16;
+  time.tm_min = 57;
+  time.tm_sec = 18;
+  time.tm_year = get_adjusted_year(476);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Fri Sep  4 16:57:18 0476");
+
+  // padding is technically undefined for this conversion, but we support it, so
+  // we need to test it.
+  // Padding is handled in the same way as POSIX describes for %F.
+  // This includes assuming the trailing conversions are of a fixed width, which
+  // isn't true for years. For simplicity, we format years (%Y) to be padded to
+  // 4 digits when possible, which means padding will work as expected for years
+  // -999 to 9999. If the current year is large enough to trigger this bug,
+  // congrats on making it another ~8000 years!
+  time.tm_wday = 5;
+  time.tm_mon = 8;
+  time.tm_mday = 4;
+  time.tm_hour = 16;
+  time.tm_min = 57;
+  time.tm_sec = 18;
+  time.tm_year = get_adjusted_year(476);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Fri Sep  4 16:57:18 0476");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%24c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Fri Sep  4 16:57:18 0476");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%26c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  Fri Sep  4 16:57:18 0476");
+
+  // '0' flag has no effect on the string part of the conversion, only the
+  // numbers, and the only one of those that defaults to spaces is day of month.
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%026c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  Fri Sep 04 16:57:18 0476");
+
+  time.tm_wday = 3;
+  time.tm_mon = 5;
+  time.tm_mday = 15;
+  time.tm_hour = 14;
+  time.tm_min = 13;
+  time.tm_sec = 12;
+  time.tm_year = get_adjusted_year(2011);
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%1c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Wed Jun 15 14:13:12 2011");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%24c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "Wed Jun 15 14:13:12 2011");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%26c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  Wed Jun 15 14:13:12 2011");
+
+  written = LIBC_NAMESPACE::strftime(buffer, sizeof(buffer), "%026c", &time);
+  EXPECT_STREQ_LEN(written, buffer, "  Wed Jun 15 14:13:12 2011");
+}
+
+// TODO: implement %z and %Z when timezones are implemented.
+//  TEST(LlvmLibcStrftimeTest, TimezoneOffset) {
+//    // this tests %z, which reads: [tm_isdst, tm_zone]
+//    struct tm time;
+//    char buffer[100];
+//    size_t written = 0;
+//    SimplePaddedNum spn;
+//  }
diff --git a/test/src/unistd/getsid_test.cpp b/test/src/unistd/getsid_test.cpp
new file mode 100644
index 0000000..b3e8d54
--- /dev/null
+++ b/test/src/unistd/getsid_test.cpp
@@ -0,0 +1,21 @@
+//===-- Unittests for getsid ----------------------------------------------===//
+//
+// 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/unistd/getsid.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcGetPidTest, GetCurrSID) {
+  pid_t sid = LIBC_NAMESPACE::getsid(0);
+  ASSERT_NE(sid, -1);
+  ASSERT_ERRNO_SUCCESS();
+
+  pid_t nonexist_sid = LIBC_NAMESPACE::getsid(-1);
+  ASSERT_EQ(nonexist_sid, -1);
+  ASSERT_ERRNO_FAILURE();
+}