//===-- 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 int BUFF_LEN = 16; char buff[BUFF_LEN]; size_t cur_len; // length of string currently in buff void clear_buff() { // TODO: builtin_memset? for (int 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 raw(num); auto str = raw.view(); int leading_zeroes = static_cast(min_width - raw.size()); int i = 0; for (; static_cast(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 (int 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 (int 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 (int 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 (int yday = 0; yday < 5; ++yday) { for (int 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 int 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 (int days_left = 0; days_left < 5; ++days_left) { for (int 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 (int yday = 0; yday < 5; ++yday) { for (int 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 int 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 (int days_left = 0; days_left < 5; ++days_left) { for (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int first_weekday = LIBC_NAMESPACE::time_constants::SUNDAY; first_weekday <= LIBC_NAMESPACE::time_constants::SATURDAY; ++first_weekday) { time.tm_wday = first_weekday; int cur_week = 0; // iterate through the year, starting on first_weekday. for (int 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 (int 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; int 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 (int 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 (int first_weekday = LIBC_NAMESPACE::time_constants::SUNDAY; first_weekday <= LIBC_NAMESPACE::time_constants::SATURDAY; ++first_weekday) { time.tm_wday = first_weekday; int cur_week = 0; // iterate through the year, starting on first_weekday. for (int 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 (int 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; // } TEST(LlvmLibcStrftimeTest, BufferTooSmall) { struct tm time; char tiny_buffer[1]; time.tm_year = get_adjusted_year(2025); time.tm_mon = 10; time.tm_mday = 24; size_t written = LIBC_NAMESPACE::strftime(tiny_buffer, sizeof(tiny_buffer), "%F", &time); EXPECT_EQ(written, size_t{0}); char small_buffer[10]; // The string "2025-11-24" is 10 chars, // so strftime needs 10 + 1 bytes to write the string and the null terminator. written = LIBC_NAMESPACE::strftime(small_buffer, sizeof(small_buffer), "%F", &time); EXPECT_EQ(written, size_t{0}); }