/ externals / fmt / test / xchar-test.cc
xchar-test.cc
  1  // Formatting library for C++ - formatting library tests
  2  //
  3  // Copyright (c) 2012 - present, Victor Zverovich
  4  // All rights reserved.
  5  //
  6  // For the license information refer to format.h.
  7  
  8  #include "fmt/xchar.h"
  9  
 10  #include <algorithm>
 11  #include <complex>
 12  #include <cwchar>
 13  #include <vector>
 14  
 15  #include "fmt/chrono.h"
 16  #include "fmt/color.h"
 17  #include "fmt/ostream.h"
 18  #include "fmt/ranges.h"
 19  #include "fmt/std.h"
 20  #include "gtest-extra.h"  // Contains
 21  #include "util.h"         // get_locale
 22  
 23  using fmt::detail::max_value;
 24  using testing::Contains;
 25  
 26  #if defined(__MINGW32__) && !defined(_UCRT)
 27  // Only C89 conversion specifiers when using MSVCRT instead of UCRT
 28  #  define FMT_HAS_C99_STRFTIME 0
 29  #else
 30  #  define FMT_HAS_C99_STRFTIME 1
 31  #endif
 32  
 33  struct non_string {};
 34  
 35  template <typename T> class is_string_test : public testing::Test {};
 36  
 37  using string_char_types = testing::Types<char, wchar_t, char16_t, char32_t>;
 38  TYPED_TEST_SUITE(is_string_test, string_char_types);
 39  
 40  template <typename Char>
 41  struct derived_from_string_view : fmt::basic_string_view<Char> {};
 42  
 43  TYPED_TEST(is_string_test, is_string) {
 44    EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value);
 45    EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value);
 46    EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value);
 47    EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value);
 48    EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value);
 49    EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value);
 50    EXPECT_TRUE(
 51        fmt::detail::is_string<derived_from_string_view<TypeParam>>::value);
 52    using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
 53    EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
 54                fmt::detail::is_string<fmt_string_view>::value);
 55    EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
 56  }
 57  
 58  // std::is_constructible is broken in MSVC until version 2015.
 59  #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
 60  struct explicitly_convertible_to_wstring_view {
 61    explicit operator fmt::wstring_view() const { return L"foo"; }
 62  };
 63  
 64  TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
 65    // Types explicitly convertible to wstring_view are not formattable by
 66    // default because it may introduce ODR violations.
 67    static_assert(
 68        !fmt::is_formattable<explicitly_convertible_to_wstring_view>::value, "");
 69  }
 70  #endif
 71  
 72  TEST(xchar_test, format) {
 73    EXPECT_EQ(L"42", fmt::format(L"{}", 42));
 74    EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
 75    EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
 76    EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
 77    EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
 78    EXPECT_EQ(L"true", fmt::format(L"{}", true));
 79    EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
 80    EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
 81    EXPECT_EQ(L"Cyrillic letter \x42e",
 82              fmt::format(L"Cyrillic letter {}", L'\x42e'));
 83    EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
 84  }
 85  
 86  TEST(xchar_test, is_formattable) {
 87    static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
 88  }
 89  
 90  TEST(xchar_test, compile_time_string) {
 91    EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
 92  #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
 93    EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
 94  #endif
 95  }
 96  
 97  #if FMT_CPLUSPLUS > 201103L
 98  struct custom_char {
 99    int value;
100    custom_char() = default;
101  
102    template <typename T>
103    constexpr custom_char(T val) : value(static_cast<int>(val)) {}
104  
105    operator char() const {
106      return value <= 0xff ? static_cast<char>(value) : '\0';
107    }
108  };
109  
110  auto to_ascii(custom_char c) -> char { return c; }
111  
112  FMT_BEGIN_NAMESPACE
113  template <> struct is_char<custom_char> : std::true_type {};
114  FMT_END_NAMESPACE
115  
116  TEST(xchar_test, format_custom_char) {
117    const custom_char format[] = {'{', '}', 0};
118    auto result = fmt::format(format, custom_char('x'));
119    EXPECT_EQ(result.size(), 1);
120    EXPECT_EQ(result[0], custom_char('x'));
121  }
122  #endif
123  
124  // Convert a char8_t string to std::string. Otherwise GTest will insist on
125  // inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
126  template <typename S> std::string from_u8str(const S& str) {
127    return std::string(str.begin(), str.end());
128  }
129  
130  TEST(xchar_test, format_utf8_precision) {
131    using str_type = std::basic_string<fmt::detail::char8_type>;
132    auto format =
133        str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}"));
134    auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>(
135        u8"caf\u00e9s"));  // cafés
136    auto result = fmt::format(format, str);
137    EXPECT_EQ(fmt::detail::compute_width(result), 4);
138    EXPECT_EQ(result.size(), 5);
139    EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
140  }
141  
142  TEST(xchar_test, format_to) {
143    auto buf = std::vector<wchar_t>();
144    fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
145    EXPECT_STREQ(buf.data(), L"42");
146  }
147  
148  TEST(xchar_test, vformat_to) {
149    auto args = fmt::make_wformat_args(42);
150    auto w = std::wstring();
151    fmt::vformat_to(std::back_inserter(w), L"{}", args);
152    EXPECT_EQ(L"42", w);
153  }
154  
155  namespace test {
156  struct struct_as_wstring_view {};
157  auto format_as(struct_as_wstring_view) -> fmt::wstring_view { return L"foo"; }
158  }  // namespace test
159  
160  TEST(xchar_test, format_as) {
161    EXPECT_EQ(fmt::format(L"{}", test::struct_as_wstring_view()), L"foo");
162  }
163  
164  TEST(format_test, wide_format_to_n) {
165    wchar_t buffer[4];
166    buffer[3] = L'x';
167    auto result = fmt::format_to_n(buffer, 3, L"{}", 12345);
168    EXPECT_EQ(5u, result.size);
169    EXPECT_EQ(buffer + 3, result.out);
170    EXPECT_EQ(L"123x", fmt::wstring_view(buffer, 4));
171    buffer[0] = L'x';
172    buffer[1] = L'x';
173    buffer[2] = L'x';
174    result = fmt::format_to_n(buffer, 3, L"{}", L'A');
175    EXPECT_EQ(1u, result.size);
176    EXPECT_EQ(buffer + 1, result.out);
177    EXPECT_EQ(L"Axxx", fmt::wstring_view(buffer, 4));
178    result = fmt::format_to_n(buffer, 3, L"{}{} ", L'B', L'C');
179    EXPECT_EQ(3u, result.size);
180    EXPECT_EQ(buffer + 3, result.out);
181    EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
182  }
183  
184  #if FMT_USE_USER_DEFINED_LITERALS
185  TEST(xchar_test, named_arg_udl) {
186    using namespace fmt::literals;
187    auto udl_a =
188        fmt::format(L"{first}{second}{first}{third}", L"first"_a = L"abra",
189                    L"second"_a = L"cad", L"third"_a = 99);
190    EXPECT_EQ(
191        fmt::format(L"{first}{second}{first}{third}", fmt::arg(L"first", L"abra"),
192                    fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
193        udl_a);
194  }
195  #endif  // FMT_USE_USER_DEFINED_LITERALS
196  
197  TEST(xchar_test, print) {
198    // Check that the wide print overload compiles.
199    if (fmt::detail::const_check(false)) {
200      fmt::print(L"test");
201      fmt::println(L"test");
202    }
203  }
204  
205  TEST(xchar_test, join) {
206    int v[3] = {1, 2, 3};
207    EXPECT_EQ(fmt::format(L"({})", fmt::join(v, v + 3, L", ")), L"(1, 2, 3)");
208    auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f);
209    EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
210  }
211  
212  enum streamable_enum {};
213  
214  std::wostream& operator<<(std::wostream& os, streamable_enum) {
215    return os << L"streamable_enum";
216  }
217  
218  namespace fmt {
219  template <>
220  struct formatter<streamable_enum, wchar_t> : basic_ostream_formatter<wchar_t> {
221  };
222  }  // namespace fmt
223  
224  enum unstreamable_enum {};
225  auto format_as(unstreamable_enum e) -> int { return e; }
226  
227  TEST(xchar_test, enum) {
228    EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
229    EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
230  }
231  
232  struct streamable_and_unformattable {};
233  
234  auto operator<<(std::wostream& os, streamable_and_unformattable)
235      -> std::wostream& {
236    return os << L"foo";
237  }
238  
239  TEST(xchar_test, streamed) {
240    EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
241    EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
242              L"foo");
243  }
244  
245  TEST(xchar_test, sign_not_truncated) {
246    wchar_t format_str[] = {
247        L'{', L':',
248        '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
249    EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
250  }
251  
252  TEST(xchar_test, chrono) {
253    auto tm = std::tm();
254    tm.tm_year = 116;
255    tm.tm_mon = 3;
256    tm.tm_mday = 25;
257    tm.tm_hour = 11;
258    tm.tm_min = 22;
259    tm.tm_sec = 33;
260    EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
261              "The date is 2016-04-25 11:22:33.");
262    EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
263    EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
264    EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
265  }
266  
267  std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
268                               std::locale* locptr = nullptr) {
269    auto loc = locptr ? *locptr : std::locale::classic();
270    auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
271    std::wostringstream os;
272    os.imbue(loc);
273    facet.put(os, os, L' ', timeptr, format.c_str(),
274              format.c_str() + format.size());
275  #ifdef _WIN32
276    // Workaround a bug in older versions of Universal CRT.
277    auto str = os.str();
278    if (str == L"-0000") str = L"+0000";
279    return str;
280  #else
281    return os.str();
282  #endif
283  }
284  
285  TEST(chrono_test_wchar, time_point) {
286    auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
287        std::chrono::system_clock::now());
288  
289    std::vector<std::wstring> spec_list = {
290        L"%%",  L"%n",  L"%t",  L"%Y",  L"%EY", L"%y",  L"%Oy", L"%Ey", L"%C",
291        L"%EC", L"%G",  L"%g",  L"%b",  L"%h",  L"%B",  L"%m",  L"%Om", L"%U",
292        L"%OU", L"%W",  L"%OW", L"%V",  L"%OV", L"%j",  L"%d",  L"%Od", L"%e",
293        L"%Oe", L"%a",  L"%A",  L"%w",  L"%Ow", L"%u",  L"%Ou", L"%H",  L"%OH",
294        L"%I",  L"%OI", L"%M",  L"%OM", L"%S",  L"%OS", L"%x",  L"%Ex", L"%X",
295        L"%EX", L"%D",  L"%F",  L"%R",  L"%T",  L"%p"};
296  #ifndef _WIN32
297    // Disabled on Windows, because these formats is not consistent among
298    // platforms.
299    spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
300  #elif !FMT_HAS_C99_STRFTIME
301    // Only C89 conversion specifiers when using MSVCRT instead of UCRT
302    spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
303                 L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
304                 L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
305  #endif
306    spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
307  
308    for (const auto& spec : spec_list) {
309      auto t = std::chrono::system_clock::to_time_t(t1);
310      auto tm = *std::gmtime(&t);
311  
312      auto sys_output = system_wcsftime(spec, &tm);
313  
314      auto fmt_spec = fmt::format(L"{{:{}}}", spec);
315      EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
316      EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
317    }
318  
319    // Timezone formatters tests makes sense for localtime.
320  #if FMT_HAS_C99_STRFTIME
321    spec_list = {L"%z", L"%Z"};
322  #else
323    spec_list = {L"%Z"};
324  #endif
325    for (const auto& spec : spec_list) {
326      auto t = std::chrono::system_clock::to_time_t(t1);
327      auto tm = *std::localtime(&t);
328  
329      auto sys_output = system_wcsftime(spec, &tm);
330  
331      auto fmt_spec = fmt::format(L"{{:{}}}", spec);
332      EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
333  
334      if (spec == L"%z") {
335        sys_output.insert(sys_output.end() - 2, 1, L':');
336        EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
337        EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
338      }
339    }
340  
341    // Separate tests for UTC, since std::time_put can use local time and ignoring
342    // the timezone in std::tm (if it presents on platform).
343    if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
344      auto t = std::chrono::system_clock::to_time_t(t1);
345      auto tm = *std::gmtime(&t);
346  
347      std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
348      EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
349      EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
350    }
351  
352    if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
353      auto t = std::chrono::system_clock::to_time_t(t1);
354      auto tm = *std::gmtime(&t);
355  
356      EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
357      EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
358  
359      EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
360      EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
361  
362      EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
363      EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
364    }
365  }
366  
367  TEST(xchar_test, color) {
368    EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
369              L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
370  }
371  
372  TEST(xchar_test, ostream) {
373  #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
374    {
375      std::wostringstream wos;
376      fmt::print(wos, L"Don't {}!", L"panic");
377      EXPECT_EQ(wos.str(), L"Don't panic!");
378    }
379  
380    {
381      std::wostringstream wos;
382      fmt::println(wos, L"Don't {}!", L"panic");
383      EXPECT_EQ(wos.str(), L"Don't panic!\n");
384    }
385  #endif
386  }
387  
388  TEST(xchar_test, format_map) {
389    auto m = std::map<std::wstring, int>{{L"one", 1}, {L"t\"wo", 2}};
390    EXPECT_EQ(fmt::format(L"{}", m), L"{\"one\": 1, \"t\\\"wo\": 2}");
391  }
392  
393  TEST(xchar_test, escape_string) {
394    using vec = std::vector<std::wstring>;
395    EXPECT_EQ(fmt::format(L"{}", vec{L"\n\r\t\"\\"}), L"[\"\\n\\r\\t\\\"\\\\\"]");
396    EXPECT_EQ(fmt::format(L"{}", vec{L"понедельник"}), L"[\"понедельник\"]");
397  }
398  
399  TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
400  
401  #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
402  
403  template <typename Char> struct numpunct : std::numpunct<Char> {
404   protected:
405    Char do_decimal_point() const override { return '?'; }
406    std::string do_grouping() const override { return "\03"; }
407    Char do_thousands_sep() const override { return '~'; }
408  };
409  
410  template <typename Char> struct no_grouping : std::numpunct<Char> {
411   protected:
412    Char do_decimal_point() const override { return '.'; }
413    std::string do_grouping() const override { return ""; }
414    Char do_thousands_sep() const override { return ','; }
415  };
416  
417  template <typename Char> struct special_grouping : std::numpunct<Char> {
418   protected:
419    Char do_decimal_point() const override { return '.'; }
420    std::string do_grouping() const override { return "\03\02"; }
421    Char do_thousands_sep() const override { return ','; }
422  };
423  
424  template <typename Char> struct small_grouping : std::numpunct<Char> {
425   protected:
426    Char do_decimal_point() const override { return '.'; }
427    std::string do_grouping() const override { return "\01"; }
428    Char do_thousands_sep() const override { return ','; }
429  };
430  
431  TEST(locale_test, localized_double) {
432    auto loc = std::locale(std::locale(), new numpunct<char>());
433    EXPECT_EQ(fmt::format(loc, "{:L}", 1.23), "1?23");
434    EXPECT_EQ(fmt::format(loc, "{:Lf}", 1.23), "1?230000");
435    EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
436    EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
437    EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), "   1~230");
438    EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), "       0?100000");
439    EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), "       1?000000");
440    EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), "   1~000?000000");
441  }
442  
443  TEST(locale_test, format) {
444    auto loc = std::locale(std::locale(), new numpunct<char>());
445    EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567));
446    EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
447    EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
448    EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
449    auto n = 1234567;
450    EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n)));
451    auto s = std::string();
452    fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
453    EXPECT_EQ("1~234~567", s);
454  
455    auto no_grouping_loc = std::locale(std::locale(), new no_grouping<char>());
456    EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567));
457  
458    auto special_grouping_loc =
459        std::locale(std::locale(), new special_grouping<char>());
460    EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678));
461    EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345));
462  
463    auto small_grouping_loc =
464        std::locale(std::locale(), new small_grouping<char>());
465    EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
466              fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>()));
467  }
468  
469  TEST(locale_test, format_detault_align) {
470    auto loc = std::locale({}, new special_grouping<char>());
471    EXPECT_EQ("  12,345", fmt::format(loc, "{:8L}", 12345));
472  }
473  
474  TEST(locale_test, format_plus) {
475    auto loc = std::locale({}, new special_grouping<char>());
476    EXPECT_EQ("+100", fmt::format(loc, "{:+L}", 100));
477  }
478  
479  TEST(locale_test, wformat) {
480    auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
481    EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
482    EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
483    int n = 1234567;
484    EXPECT_EQ(L"1~234~567",
485              fmt::vformat(loc, L"{:L}", fmt::make_wformat_args(n)));
486    EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
487  
488    auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
489    EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567));
490  
491    auto special_grouping_loc =
492        std::locale(std::locale(), new special_grouping<wchar_t>());
493    EXPECT_EQ(L"1,23,45,678",
494              fmt::format(special_grouping_loc, L"{:L}", 12345678));
495  
496    auto small_grouping_loc =
497        std::locale(std::locale(), new small_grouping<wchar_t>());
498    EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
499              fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
500  }
501  
502  TEST(locale_test, int_formatter) {
503    auto loc = std::locale(std::locale(), new special_grouping<char>());
504    auto f = fmt::formatter<int>();
505    auto parse_ctx = fmt::format_parse_context("L");
506    f.parse(parse_ctx);
507    auto buf = fmt::memory_buffer();
508    fmt::basic_format_context<fmt::appender, char> format_ctx(
509        fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
510    f.format(12345, format_ctx);
511    EXPECT_EQ(fmt::to_string(buf), "12,345");
512  }
513  
514  FMT_BEGIN_NAMESPACE
515  template <class charT> struct formatter<std::complex<double>, charT> {
516   private:
517    detail::dynamic_format_specs<char> specs_;
518  
519   public:
520    FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
521        basic_format_parse_context<charT>& ctx) {
522      auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
523                                    detail::type::float_type);
524      detail::parse_float_type_spec(specs_, detail::error_handler());
525      return end;
526    }
527  
528    template <class FormatContext>
529    typename FormatContext::iterator format(const std::complex<double>& c,
530                                            FormatContext& ctx) {
531      detail::handle_dynamic_spec<detail::precision_checker>(
532          specs_.precision, specs_.precision_ref, ctx);
533      auto specs = std::string();
534      if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision);
535      if (specs_.type == presentation_type::fixed_lower) specs += 'f';
536      auto real = fmt::format(ctx.locale().template get<std::locale>(),
537                              fmt::runtime("{:" + specs + "}"), c.real());
538      auto imag = fmt::format(ctx.locale().template get<std::locale>(),
539                              fmt::runtime("{:" + specs + "}"), c.imag());
540      auto fill_align_width = std::string();
541      if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width);
542      return format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
543                       c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
544                                     : fmt::format("{}i", imag));
545    }
546  };
547  FMT_END_NAMESPACE
548  
549  TEST(locale_test, complex) {
550    std::string s = fmt::format("{}", std::complex<double>(1, 2));
551    EXPECT_EQ(s, "(1+2i)");
552    EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)");
553    EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "  (1+2i)");
554  }
555  
556  TEST(locale_test, chrono_weekday) {
557    auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251");
558    auto loc_old = std::locale::global(loc);
559    auto mon = fmt::weekday(1);
560    EXPECT_EQ(fmt::format(L"{}", mon), L"Mon");
561    if (loc != std::locale::classic()) {
562      // {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"}
563      // {L"пн", L"Пн", L"пнд", L"Пнд"}
564      EXPECT_THAT(
565          (std::vector<std::wstring>{L"\x43F\x43D", L"\x41F\x43D",
566                                     L"\x43F\x43D\x434", L"\x41F\x43D\x434"}),
567          Contains(fmt::format(loc, L"{:L}", mon)));
568    }
569    std::locale::global(loc_old);
570  }
571  
572  TEST(locale_test, sign) {
573    EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
574  }
575  
576  TEST(std_test_xchar, optional) {
577  #  ifdef __cpp_lib_optional
578    EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
579    EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
580              L"optional(\"wide string\")");
581  #  endif
582  }
583  
584  #endif  // FMT_STATIC_THOUSANDS_SEPARATOR