[libcxx-commits] [libcxx] 3eb4f16 - [libc++][chrono] Implements formatter year.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Wed Oct 5 08:01:28 PDT 2022


Author: Mark de Wever
Date: 2022-10-05T17:01:19+02:00
New Revision: 3eb4f16b446ed4619d5c2ddeaec06040c83f189c

URL: https://github.com/llvm/llvm-project/commit/3eb4f16b446ed4619d5c2ddeaec06040c83f189c
DIFF: https://github.com/llvm/llvm-project/commit/3eb4f16b446ed4619d5c2ddeaec06040c83f189c.diff

LOG: [libc++][chrono] Implements formatter year.

Partially implements:
- P1361 Integration of chrono with text formatting

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D133663

Added: 
    libcxx/test/std/time/time.cal/time.cal.year/time.cal.year.nonmembers/ostream.pass.cpp
    libcxx/test/std/time/time.syn/formatter.year.pass.cpp

Modified: 
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/__chrono/convert_to_tm.h
    libcxx/include/__chrono/formatter.h
    libcxx/include/__chrono/ostream.h
    libcxx/include/chrono
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx2b.csv
    libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp
    libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp
    libcxx/utils/ci/run-buildbot

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index dffb39384c9dd..4e7c27d95e024 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -10,7 +10,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local-time-format-t<Duration>``",A ``<chrono>`` implementation,Not assigned,,,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::day``",,Mark de Wever,|Complete|, Clang 16
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::month``",,Mark de Wever,|In Progress|,
-`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year``",,Mark de Wever,|In Progress|,
+`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year``",,Mark de Wever,|Complete|, Clang 16
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::weekday``",,Mark de Wever,|In Progress|,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::weekday_indexed``",,Mark de Wever,|In Progress|,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::weekday_last``",,Mark de Wever,|In Progress|,

diff  --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h
index 169d97b34d112..6c22b5a1277b3 100644
--- a/libcxx/include/__chrono/convert_to_tm.h
+++ b/libcxx/include/__chrono/convert_to_tm.h
@@ -11,6 +11,7 @@
 #define _LIBCPP___CHRONO_CONVERT_TO_TM_H
 
 #include <__chrono/day.h>
+#include <__chrono/year.h>
 #include <__concepts/same_as.h>
 #include <__config>
 
@@ -33,6 +34,8 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoCalendarTimePoint& __valu
 
   if constexpr (same_as<_ChronoCalendarTimePoint, chrono::day>)
     __result.tm_mday = static_cast<unsigned>(__value);
+  else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::year>)
+    __result.tm_year = static_cast<int>(__value) - 1900;
   else
     static_assert(sizeof(_ChronoCalendarTimePoint) == 0, "Add the missing type specialization");
 

diff  --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index 47c79201e62ef..1dad41d3004f5 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -13,12 +13,17 @@
 #include <__chrono/convert_to_tm.h>
 #include <__chrono/day.h>
 #include <__chrono/parser_std_format_spec.h>
+#include <__chrono/statically_widen.h>
+#include <__chrono/year.h>
 #include <__config>
 #include <__format/concepts.h>
+#include <__format/format_functions.h>
 #include <__format/format_parse_context.h>
 #include <__format/formatter.h>
 #include <__format/formatter_output.h>
 #include <__format/parser_std_format_spec.h>
+#include <__memory/addressof.h>
+#include <cmath>
 #include <ctime>
 #include <sstream>
 #include <string>
@@ -52,6 +57,34 @@ namespace __formatter {
 ///
 /// When no chrono-specs are provided it uses the stream formatter.
 
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI void __format_year(int __year, basic_stringstream<_CharT>& __sstr) {
+  if (__year < 0) {
+    __sstr << _CharT('-');
+    __year = -__year;
+  }
+
+  // TODO FMT Write an issue
+  //   If the result has less than four digits it is zero-padded with 0 to two digits.
+  // is less -> has less
+  // left-padded -> zero-padded, otherwise the proper value would be 000-0.
+
+  // Note according to the wording it should be left padded, which is odd.
+  __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:04}"), __year);
+}
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI void __format_century(int __year, basic_stringstream<_CharT>& __sstr) {
+  // TODO FMT Write an issue
+  // [tab:time.format.spec]
+  //   %C The year divided by 100 using floored division. If the result is a
+  //   single decimal digit, it is prefixed with 0.
+
+  bool __negative = __year < 0;
+  int __century   = (__year - (99 * __negative)) / 100; // floored division
+  __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __century);
+}
+
 template <class _CharT, class _Tp>
 _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
     const _Tp& __value, basic_stringstream<_CharT>& __sstr, basic_string_view<_CharT> __chrono_specs) {
@@ -74,7 +107,59 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
         __sstr << *__it;
         break;
 
+      case _CharT('C'): {
+        // strftime's output is only defined in the range [00, 99].
+        int __year = __t.tm_year + 1900;
+        if (__year < 1000 || __year > 9999)
+          __formatter::__format_century(__year, __sstr);
+        else
+          __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
+      } break;
+
+      // Unlike time_put and strftime the formatting library requires %Y
+      //
+      // [tab:time.format.spec]
+      //   The year as a decimal number. If the result is less than four digits
+      //   it is left-padded with 0 to four digits.
+      //
+      // This means years in the range (-1000, 1000) need manual formatting.
+      // It's unclear whether %EY needs the same treatment. For example the
+      // Japanese EY contains the era name and year. This is zero-padded to 2
+      // digits in time_put (note that older glibc versions didn't do
+      // padding.) However most eras won't reach 100 years, let alone 1000.
+      // So padding to 4 digits seems unwanted for Japanese.
+      //
+      // The same applies to %Ex since that too depends on the era.
+      //
+      // %x the locale's date representation is currently doesn't handle the
+      // zero-padding too.
+      //
+      // The 4 digits can be implemented better at a later time. On POSIX
+      // systems the required information can be extracted by nl_langinfo
+      // https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
+      //
+      // Note since year < -1000 is expected to be rare it uses the more
+      // expensive year routine.
+      //
+      // TODO FMT evaluate the comment above.
+
+#  if defined(__GLIBC__) || defined(_AIX)
+      case _CharT('y'):
+        // Glibc fails for negative values, AIX for positive values too.
+        __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), (std::abs(__t.tm_year + 1900)) % 100);
+        break;
+#  endif // defined(__GLIBC__) || defined(_AIX)
+
+      case _CharT('Y'): {
+        int __year = __t.tm_year + 1900;
+        if (__year < 1000)
+          __formatter::__format_year(__year, __sstr);
+        else
+          __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
+      } break;
+
       case _CharT('O'):
+      case _CharT('E'):
         ++__it;
         [[fallthrough]];
       default:
@@ -146,7 +231,19 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::day, _
   }
 };
 
-#endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
+template <__fmt_char_type _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::year, _CharT>
+    : public __formatter_chrono<_CharT> {
+public:
+  using _Base = __formatter_chrono<_CharT>;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+    return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__year);
+  }
+};
+
+#endif // if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
 
 _LIBCPP_END_NAMESPACE_STD
 

diff  --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h
index 7d6796452fdd4..b6dcc5035ef66 100644
--- a/libcxx/include/__chrono/ostream.h
+++ b/libcxx/include/__chrono/ostream.h
@@ -12,6 +12,7 @@
 
 #include <__chrono/day.h>
 #include <__chrono/statically_widen.h>
+#include <__chrono/year.h>
 #include <__config>
 #include <__format/format_functions.h>
 #include <ostream>
@@ -40,6 +41,13 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) {
               : std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02} is not a valid day"), static_cast<unsigned>(__d)));
 }
 
+template <class _CharT, class _Traits>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
+operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y) {
+  return __os << (__y.ok() ? std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:%Y}"), __y)
+                           : std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:%Y} is not a valid year"), __y));
+}
+
 } // namespace chrono
 
 #endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)

diff  --git a/libcxx/include/chrono b/libcxx/include/chrono
index ce7c621e25e68..eefb26d7aa9d4 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -355,6 +355,9 @@ constexpr year  operator+(const year&  x, const years& y) noexcept;
 constexpr year  operator+(const years& x, const year&  y) noexcept;
 constexpr year  operator-(const year&  x, const years& y) noexcept;
 constexpr years operator-(const year&  x, const year&  y) noexcept;
+template<class charT, class traits>
+  basic_ostream<charT, traits>&
+    operator<<(basic_ostream<charT, traits>& os, const year& y);
 
 // 25.8.6, class weekday    // C++20
 class weekday;
@@ -620,6 +623,7 @@ bool operator>=(const time_zone& x, const time_zone& y) noexcept;
 
 namespace std {
   template<class charT> struct formatter<chrono::day, charT>;                     // C++20
+  template<class charT> struct formatter<chrono::year, charT>;                    // C++20
 } // namespace std
 
 namespace chrono {

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 346e40b24eb57..b58d3d00ed4be 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -106,6 +106,7 @@ charconv type_traits
 chrono array
 chrono bit
 chrono charconv
+chrono cmath
 chrono compare
 chrono concepts
 chrono cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx2b.csv b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
index e1e57466a6833..d052fb41f9ce8 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx2b.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
@@ -86,6 +86,7 @@ charconv type_traits
 chrono array
 chrono bit
 chrono charconv
+chrono cmath
 chrono compare
 chrono concepts
 chrono cstddef

diff  --git a/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp b/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp
index 177900b181cd4..ccf7c58c06955 100644
--- a/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp
@@ -143,7 +143,7 @@ void test_P1361() {
 
   assert_is_formattable<std::chrono::day, CharT>();
   assert_is_not_formattable<std::chrono::month, CharT>();
-  assert_is_not_formattable<std::chrono::year, CharT>();
+  assert_is_formattable<std::chrono::year, CharT>();
 
   assert_is_not_formattable<std::chrono::weekday, CharT>();
   assert_is_not_formattable<std::chrono::weekday_indexed, CharT>();

diff  --git a/libcxx/test/std/time/time.cal/time.cal.year/time.cal.year.nonmembers/ostream.pass.cpp b/libcxx/test/std/time/time.cal/time.cal.year/time.cal.year.nonmembers/ostream.pass.cpp
new file mode 100644
index 0000000000000..c979fc2874ca7
--- /dev/null
+++ b/libcxx/test/std/time/time.cal/time.cal.year/time.cal.year.nonmembers/ostream.pass.cpp
@@ -0,0 +1,90 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate Windows issues.
+// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
+
+// REQUIRES: locale.fr_FR.UTF-8
+// REQUIRES: locale.ja_JP.UTF-8
+
+// <chrono>
+// class year;
+
+// template<class charT, class traits>
+//   basic_ostream<charT, traits>&
+//     operator<<(basic_ostream<charT, traits>& os, const year& year);
+
+#include <chrono>
+#include <cassert>
+#include <sstream>
+
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "test_macros.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class CharT>
+static std::basic_string<CharT> stream_c_locale(std::chrono::year year) {
+  std::basic_stringstream<CharT> sstr;
+  sstr << year;
+  return sstr.str();
+}
+
+template <class CharT>
+static std::basic_string<CharT> stream_fr_FR_locale(std::chrono::year year) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_fr_FR_UTF_8);
+  sstr.imbue(locale);
+  sstr << year;
+  return sstr.str();
+}
+
+template <class CharT>
+static std::basic_string<CharT> stream_ja_JP_locale(std::chrono::year year) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_ja_JP_UTF_8);
+  sstr.imbue(locale);
+  sstr << year;
+  return sstr.str();
+}
+
+template <class CharT>
+static void test() {
+  assert(stream_c_locale<CharT>(std::chrono::year{-32'768}) == SV("-32768 is not a valid year"));
+  assert(stream_c_locale<CharT>(std::chrono::year{-32'767}) == SV("-32767"));
+  assert(stream_c_locale<CharT>(std::chrono::year{0}) == SV("0000"));
+  assert(stream_c_locale<CharT>(std::chrono::year{1970}) == SV("1970"));
+  assert(stream_c_locale<CharT>(std::chrono::year{32'767}) == SV("32767"));
+
+  assert(stream_fr_FR_locale<CharT>(std::chrono::year{-32'768}) == SV("-32768 is not a valid year"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::year{-32'767}) == SV("-32767"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::year{0}) == SV("0000"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::year{1970}) == SV("1970"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::year{32'767}) == SV("32767"));
+
+  assert(stream_ja_JP_locale<CharT>(std::chrono::year{-32'768}) == SV("-32768 is not a valid year"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::year{-32'767}) == SV("-32767"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::year{0}) == SV("0000"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::year{1970}) == SV("1970"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::year{32'767}) == SV("32767"));
+}
+
+int main(int, char**) {
+  test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/time/time.syn/formatter.year.pass.cpp b/libcxx/test/std/time/time.syn/formatter.year.pass.cpp
new file mode 100644
index 0000000000000..cbb2dcd135444
--- /dev/null
+++ b/libcxx/test/std/time/time.syn/formatter.year.pass.cpp
@@ -0,0 +1,308 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate Windows issues.
+// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
+
+// REQUIRES: locale.fr_FR.UTF-8
+// REQUIRES: locale.ja_JP.UTF-8
+
+// <chrono>
+
+// template<class charT> struct formatter<chrono::year, charT>;
+
+#include <chrono>
+#include <format>
+
+#include <cassert>
+#include <concepts>
+#include <locale>
+#include <iostream>
+#include <type_traits>
+
+#include "formatter_tests.h"
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "string_literal.h"
+#include "test_macros.h"
+
+template <class CharT>
+static void test_no_chrono_specs() {
+  check(SV("-32767"), SV("{}"), std::chrono::year{-32'767});
+  check(SV("-1000"), SV("{}"), std::chrono::year{-1000});
+  check(SV("-0100"), SV("{}"), std::chrono::year{-100});
+  check(SV("-0010"), SV("{}"), std::chrono::year{-10});
+  check(SV("-0001"), SV("{}"), std::chrono::year{-1});
+  check(SV("0000"), SV("{}"), std::chrono::year{0});
+  check(SV("0001"), SV("{}"), std::chrono::year{1});
+  check(SV("0010"), SV("{}"), std::chrono::year{10});
+  check(SV("0100"), SV("{}"), std::chrono::year{100});
+  check(SV("1000"), SV("{}"), std::chrono::year{1000});
+  check(SV("32727"), SV("{}"), std::chrono::year{32'727});
+
+  // Invalid year
+  check(SV("-32768 is not a valid year"), SV("{}"), std::chrono::year{-32'768});
+  check(SV("-32768 is not a valid year"), SV("{}"), std::chrono::year{32'768});
+}
+
+template <class CharT>
+static void test_valid_values() {
+  constexpr std::basic_string_view<CharT> fmt = SV(
+      "{:"
+      "%%C='%C'%t"
+      "%%EC='%EC'%t"
+      "%%y='%y'%t"
+      "%%Ey='%Ey'%t"
+      "%%Oy='%Oy'%t"
+      "%%Y='%Y'%t"
+      "%%EY='%EY'%t"
+      "%n}");
+  constexpr std::basic_string_view<CharT> lfmt = SV(
+      "{:L"
+      "%%C='%C'%t"
+      "%%EC='%EC'%t"
+      "%%y='%y'%t"
+      "%%Ey='%Ey'%t"
+      "%%Oy='%Oy'%t"
+      "%%Y='%Y'%t"
+      "%%EY='%EY'%t"
+      "%n}");
+
+  const std::locale loc(LOCALE_ja_JP_UTF_8);
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+
+  // Non localized output using C-locale
+  check(SV("%C='00'\t"
+#if defined(__APPLE__)
+           "%EC='00'\t"
+#else
+           "%EC='0'\t"
+#endif
+           "%y='00'\t"
+           "%Ey='00'\t"
+           "%Oy='00'\t"
+           "%Y='0000'\t"
+#if defined(__APPLE__)
+           "%EY='0000'\t"
+#elif defined(_AIX)
+           "%EY=''\t"
+#else
+           "%EY='0'\t"
+#endif
+           "\n"),
+        fmt,
+        std::chrono::year{0});
+
+  check(SV("%C='19'\t"
+           "%EC='19'\t"
+           "%y='70'\t"
+           "%Ey='70'\t"
+           "%Oy='70'\t"
+           "%Y='1970'\t"
+           "%EY='1970'\t"
+           "\n"),
+        fmt,
+        std::chrono::year{1970});
+
+  check(SV("%C='20'\t"
+           "%EC='20'\t"
+           "%y='38'\t"
+           "%Ey='38'\t"
+           "%Oy='38'\t"
+           "%Y='2038'\t"
+           "%EY='2038'\t"
+           "\n"),
+        fmt,
+        std::chrono::year{2038});
+
+  // Use the global locale (fr_FR)
+  check(SV("%C='00'\t"
+#if defined(__APPLE__)
+           "%EC='00'\t"
+#else
+           "%EC='0'\t"
+#endif
+           "%y='00'\t"
+           "%Ey='00'\t"
+           "%Oy='00'\t"
+           "%Y='0000'\t"
+#if defined(__APPLE__)
+           "%EY='0000'\t"
+#elif defined(_AIX)
+           "%EY=''\t"
+#else
+           "%EY='0'\t"
+#endif
+           "\n"),
+        lfmt,
+        std::chrono::year{0});
+
+  check(SV("%C='19'\t"
+           "%EC='19'\t"
+           "%y='70'\t"
+           "%Ey='70'\t"
+           "%Oy='70'\t"
+           "%Y='1970'\t"
+           "%EY='1970'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{1970});
+
+  check(SV("%C='20'\t"
+           "%EC='20'\t"
+           "%y='38'\t"
+           "%Ey='38'\t"
+           "%Oy='38'\t"
+           "%Y='2038'\t"
+           "%EY='2038'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{2038});
+
+  // Use supplied locale (ja_JP). This locale has a 
diff erent alternate.
+#if defined(__APPLE__) || defined(_AIX)
+
+  check(SV("%C='00'\t"
+#  if defined(__APPLE__)
+           "%EC='00'\t"
+#  else
+           "%EC='0'\t"
+#  endif
+           "%y='00'\t"
+           "%Ey='00'\t"
+           "%Oy='00'\t"
+           "%Y='0000'\t"
+#  if defined(_AIX)
+           "%EY=''\t"
+#  else
+           "%EY='0000'\t"
+#  endif
+           "\n"),
+        lfmt,
+        std::chrono::year{0});
+
+  check(SV("%C='19'\t"
+           "%EC='19'\t"
+           "%y='70'\t"
+           "%Ey='70'\t"
+           "%Oy='70'\t"
+           "%Y='1970'\t"
+           "%EY='1970'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{1970});
+
+  check(SV("%C='20'\t"
+           "%EC='20'\t"
+           "%y='38'\t"
+           "%Ey='38'\t"
+           "%Oy='38'\t"
+           "%Y='2038'\t"
+           "%EY='2038'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{2038});
+
+#else // defined(__APPLE__) || defined(_AIX)
+  check(loc,
+        SV("%C='00'\t"
+           "%EC='紀元前'\t"
+           "%y='00'\t"
+// https://sourceware.org/bugzilla/show_bug.cgi?id=23758
+#  if defined(__GLIBC__) && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 29
+           "%Ey='1'\t"
+#  else
+           "%Ey='01'\t"
+#  endif
+           "%Oy='〇'\t"
+           "%Y='0000'\t"
+// https://sourceware.org/bugzilla/show_bug.cgi?id=23758
+#  if defined(__GLIBC__) && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 29
+           "%EY='紀元前1年'\t"
+#  else
+           "%EY='紀元前01年'\t"
+#  endif
+           "\n"),
+        lfmt,
+        std::chrono::year{0});
+
+  check(loc,
+        SV("%C='19'\t"
+           "%EC='昭和'\t"
+           "%y='70'\t"
+           "%Ey='45'\t"
+           "%Oy='七十'\t"
+           "%Y='1970'\t"
+           "%EY='昭和45年'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{1970});
+
+  // Note this test will fail if the Reiwa era ends before 2038.
+  check(loc,
+        SV("%C='20'\t"
+           "%EC='令和'\t"
+           "%y='38'\t"
+           "%Ey='20'\t"
+           "%Oy='三十八'\t"
+           "%Y='2038'\t"
+           "%EY='令和20年'\t"
+           "\n"),
+        lfmt,
+        std::chrono::year{2038});
+#endif // defined(__APPLE__) || defined(_AIX)
+
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_padding() {
+  constexpr std::basic_string_view<CharT> fmt = SV("{:%%C='%C'%t%%y='%y'%t%%Y='%Y'%t%n}");
+
+  check(SV("%C='-100'\t%y='99'\t%Y='-9999'\t\n"), fmt, std::chrono::year{-9'999});
+  check(SV("%C='-10'\t%y='99'\t%Y='-0999'\t\n"), fmt, std::chrono::year{-999});
+  check(SV("%C='-1'\t%y='99'\t%Y='-0099'\t\n"), fmt, std::chrono::year{-99});
+  check(SV("%C='-1'\t%y='09'\t%Y='-0009'\t\n"), fmt, std::chrono::year{-9});
+  check(SV("%C='00'\t%y='00'\t%Y='0000'\t\n"), fmt, std::chrono::year{0});
+  check(SV("%C='00'\t%y='09'\t%Y='0009'\t\n"), fmt, std::chrono::year{9});
+  check(SV("%C='00'\t%y='99'\t%Y='0099'\t\n"), fmt, std::chrono::year{99});
+  check(SV("%C='09'\t%y='99'\t%Y='0999'\t\n"), fmt, std::chrono::year{999});
+  check(SV("%C='99'\t%y='99'\t%Y='9999'\t\n"), fmt, std::chrono::year{9'999});
+  check(SV("%C='100'\t%y='00'\t%Y='10000'\t\n"), fmt, std::chrono::year{10'000});
+}
+
+template <class CharT>
+static void test() {
+  test_no_chrono_specs<CharT>();
+  test_valid_values<CharT>();
+  test_padding<CharT>();
+  check_invalid_types<CharT>(
+      {SV("C"), SV("y"), SV("Y"), SV("EC"), SV("Ey"), SV("EY"), SV("Oy")}, std::chrono::year{1970});
+
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), std::chrono::year{1970});
+  check_exception("The chrono-specs contains a '{'", SV("{:%%{"), std::chrono::year{1970});
+  check_exception("End of input while parsing the modifier chrono conversion-spec", SV("{:%"), std::chrono::year{1970});
+  check_exception("End of input while parsing the modifier E", SV("{:%E"), std::chrono::year{1970});
+  check_exception("End of input while parsing the modifier O", SV("{:%O"), std::chrono::year{1970});
+
+  // Precision not allowed
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), std::chrono::year{1970});
+}
+
+int main(int, char**) {
+  test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp
index 8beaeb2cfe472..766f0422fbaaa 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp
@@ -185,7 +185,7 @@ void test_P1361() {
 
   assert_formatter_is_enabled<std::chrono::day, CharT>();
   assert_formatter_is_disabled<std::chrono::month, CharT>();
-  assert_formatter_is_disabled<std::chrono::year, CharT>();
+  assert_formatter_is_enabled<std::chrono::year, CharT>();
 
   assert_formatter_is_disabled<std::chrono::weekday, CharT>();
   assert_formatter_is_disabled<std::chrono::weekday_indexed, CharT>();

diff  --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index 8a306801d7e02..d32bf5df2f36e 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -190,7 +190,7 @@ check-generated-output)
     ! grep -rn '[^ -~]' libcxx/include libcxx/src libcxx/test libcxx/benchmarks \
            --exclude '*.dat' \
            --exclude 'format_tests.h' \
-           --exclude 'formatter.day.pass.cpp' \
+           --exclude 'formatter.*.pass.cpp' \
            --exclude 'grep.pass.cpp' \
            --exclude 'locale-specific_form.pass.cpp' \
            --exclude 'std_format_spec_string_unicode.bench.cpp' \


        


More information about the libcxx-commits mailing list