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(); +}