1//
2// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
3// Copyright (c) 2021-2022 Alexander Grund
4//
5// Distributed under the Boost Software License, Version 1.0.
6// https://www.boost.org/LICENSE_1_0.txt
7
8#include <boost/locale/date_time.hpp>
9#include <boost/locale/encoding_utf.hpp>
10#include <boost/locale/format.hpp>
11#include <boost/locale/formatting.hpp>
12#include <boost/locale/generator.hpp>
13#include <boost/locale/localization_backend.hpp>
14#include <cstdint>
15#include <ctime>
16#include <iomanip>
17#include <iostream>
18#include <limits>
19#include <memory>
20#include <sstream>
21
22#include "boostLocale/test/tools.hpp"
23#include "boostLocale/test/unit_test.hpp"
24
25const std::string test_locale_name = "en_US";
26std::string message_path = "./";
27
28#ifdef BOOST_LOCALE_WITH_ICU
29# include <unicode/datefmt.h>
30# include <unicode/numfmt.h>
31# include <unicode/timezone.h>
32# include <unicode/uversion.h>
33# define BOOST_LOCALE_ICU_VERSION (U_ICU_VERSION_MAJOR_NUM * 100 + U_ICU_VERSION_MINOR_NUM)
34# define BOOST_LOCALE_ICU_VERSION_EXACT (BOOST_LOCALE_ICU_VERSION * 100 + U_ICU_VERSION_PATCHLEVEL_NUM)
35
36const icu::Locale& get_icu_test_locale()
37{
38 static icu::Locale locale = icu::Locale::createCanonical(name: test_locale_name.c_str());
39 return locale;
40}
41
42std::string from_icu_string(const icu::UnicodeString& str)
43{
44 return boost::locale::conv::utf_to_utf<char>(begin: str.getBuffer(), end: str.getBuffer() + str.length());
45}
46#else
47# define BOOST_LOCALE_ICU_VERSION 0
48# define BOOST_LOCALE_ICU_VERSION_EXACT 0
49#endif
50
51// Currency style changes between ICU versions, so get "real" value from ICU
52#if BOOST_LOCALE_ICU_VERSION >= 402
53
54std::string get_icu_currency_iso(const double value)
55{
56# if BOOST_LOCALE_ICU_VERSION >= 408
57 auto styleIso = UNUM_CURRENCY_ISO;
58# else
59 auto styleIso = icu::NumberFormat::kIsoCurrencyStyle;
60# endif
61 UErrorCode err = U_ZERO_ERROR;
62 std::unique_ptr<icu::NumberFormat> fmt(icu::NumberFormat::createInstance(desiredLocale: get_icu_test_locale(), style: styleIso, errorCode&: err));
63 TEST_REQUIRE(U_SUCCESS(err) && fmt.get());
64
65 icu::UnicodeString tmp;
66 return from_icu_string(str: fmt->format(number: value, appendTo&: tmp));
67}
68
69#endif
70
71using format_style_t = std::ios_base&(std::ios_base&);
72
73#ifdef BOOST_LOCALE_WITH_ICU
74std::string get_icu_gmt_name(icu::TimeZone::EDisplayType style)
75{
76 icu::UnicodeString tmp;
77 return from_icu_string(str: icu::TimeZone::getGMT()->getDisplayName(inDaylight: false, style, locale: get_icu_test_locale(), result&: tmp));
78}
79
80// This changes between ICU versions, e.g. "GMT" or "Greenwich Mean Time"
81const std::string icu_full_gmt_name = get_icu_gmt_name(style: icu::TimeZone::EDisplayType::LONG);
82
83std::string get_ICU_time(format_style_t style, const time_t ts, const char* tz = nullptr)
84{
85 using icu::DateFormat;
86 DateFormat::EStyle icu_style = DateFormat::kDefault;
87 namespace as = boost::locale::as;
88 if(style == as::time_short)
89 icu_style = DateFormat::kShort;
90 else if(style == as::time_medium)
91 icu_style = DateFormat::kMedium;
92 else if(style == as::time_long)
93 icu_style = DateFormat::kLong;
94 else if(style == as::time_full)
95 icu_style = DateFormat::kFull;
96 std::unique_ptr<icu::DateFormat> fmt(icu::DateFormat::createTimeInstance(style: icu_style, aLocale: get_icu_test_locale()));
97 if(!tz)
98 fmt->setTimeZone(*icu::TimeZone::getGMT());
99 else
100 fmt->adoptTimeZone(zoneToAdopt: icu::TimeZone::createTimeZone(ID: icu::UnicodeString::fromUTF8(utf8: tz)));
101 icu::UnicodeString s;
102 return from_icu_string(str: fmt->format(date: ts * 1000., appendTo&: s));
103}
104
105std::string get_ICU_date(format_style_t style, const time_t ts)
106{
107 using icu::DateFormat;
108 DateFormat::EStyle icu_style = DateFormat::kDefault;
109 namespace as = boost::locale::as;
110 if(style == as::date_short)
111 icu_style = DateFormat::kShort;
112 else if(style == as::date_medium)
113 icu_style = DateFormat::kMedium;
114 else if(style == as::date_long)
115 icu_style = DateFormat::kLong;
116 else if(style == as::date_full)
117 icu_style = DateFormat::kFull;
118 std::unique_ptr<icu::DateFormat> fmt(icu::DateFormat::createDateInstance(style: icu_style, aLocale: get_icu_test_locale()));
119 fmt->setTimeZone(*icu::TimeZone::getGMT());
120 icu::UnicodeString s;
121 return from_icu_string(str: fmt->format(date: ts * 1000., appendTo&: s));
122}
123
124std::string get_ICU_datetime(format_style_t style, const time_t ts)
125{
126 using icu::DateFormat;
127 DateFormat::EStyle icu_style = DateFormat::kDefault;
128 namespace as = boost::locale::as;
129 if(style == as::time_short)
130 icu_style = DateFormat::kShort;
131 else if(style == as::time_medium)
132 icu_style = DateFormat::kMedium;
133 else if(style == as::time_long)
134 icu_style = DateFormat::kLong;
135 else if(style == as::time_full)
136 icu_style = DateFormat::kFull;
137 std::unique_ptr<icu::DateFormat> fmt(
138 icu::DateFormat::createDateTimeInstance(dateStyle: icu_style, timeStyle: icu_style, aLocale: get_icu_test_locale()));
139 fmt->setTimeZone(*icu::TimeZone::getGMT());
140 icu::UnicodeString s;
141 return from_icu_string(str: fmt->format(date: ts * 1000., appendTo&: s));
142}
143
144#else
145const std::string icu_full_gmt_name;
146// clang-format off
147std::string get_ICU_time(...){ return ""; } // LCOV_EXCL_LINE
148std::string get_ICU_datetime(...){ return ""; } // LCOV_EXCL_LINE
149std::string get_ICU_date(...){ return ""; } // LCOV_EXCL_LINE
150// clang-format on
151#endif
152
153using namespace boost::locale;
154
155template<typename CharType, typename T>
156void test_fmt_impl(std::basic_ostringstream<CharType>& ss,
157 const T& value,
158 const std::basic_string<CharType>& expected,
159 int line)
160{
161 ss << value;
162 test_eq_impl(ss.str(), expected, "", line);
163}
164
165template<typename T, typename CharType>
166void test_parse_impl(std::basic_istringstream<CharType>& ss, const T& expected, int line)
167{
168 T v;
169 ss >> v >> std::ws;
170 test_eq_impl(v, expected, "v == expected", line);
171 test_eq_impl(ss.eof(), true, "ss.eof()", line);
172}
173
174template<typename T, typename CharType>
175void test_parse_at_impl(std::basic_istringstream<CharType>& ss, const T& expected, int line)
176{
177 T v;
178 CharType c_at;
179 ss >> v >> std::skipws >> c_at;
180 test_eq_impl(v, expected, "v == expected", line);
181 test_eq_impl(c_at, '@', "c_at == @", line);
182}
183
184template<typename T, typename CharType>
185void test_parse_fail_impl(std::basic_istringstream<CharType>& ss, int line)
186{
187 T v;
188 ss >> v;
189 test_eq_impl(ss.fail(), true, "ss.fail()", line);
190}
191
192#define TEST_FMT(manip, value, expected) \
193 do { \
194 std::basic_ostringstream<CharType> ss; \
195 ss.imbue(loc); \
196 ss << manip; \
197 test_fmt_impl(ss, (value), to_correct_string<CharType>(expected, loc), __LINE__); \
198 BOOST_LOCALE_START_CONST_CONDITION \
199 } while(0) BOOST_LOCALE_END_CONST_CONDITION
200
201#define TEST_PARSE_FAILS(manip, actual, type) \
202 do { \
203 std::basic_istringstream<CharType> ss; \
204 ss.imbue(loc); \
205 ss.str(to_correct_string<CharType>(actual, loc)); \
206 ss >> manip; \
207 test_parse_fail_impl<type>(ss, __LINE__); \
208 BOOST_LOCALE_START_CONST_CONDITION \
209 } while(0) BOOST_LOCALE_END_CONST_CONDITION
210
211#define TEST_PARSE(manip, value, expected) \
212 do { \
213 const auto str_value = to_correct_string<CharType>(value, loc); \
214 { \
215 std::basic_istringstream<CharType> ss; \
216 ss.imbue(loc); \
217 ss.str(str_value); \
218 ss >> manip; \
219 test_parse_impl(ss, expected, __LINE__); \
220 } \
221 { \
222 std::basic_istringstream<CharType> ss; \
223 ss.imbue(loc); \
224 ss.str(str_value + CharType('@')); \
225 ss >> manip; \
226 test_parse_at_impl(ss, expected, __LINE__); \
227 } \
228 BOOST_LOCALE_START_CONST_CONDITION \
229 } while(0) BOOST_LOCALE_END_CONST_CONDITION
230
231#define TEST_FMT_PARSE_1(manip, value_in, value_str) \
232 do { \
233 const std::string value_str_ = value_str; \
234 TEST_FMT(manip, value_in, value_str_); \
235 TEST_PARSE(manip, value_str_, value_in); \
236 BOOST_LOCALE_START_CONST_CONDITION \
237 } while(0) BOOST_LOCALE_END_CONST_CONDITION
238
239#define TEST_FMT_PARSE_2(m1, m2, value_in, value_str) \
240 do { \
241 const std::string value_str_ = value_str; \
242 TEST_FMT(m1 << m2, value_in, value_str_); \
243 TEST_PARSE(m1 >> m2, value_str_, value_in); \
244 BOOST_LOCALE_START_CONST_CONDITION \
245 } while(0) BOOST_LOCALE_END_CONST_CONDITION
246
247#define TEST_FMT_PARSE_2_2(m1, m2, value_in, value_str, value_parsed) \
248 do { \
249 const std::string value_str_ = value_str; \
250 TEST_FMT(m1 << m2, value_in, value_str_); \
251 TEST_PARSE(m1 >> m2, value_str_, value_parsed); \
252 BOOST_LOCALE_START_CONST_CONDITION \
253 } while(0) BOOST_LOCALE_END_CONST_CONDITION
254
255#define TEST_FMT_PARSE_3(m1, m2, m3, value_in, value_str) \
256 do { \
257 const std::string value_str_ = value_str; \
258 TEST_FMT(m1 << m2 << m3, value_in, value_str_); \
259 TEST_PARSE(m1 >> m2 >> m3, value_str_, value_in); \
260 BOOST_LOCALE_START_CONST_CONDITION \
261 } while(0) BOOST_LOCALE_END_CONST_CONDITION
262
263#define TEST_FMT_PARSE_3_2(m1, m2, m3, value_in, value_str, value_parsed) \
264 do { \
265 const std::string value_str_ = value_str; \
266 TEST_FMT(m1 << m2 << m3, value_in, value_str_); \
267 TEST_PARSE(m1 >> m2 >> m3, value_str_, value_parsed); \
268 BOOST_LOCALE_START_CONST_CONDITION \
269 } while(0) BOOST_LOCALE_END_CONST_CONDITION
270
271#define TEST_FMT_PARSE_4(m1, m2, m3, m4, value_in, value_str) \
272 do { \
273 const std::string value_str_ = value_str; \
274 TEST_FMT(m1 << m2 << m3 << m4, value_in, value_str_); \
275 TEST_PARSE(m1 >> m2 >> m3 >> m4, value_str_, value_in); \
276 BOOST_LOCALE_START_CONST_CONDITION \
277 } while(0) BOOST_LOCALE_END_CONST_CONDITION
278
279#define TEST_FMT_PARSE_4_2(m1, m2, m3, m4, value_in, value_str, value_parsed) \
280 do { \
281 const std::string value_str_ = value_str; \
282 TEST_FMT(m1 << m2 << m3 << m4, value_in, value_str_); \
283 TEST_PARSE(m1 >> m2 >> m3 >> m4, value_str_, value_parsed); \
284 BOOST_LOCALE_START_CONST_CONDITION \
285 } while(0) BOOST_LOCALE_END_CONST_CONDITION
286
287#define TEST_MIN_MAX_FMT(as, type, minval, maxval) \
288 TEST_FMT(as, std::numeric_limits<type>::min(), minval); \
289 TEST_FMT(as, std::numeric_limits<type>::max(), maxval)
290
291#define TEST_MIN_MAX_PARSE(as, type, minval, maxval) \
292 TEST_PARSE(as, minval, std::numeric_limits<type>::min()); \
293 TEST_PARSE(as, maxval, std::numeric_limits<type>::max())
294
295#define TEST_MIN_MAX(type, minval, maxval) \
296 TEST_MIN_MAX_FMT(as::number, type, minval, maxval); \
297 TEST_MIN_MAX_PARSE(as::number, type, minval, maxval)
298
299#define TEST_MIN_MAX_POSIX(type) \
300 do { \
301 const std::string minval = as_posix_string(std::numeric_limits<type>::min()); \
302 const std::string maxval = as_posix_string(std::numeric_limits<type>::max()); \
303 TEST_MIN_MAX_FMT(as::posix, type, minval, maxval); \
304 TEST_MIN_MAX_PARSE(as::posix, type, minval, maxval); \
305 BOOST_LOCALE_START_CONST_CONDITION \
306 } while(0) BOOST_LOCALE_END_CONST_CONDITION
307
308bool stdlib_correctly_errors_on_out_of_range_int16()
309{
310 static bool fails = []() -> bool {
311 std::stringstream ss("65000");
312 ss.imbue(loc: std::locale::classic());
313 int16_t v = 0;
314 ss >> v;
315 return ss.fail();
316 }();
317 return fails;
318}
319
320template<typename T>
321std::string as_posix_string(const T v)
322{
323 std::ostringstream ss;
324 ss.imbue(loc: std::locale::classic());
325 ss << v;
326 return ss.str();
327}
328
329template<typename CharType>
330void test_as_posix(const std::string& e_charset = "UTF-8")
331{
332 using boost::locale::localization_backend_manager;
333 const auto orig_backend = localization_backend_manager::global();
334 for(const std::string& backendName : orig_backend.get_all_backends()) {
335 std::cout << "Backend: " << backendName << std::endl;
336 auto backend = orig_backend;
337 backend.select(backend_name: backendName);
338 localization_backend_manager::global(backend);
339 for(const std::string name : {"en_US", "ru_RU", "de_DE"}) {
340 const std::locale loc = boost::locale::generator{}(name + "." + e_charset);
341 TEST_MIN_MAX_POSIX(int16_t);
342 TEST_MIN_MAX_POSIX(uint16_t);
343
344 TEST_MIN_MAX_POSIX(int32_t);
345 TEST_MIN_MAX_POSIX(uint32_t);
346 TEST_MIN_MAX_POSIX(signed long);
347 TEST_MIN_MAX_POSIX(unsigned long);
348
349 TEST_MIN_MAX_POSIX(int64_t);
350 TEST_MIN_MAX_POSIX(uint64_t);
351 TEST_MIN_MAX_POSIX(signed long long);
352 TEST_MIN_MAX_POSIX(unsigned long long);
353
354 TEST_FMT_PARSE_1(as::posix, 1.25f, "1.25");
355 TEST_FMT_PARSE_1(as::posix, -4.57, "-4.57");
356 TEST_FMT_PARSE_1(as::posix, 3.815l, "3.815");
357 }
358 }
359 localization_backend_manager::global(orig_backend);
360}
361
362template<typename CharType>
363void test_manip(std::string e_charset = "UTF-8")
364{
365 test_as_posix<CharType>(e_charset);
366 using string_type = std::basic_string<CharType>;
367 boost::locale::generator g;
368 for(const auto& name_number : {std::make_pair(x: "en_US", y: "1,200.1"),
369 std::make_pair(x: "he_IL", y: "1,200.1"),
370 std::make_pair(x: "ru_RU",
371 y: "1\xC2\xA0"
372 "200,1")})
373 {
374 const std::string locName = std::string(name_number.first) + "." + e_charset;
375 std::cout << "-- " << locName << '\n';
376 const std::locale loc = g(locName);
377 TEST_FMT_PARSE_1(as::posix, 1200.1, "1200.1");
378 TEST_FMT_PARSE_1(as::number, 1200.1, name_number.second);
379 }
380
381 const std::locale loc = g(test_locale_name + "." + e_charset);
382 TEST_FMT_PARSE_1(as::posix, 1200.1, "1200.1");
383 TEST_FMT_PARSE_1(as::number, 1200.1, "1,200.1");
384 TEST_FMT(as::number << std::setfill(CharType('_')) << std::setw(6), 1534, "_1,534");
385 TEST_FMT(as::number << std::left << std::setfill(CharType('_')) << std::setw(6), 1534, "1,534_");
386
387 // Ranges
388 TEST_MIN_MAX(int16_t, "-32,768", "32,767");
389 TEST_MIN_MAX(uint16_t, "0", "65,535");
390 TEST_PARSE_FAILS(as::number, "-1", uint16_t);
391 if(stdlib_correctly_errors_on_out_of_range_int16())
392 TEST_PARSE_FAILS(as::number, "65,535", int16_t);
393
394 TEST_MIN_MAX(int32_t, "-2,147,483,648", "2,147,483,647");
395 TEST_MIN_MAX(uint32_t, "0", "4,294,967,295");
396 TEST_PARSE_FAILS(as::number, "-1", uint32_t);
397 TEST_PARSE_FAILS(as::number, "4,294,967,295", int32_t);
398
399 TEST_MIN_MAX(int64_t, "-9,223,372,036,854,775,808", "9,223,372,036,854,775,807");
400 // ICU does not support uint64, but we have a fallback to format it at least
401 TEST_MIN_MAX_FMT(as::number, uint64_t, "0", "18446744073709551615");
402 TEST_PARSE_FAILS(as::number, "-1", uint64_t);
403
404 TEST_FMT_PARSE_3(as::number, std::left, std::setw(3), 15, "15 ");
405 TEST_FMT_PARSE_3(as::number, std::right, std::setw(3), 15, " 15");
406 TEST_FMT_PARSE_3(as::number, std::setprecision(3), std::fixed, 13.1, "13.100");
407 TEST_FMT_PARSE_3(as::number, std::setprecision(3), std::scientific, 13.1, "1.310E1");
408
409 TEST_PARSE_FAILS(as::number, "", int);
410 TEST_PARSE_FAILS(as::number, "--3", int);
411 TEST_PARSE_FAILS(as::number, "y", int);
412
413 TEST_FMT_PARSE_1(as::percent, 0.1, "10%");
414 TEST_FMT_PARSE_3(as::percent, std::fixed, std::setprecision(1), 0.10, "10.0%");
415
416 TEST_PARSE_FAILS(as::percent, "1", double);
417
418 TEST_FMT_PARSE_1(as::currency, 1345, "$1,345.00");
419 TEST_FMT_PARSE_1(as::currency, 1345.34, "$1,345.34");
420
421 TEST_PARSE_FAILS(as::currency, "$", double);
422
423#if BOOST_LOCALE_ICU_VERSION >= 402
424 TEST_FMT_PARSE_2(as::currency, as::currency_national, 1345, "$1,345.00");
425 TEST_FMT_PARSE_2(as::currency, as::currency_national, 1345.34, "$1,345.34");
426 TEST_FMT_PARSE_2(as::currency, as::currency_iso, 1345, get_icu_currency_iso(1345));
427 TEST_FMT_PARSE_2(as::currency, as::currency_iso, 1345.34, get_icu_currency_iso(1345.34));
428#endif
429 TEST_FMT_PARSE_1(as::spellout, 10, "ten");
430#if 402 <= BOOST_LOCALE_ICU_VERSION && BOOST_LOCALE_ICU_VERSION < 408
431 if(e_charset == "UTF-8")
432 TEST_FMT(as::ordinal, 1, "1\xcb\xa2\xe1\xb5\x97"); // 1st with st as ligatures
433#else
434 TEST_FMT(as::ordinal, 1, "1st");
435#endif
436
437 time_t a_date = 3600 * 24 * (31 + 4); // Feb 5th
438 time_t a_time = 3600 * 15 + 60 * 33; // 15:33:05
439 time_t a_timesec = 13;
440 time_t a_datetime = a_date + a_time + a_timesec;
441
442 TEST_FMT_PARSE_2_2(as::date, as::gmt, a_datetime, "Feb 5, 1970", a_date);
443 TEST_FMT_PARSE_3_2(as::date, as::date_short, as::gmt, a_datetime, "2/5/70", a_date);
444 TEST_FMT_PARSE_3_2(as::date, as::date_medium, as::gmt, a_datetime, "Feb 5, 1970", a_date);
445 TEST_FMT_PARSE_3_2(as::date, as::date_long, as::gmt, a_datetime, "February 5, 1970", a_date);
446 TEST_FMT_PARSE_3_2(as::date, as::date_full, as::gmt, a_datetime, "Thursday, February 5, 1970", a_date);
447
448 TEST_PARSE_FAILS(as::date >> as::date_short, "aa/bb/cc", double);
449
450 std::string icu_time_def = get_ICU_time(style: as::time, ts: a_datetime);
451 std::string icu_time_short = get_ICU_time(style: as::time_short, ts: a_datetime);
452 std::string icu_time_medium = get_ICU_time(style: as::time_medium, ts: a_datetime);
453 std::string icu_time_long = get_ICU_time(style: as::time_long, ts: a_datetime);
454 std::string icu_time_full = get_ICU_time(style: as::time_full, ts: a_datetime);
455
456 TEST_PARSE(as::time >> as::gmt, "3:33:13 PM", a_time + a_timesec);
457 TEST_FMT_PARSE_2_2(as::time, as::gmt, a_datetime, icu_time_def, a_time + a_timesec);
458
459 TEST_PARSE(as::time >> as::time_short >> as::gmt, "3:33 PM", a_time);
460 TEST_FMT_PARSE_3_2(as::time, as::time_short, as::gmt, a_datetime, icu_time_short, a_time);
461
462 TEST_PARSE(as::time >> as::time_medium >> as::gmt, "3:33:13 PM", a_time + a_timesec);
463 TEST_FMT_PARSE_3_2(as::time, as::time_medium, as::gmt, a_datetime, icu_time_medium, a_time + a_timesec);
464
465 TEST_PARSE(as::time >> as::time_long >> as::gmt, "3:33:13 PM GMT", a_time + a_timesec);
466 TEST_FMT_PARSE_3_2(as::time, as::time_long, as::gmt, a_datetime, icu_time_long, a_time + a_timesec);
467 // ICU 4.8.0 has a bug which makes parsing the full time fail when anything follows the time zone
468#if BOOST_LOCALE_ICU_VERSION_EXACT != 40800
469 TEST_PARSE(as::time >> as::time_full >> as::gmt, "3:33:13 PM GMT+00:00", a_time + a_timesec);
470 TEST_FMT_PARSE_3_2(as::time, as::time_full, as::gmt, a_datetime, icu_time_full, a_time + a_timesec);
471#endif
472 TEST_PARSE_FAILS(as::time, "AM", double);
473
474 icu_time_def = get_ICU_time(style: as::time, ts: a_datetime, tz: "GMT+01:00");
475 icu_time_short = get_ICU_time(style: as::time_short, ts: a_datetime, tz: "GMT+01:00");
476 icu_time_medium = get_ICU_time(style: as::time_medium, ts: a_datetime, tz: "GMT+01:00");
477 icu_time_long = get_ICU_time(style: as::time_long, ts: a_datetime, tz: "GMT+01:00");
478 icu_time_full = get_ICU_time(style: as::time_full, ts: a_datetime, tz: "GMT+01:00");
479
480 TEST_PARSE(as::time >> as::time_zone("GMT+01:00"), "4:33:13 PM", a_time + a_timesec);
481 TEST_FMT_PARSE_2_2(as::time, as::time_zone("GMT+01:00"), a_datetime, icu_time_def, a_time + a_timesec);
482
483 TEST_PARSE(as::time >> as::time_short >> as::time_zone("GMT+01:00"), "4:33 PM", a_time);
484 TEST_FMT_PARSE_3_2(as::time, as::time_short, as::time_zone("GMT+01:00"), a_datetime, icu_time_short, a_time);
485
486 TEST_PARSE(as::time >> as::time_medium >> as::time_zone("GMT+01:00"), "4:33:13 PM", a_time + a_timesec);
487 TEST_FMT_PARSE_3_2(as::time,
488 as::time_medium,
489 as::time_zone("GMT+01:00"),
490 a_datetime,
491 icu_time_medium,
492 a_time + a_timesec);
493
494 TEST_PARSE(as::time >> as::time_long >> as::time_zone("GMT+01:00"), "4:33:13 PM GMT+01:00", a_time + a_timesec);
495 TEST_FMT_PARSE_3_2(as::time,
496 as::time_long,
497 as::time_zone("GMT+01:00"),
498 a_datetime,
499 icu_time_long,
500 a_time + a_timesec);
501#if !(BOOST_LOCALE_ICU_VERSION == 308 && defined(__CYGWIN__)) // Known failure due to ICU issue
502 TEST_PARSE(as::time >> as::time_full >> as::time_zone("GMT+01:00"), "4:33:13 PM GMT+01:00", a_time + a_timesec);
503 TEST_FMT_PARSE_3_2(as::time,
504 as::time_full,
505 as::time_zone("GMT+01:00"),
506 a_datetime,
507 icu_time_full,
508 a_time + a_timesec);
509#endif
510
511 const std::string icu_def = get_ICU_datetime(style: as::time, ts: a_datetime);
512 const std::string icu_short = get_ICU_datetime(style: as::time_short, ts: a_datetime);
513 const std::string icu_medium = get_ICU_datetime(style: as::time_medium, ts: a_datetime);
514 const std::string icu_long = get_ICU_datetime(style: as::time_long, ts: a_datetime);
515 const std::string icu_full = get_ICU_datetime(style: as::time_full, ts: a_datetime);
516
517 TEST_PARSE(as::datetime >> as::gmt, "Feb 5, 1970 3:33:13 PM", a_datetime);
518 TEST_FMT_PARSE_2(as::datetime, as::gmt, a_datetime, icu_def);
519
520 TEST_PARSE(as::datetime >> as::date_short >> as::time_short >> as::gmt, "2/5/70 3:33 PM", a_date + a_time);
521 TEST_FMT_PARSE_4_2(as::datetime, as::date_short, as::time_short, as::gmt, a_datetime, icu_short, a_date + a_time);
522
523 TEST_PARSE(as::datetime >> as::date_medium >> as::time_medium >> as::gmt, "Feb 5, 1970 3:33:13 PM", a_datetime);
524 TEST_FMT_PARSE_4(as::datetime, as::date_medium, as::time_medium, as::gmt, a_datetime, icu_medium);
525
526 TEST_PARSE(as::datetime >> as::date_long >> as::time_long >> as::gmt,
527 "February 5, 1970 3:33:13 PM GMT",
528 a_datetime);
529 TEST_FMT_PARSE_4(as::datetime, as::date_long, as::time_long, as::gmt, a_datetime, icu_long);
530#if BOOST_LOCALE_ICU_VERSION_EXACT != 40800
531 // ICU 4.8.0 has a bug which makes parsing the full time fail when anything follows the time zone
532 TEST_PARSE(as::datetime >> as::date_full >> as::time_full >> as::gmt,
533 "Thursday, February 5, 1970 3:33:13 PM Greenwich Mean Time",
534 a_datetime);
535 TEST_FMT_PARSE_4(as::datetime, as::date_full, as::time_full, as::gmt, a_datetime, icu_full);
536#endif
537
538 const std::pair<char, std::string> mark_test_cases[] = {
539 std::make_pair(x: 'a', y: "Thu"),
540 std::make_pair(x: 'A', y: "Thursday"),
541 std::make_pair(x: 'b', y: "Feb"),
542 std::make_pair(x: 'B', y: "February"),
543 std::make_pair(x: 'c', y: icu_full),
544 std::make_pair(x: 'd', y: "05"),
545 std::make_pair(x: 'e', y: "5"),
546 std::make_pair(x: 'h', y: "Feb"),
547 std::make_pair(x: 'H', y: "15"),
548 std::make_pair(x: 'I', y: "03"),
549 std::make_pair(x: 'j', y: "36"),
550 std::make_pair(x: 'm', y: "02"),
551 std::make_pair(x: 'M', y: "33"),
552 std::make_pair(x: 'n', y: "\n"),
553 std::make_pair(x: 'p', y: "PM"),
554 std::make_pair(x: 'r', y: "03:33:13 PM"),
555 std::make_pair(x: 'R', y: "15:33"),
556 std::make_pair(x: 'S', y: "13"),
557 std::make_pair(x: 't', y: "\t"),
558 std::make_pair(x: 'T', y: "15:33:13"),
559 std::make_pair(x: 'x', y: "Feb 5, 1970"),
560 std::make_pair(x: 'X', y: get_ICU_time(style: as::time, ts: a_datetime)),
561 std::make_pair(x: 'y', y: "70"),
562 std::make_pair(x: 'Y', y: "1970"),
563 std::make_pair(x: 'Z', y: icu_full_gmt_name),
564 std::make_pair(x: '%', y: "%"),
565 };
566
567 for(const auto& mark_result : mark_test_cases) {
568 string_type format_string;
569 format_string += static_cast<CharType>('%');
570 format_string += static_cast<CharType>(mark_result.first);
571 std::cout << "Test: %" << mark_result.first << "\n";
572 std::basic_ostringstream<CharType> ss;
573 ss.imbue(loc);
574 ss << as::ftime(format_string) << as::gmt << a_datetime;
575 TEST_EQ(ss.str(), to_correct_string<CharType>(mark_result.second, loc));
576 }
577
578 {
579 const time_t now = time(timer: nullptr);
580 boost::locale::time_zone::global(new_tz: "GMT+4:00");
581 const time_t local_now = now + 3600 * 4;
582 char time_str[256];
583 const std::string format = "%H:%M:%S";
584 const string_type format_string(format.begin(), format.end());
585
586 std::basic_ostringstream<CharType> ss;
587 ss.imbue(loc);
588 // By default the globally set (local) time zone is used
589 ss << as::ftime(format_string) << now;
590 strftime(s: time_str, maxsize: sizeof(time_str), format: format.c_str(), tp: gmtime_wrap(time: &local_now));
591 TEST_EQ(ss.str(), to<CharType>(time_str));
592 // We can manually tell it to use the local time zone
593 empty_stream(ss) << as::ftime(format_string) << as::local_time << now;
594 TEST_EQ(ss.str(), to<CharType>(time_str));
595 // Or e.g. GMT
596 empty_stream(ss) << as::ftime(format_string) << as::gmt << now;
597 strftime(s: time_str, maxsize: sizeof(time_str), format: format.c_str(), tp: gmtime_wrap(time: &now));
598 TEST_EQ(ss.str(), to<CharType>(time_str));
599 }
600 const std::pair<std::string, std::string> format_string_test_cases[] = {
601 std::make_pair(x: "Now is %A, %H o'clo''ck ' or not ' ", y: "Now is Thursday, 15 o'clo''ck ' or not ' "),
602 std::make_pair(x: "'test %H'", y: "'test 15'"),
603 std::make_pair(x: "%H'", y: "15'"),
604 std::make_pair(x: "'%H'", y: "'15'"),
605 };
606
607 for(const auto& test_case : format_string_test_cases) {
608 const string_type format_string(test_case.first.begin(), test_case.first.end());
609 std::cout << "Test: '" << test_case.first << "'\n";
610 std::basic_ostringstream<CharType> ss;
611 ss.imbue(loc);
612 ss << as::ftime(format_string) << as::gmt << a_datetime;
613 TEST_EQ(ss.str(), to<CharType>(test_case.second));
614 }
615}
616
617template<typename CharType, typename... Ts>
618std::basic_string<CharType> do_format(const std::locale& loc, const std::basic_string<CharType> fmt_str, Ts&&... ts)
619{
620 boost::locale::basic_format<CharType> fmt(fmt_str);
621 using expander = int[];
622 (void)expander{0, (fmt % std::forward<Ts>(ts), 0)...};
623 return fmt.str(loc);
624}
625
626template<typename CharType, size_t size, typename... Ts>
627std::basic_string<CharType> do_format(const std::locale& loc, const char (&fmt_str)[size], Ts&&... ts)
628{
629 return do_format(loc, ascii_to<CharType>(fmt_str), std::forward<Ts>(ts)...);
630}
631
632template<typename CharType, typename T>
633void test_format_class_impl(const std::string& fmt_string,
634 const T& value,
635 const std::string& expected_str,
636 const std::locale& loc,
637 unsigned line)
638{
639 using format_type = boost::locale::basic_format<CharType>;
640 format_type fmt(std::basic_string<CharType>(fmt_string.begin(), fmt_string.end()));
641 fmt % value;
642 std::basic_string<CharType> expected_str_loc(to_correct_string<CharType>(expected_str, loc));
643 test_eq_impl(fmt.str(loc), expected_str_loc, ("Format: " + fmt_string).c_str(), line);
644}
645
646template<typename CharType>
647void test_format_class(std::string charset = "UTF-8")
648{
649 using string_type = std::basic_string<CharType>;
650 using format_type = boost::locale::basic_format<CharType>;
651
652 boost::locale::generator g;
653 std::locale loc = g(test_locale_name + "." + charset);
654
655 // Simple tests using same input/output
656 {
657 const string_type fmt_string = ascii_to<CharType>("{3} {1} {2}");
658 const string_type expected = ascii_to<CharType>("3 1 2");
659
660 // Output format to stream
661 {
662 std::basic_ostringstream<CharType> ss;
663 ss.imbue(loc);
664
665 // Stream formatted output
666 empty_stream(ss) << format_type(fmt_string) % 1 % 2 % 3;
667 TEST_EQ(ss.str(), expected);
668
669 // Stream translated output
670 empty_stream(ss) << format_type(boost::locale::translate(fmt_string)) % 1 % 2 % 3;
671 TEST_EQ(ss.str(), expected);
672 }
673
674 // Multi-step: Create, format & output via str() method
675 {
676 format_type fmt(fmt_string);
677 TEST_EQ((fmt % 1 % 2 % 3).str(loc), expected);
678 }
679 // Output via str() on intermediate with ctor from string and C-string
680 TEST_EQ((format_type(fmt_string.c_str()) % 1 % 2 % 3).str(loc), expected);
681 TEST_EQ((format_type(fmt_string) % 1 % 2 % 3).str(loc), expected);
682 }
683
684 // Actually translate something
685 {
686 g.add_messages_domain(domain: "default/ISO-8859-8");
687 g.add_messages_path(path: message_path);
688 std::locale loc_he = g("he_IL.UTF-8");
689 const string_type hello = ascii_to<CharType>("hello");
690 const string_type hello_he = to_correct_string<CharType>("שלום", loc_he);
691 format_type fmt(boost::locale::translate(hello));
692 TEST_EQ(fmt.str(loc_he), hello_he);
693 // Use current global locale if none is given to str()
694 std::locale old_locale = std::locale::global(loc: g("en_US.UTF-8"));
695 TEST_EQ(fmt.str(), hello); // Not translated in en_US
696 std::locale::global(loc: loc_he);
697 TEST_EQ(fmt.str(), hello_he); // translated in he_IL
698
699 // Movable
700 {
701 format_type fmt2 = format_type(ascii_to<CharType>("{3} {1} {2}"));
702 const int i1 = 1, i2 = 2, i3 = 3, i42 = 42;
703 fmt2 % i1 % i2 % i3;
704 fmt2 = format_type(ascii_to<CharType>("{1}"));
705 TEST_EQ(fmt2.str(), ascii_to<CharType>("")); // No bound value
706 TEST_EQ((fmt2 % i42).str(), ascii_to<CharType>("42"));
707 // Can't move with bound params
708 TEST_THROWS(format_type fmt3(std::move(fmt2)), std::exception);
709 TEST_EQ(fmt2.str(), ascii_to<CharType>("42")); // Original unchanged
710 fmt2 = format_type(ascii_to<CharType>("{1}"));
711 fmt2 % i1;
712 format_type fmt3(string_type{});
713 TEST_THROWS(fmt3 = std::move(fmt2), std::exception);
714 fmt2 = format_type(ascii_to<CharType>("{1}"));
715 fmt3 = std::move(fmt2);
716 TEST_EQ((fmt3 % 42).str(), ascii_to<CharType>("42"));
717
718 fmt2 = format_type(hello);
719 TEST_EQ(fmt2.str(), hello); // Not translated
720 fmt2 = format_type(boost::locale::translate(hello));
721 TEST_EQ(fmt2.str(), hello_he); // Translated
722 }
723 // Restore
724 std::locale::global(loc: old_locale);
725 }
726 // Allows many params
727 TEST_EQ(do_format<CharType>(loc, "{1}{2}{3}{4}{5}{10}{9}{8}{7}{6}", 11, 22, 33, 44, 55, 'a', 'b', 'c', 'd', 'f'),
728 ascii_to<CharType>("1122334455fdcba"));
729 // Not passed placeholders are removed
730 TEST_EQ(do_format<CharType>(loc, "{1}{3}{2}", "hello", "world"), ascii_to<CharType>("helloworld"));
731 TEST_EQ(do_format<CharType>(loc, "{1}"), ascii_to<CharType>(""));
732 // Invalid indices are ignored
733 TEST_EQ(do_format<CharType>(loc, "b{}e"), ascii_to<CharType>("be"));
734 TEST_EQ(do_format<CharType>(loc, "b{0}e", 1), ascii_to<CharType>("be"));
735 TEST_EQ(do_format<CharType>(loc, "b{-1}e", 1), ascii_to<CharType>("be"));
736 TEST_EQ(do_format<CharType>(loc, "b{1.x}e"), ascii_to<CharType>("be"));
737 // Unexpected closing brace and other chars are ignored
738 TEST_EQ(do_format<CharType>(loc, " = , } 3"), ascii_to<CharType>(" = , } 3"));
739 // Trailing opening brace is ignored
740 TEST_EQ(do_format<CharType>(loc, "End {"), ascii_to<CharType>("End "));
741 // Trailing closing brace is added like any other char
742 TEST_EQ(do_format<CharType>(loc, "End}"), ascii_to<CharType>("End}"));
743 // Escaped trailing closing brace added once
744 TEST_EQ(do_format<CharType>(loc, "End}}"), ascii_to<CharType>("End}"));
745 // ...and twice when another trailing brace is added
746 TEST_EQ(do_format<CharType>(loc, "End}}}"), ascii_to<CharType>("End}}"));
747 // Escaped braces
748 TEST_EQ(do_format<CharType>(loc, "Unexpected {{ in file"), ascii_to<CharType>("Unexpected { in file"));
749 TEST_EQ(do_format<CharType>(loc, "Unexpected {{ in {1}#{2}", "f", 7), ascii_to<CharType>("Unexpected { in f#7"));
750 TEST_EQ(do_format<CharType>(loc, "Unexpected }} in file"), ascii_to<CharType>("Unexpected } in file"));
751 TEST_EQ(do_format<CharType>(loc, "Unexpected }} in {1}#{2}", "f", 9), ascii_to<CharType>("Unexpected } in f#9"));
752
753 // format with multiple types
754 TEST_EQ(do_format<CharType>(loc, "{1} {2}", "hello", 2), ascii_to<CharType>("hello 2"));
755
756 // format with locale & encoding
757 {
758#if BOOST_LOCALE_ICU_VERSION >= 400
759 const auto expected = boost::locale::conv::utf_to_utf<CharType>("10,00\xC2\xA0€");
760#else
761 const auto expected = boost::locale::conv::utf_to_utf<CharType>("10,00 €"); // LCOV_EXCL_LINE
762#endif
763 TEST_EQ(do_format<CharType>(loc, "{1,cur,locale=de_DE.UTF-8}", 10), expected);
764 }
765
766#define TEST_FORMAT_CLS(fmt_string, value, expected_str) \
767 test_format_class_impl<CharType>(fmt_string, value, expected_str, loc, __LINE__)
768
769 // Test different types and modifiers
770 TEST_FORMAT_CLS("{1}", 1200.1, "1200.1");
771 TEST_FORMAT_CLS("Test {1,num}", 1200.1, "Test 1,200.1");
772 TEST_FORMAT_CLS("{{1}} {1,number}", 3200.4, "{1} 3,200.4");
773 // placeholder in escaped braces, see issue #194
774 TEST_FORMAT_CLS("{{{1}}}", "num", "{num}");
775 TEST_FORMAT_CLS("{{{1}}}", 1200.1, "{1200.1}");
776
777 TEST_FORMAT_CLS("{1,num=sci,p=3}", 13.1, "1.310E1");
778 TEST_FORMAT_CLS("{1,num=scientific,p=3}", 13.1, "1.310E1");
779 TEST_FORMAT_CLS("{1,num=fix,p=3}", 13.1, "13.100");
780 TEST_FORMAT_CLS("{1,num=fixed,p=3}", 13.1, "13.100");
781 TEST_FORMAT_CLS("{1,num=hex}", 0x1234, "1234");
782 TEST_FORMAT_CLS("{1,num=oct}", 42, "52");
783 TEST_FORMAT_CLS("{1,<,w=3,num}", -1, "-1 ");
784 TEST_FORMAT_CLS("{1,>,w=3,num}", 1, " 1");
785 TEST_FORMAT_CLS("{per,1}", 0.1, "10%");
786 TEST_FORMAT_CLS("{percent,1}", 0.1, "10%");
787 TEST_FORMAT_CLS("{1,cur}", 1234, "$1,234.00");
788 TEST_FORMAT_CLS("{1,currency}", 1234, "$1,234.00");
789 if(charset == "UTF-8") {
790#if BOOST_LOCALE_ICU_VERSION >= 400
791 TEST_FORMAT_CLS("{1,cur,locale=de_DE}", 10, "10,00\xC2\xA0€");
792#else
793 TEST_FORMAT_CLS("{1,cur,locale=de_DE}", 10, "10,00 €"); // LCOV_EXCL_LINE
794#endif
795 }
796#if BOOST_LOCALE_ICU_VERSION >= 402
797 TEST_FORMAT_CLS("{1,cur=nat}", 1234, "$1,234.00");
798 TEST_FORMAT_CLS("{1,cur=national}", 1234, "$1,234.00");
799 TEST_FORMAT_CLS("{1,cur=iso}", 1234, get_icu_currency_iso(1234));
800#endif
801 TEST_FORMAT_CLS("{1,spell}", 10, "ten");
802 TEST_FORMAT_CLS("{1,spellout}", 10, "ten");
803#if 402 <= BOOST_LOCALE_ICU_VERSION && BOOST_LOCALE_ICU_VERSION < 408
804 if(charset == "UTF-8") {
805 TEST_FORMAT_CLS("{1,ord}", 1, "1\xcb\xa2\xe1\xb5\x97");
806 TEST_FORMAT_CLS("{1,ordinal}", 1, "1\xcb\xa2\xe1\xb5\x97");
807 }
808#else
809 TEST_FORMAT_CLS("{1,ord}", 1, "1st");
810 TEST_FORMAT_CLS("{1,ordinal}", 1, "1st");
811#endif
812
813 // formatted time
814 {
815 boost::locale::time_zone::global(new_tz: "GMT+4:00");
816 const time_t now = time(timer: nullptr);
817 char local_time_str[256], local_time_str_gmt2[256];
818 time_t local_now = now + 3600 * 4;
819 strftime(s: local_time_str, maxsize: sizeof(local_time_str), format: "'%H:%M:%S'", tp: gmtime_wrap(time: &local_now));
820 local_now = now + 3600 * 2;
821 strftime(s: local_time_str_gmt2, maxsize: sizeof(local_time_str_gmt2), format: "'%H:%M:%S'", tp: gmtime_wrap(time: &local_now));
822
823 TEST_FORMAT_CLS("{1,ftime='''%H:%M:%S'''}", now, local_time_str);
824 TEST_FORMAT_CLS("{1,strftime='''%H:%M:%S'''}", now, local_time_str);
825 // 'local' has no impact on str(), uses global timezone
826 TEST_FORMAT_CLS("{1,local,ftime='''%H:%M:%S'''}", now, local_time_str);
827
828 std::basic_ostringstream<CharType> ss;
829 ss.imbue(loc);
830 ss << as::time_zone(id: "GMT+02:00");
831 format_type fmt_stream(ascii_to<CharType>("{1,ftime='''%H:%M:%S'''}")); // Use timezone of stream
832 format_type fmt_local(ascii_to<CharType>("{1,local,ftime='''%H:%M:%S'''}")); // Use global timezone
833 empty_stream(ss) << fmt_stream % now;
834 TEST_EQ(ss.str(), to<CharType>(local_time_str_gmt2));
835 empty_stream(ss) << fmt_local % now;
836 TEST_EQ(ss.str(), to<CharType>(local_time_str));
837 }
838
839 time_t a_date = 3600 * 24 * (31 + 4); // Feb 5th
840 time_t a_time = 3600 * 15 + 60 * 33; // 15:33:05
841 time_t a_timesec = 13;
842 time_t a_datetime = a_date + a_time + a_timesec;
843 const std::string icu_time_def = get_ICU_time(style: as::time, ts: a_datetime);
844 const std::string icu_time_short = get_ICU_time(style: as::time_short, ts: a_datetime);
845 const std::string icu_time_medium = get_ICU_time(style: as::time_medium, ts: a_datetime);
846 const std::string icu_time_long = get_ICU_time(style: as::time_long, ts: a_datetime);
847 const std::string icu_time_full = get_ICU_time(style: as::time_full, ts: a_datetime);
848 const std::string icu_date_short = get_ICU_date(style: as::date_short, ts: a_datetime);
849 const std::string icu_date_medium = get_ICU_date(style: as::date_medium, ts: a_datetime);
850 const std::string icu_date_long = get_ICU_date(style: as::date_long, ts: a_datetime);
851 const std::string icu_date_full = get_ICU_date(style: as::date_full, ts: a_datetime);
852 const std::string icu_datetime_def = get_ICU_datetime(style: as::time, ts: a_datetime);
853 const std::string icu_datetime_short = get_ICU_datetime(style: as::time_short, ts: a_datetime);
854 const std::string icu_datetime_medium = get_ICU_datetime(style: as::time_medium, ts: a_datetime);
855 const std::string icu_datetime_long = get_ICU_datetime(style: as::time_long, ts: a_datetime);
856 const std::string icu_datetime_full = get_ICU_datetime(style: as::time_full, ts: a_datetime);
857 // Sanity check
858 TEST_EQ(icu_date_full, "Thursday, February 5, 1970");
859
860 TEST_FORMAT_CLS("{1,date,gmt}", a_datetime, "Feb 5, 1970");
861 TEST_FORMAT_CLS("{1,time,gmt}", a_datetime, icu_time_def);
862 TEST_FORMAT_CLS("{1,datetime,gmt}", a_datetime, icu_datetime_def);
863 TEST_FORMAT_CLS("{1,dt,gmt}", a_datetime, icu_datetime_def);
864 // With length modifier
865 TEST_FORMAT_CLS("{1,time=short,gmt}", a_datetime, icu_time_short);
866 TEST_FORMAT_CLS("{1,time=s,gmt}", a_datetime, icu_time_short);
867 TEST_FORMAT_CLS("{1,time=medium,gmt}", a_datetime, icu_time_medium);
868 TEST_FORMAT_CLS("{1,time=m,gmt}", a_datetime, icu_time_medium);
869 TEST_FORMAT_CLS("{1,time=long,gmt}", a_datetime, icu_time_long);
870 TEST_FORMAT_CLS("{1,time=l,gmt}", a_datetime, icu_time_long);
871 TEST_FORMAT_CLS("{1,time=full,gmt}", a_datetime, icu_time_full);
872 TEST_FORMAT_CLS("{1,time=f,gmt}", a_datetime, icu_time_full);
873 TEST_FORMAT_CLS("{1,date=short,gmt}", a_datetime, icu_date_short);
874 TEST_FORMAT_CLS("{1,date=s,gmt}", a_datetime, icu_date_short);
875 TEST_FORMAT_CLS("{1,date=medium,gmt}", a_datetime, icu_date_medium);
876 TEST_FORMAT_CLS("{1,date=m,gmt}", a_datetime, icu_date_medium);
877 TEST_FORMAT_CLS("{1,date=long,gmt}", a_datetime, icu_date_long);
878 TEST_FORMAT_CLS("{1,date=l,gmt}", a_datetime, icu_date_long);
879 TEST_FORMAT_CLS("{1,date=full,gmt}", a_datetime, icu_date_full);
880 TEST_FORMAT_CLS("{1,date=f,gmt}", a_datetime, icu_date_full);
881 // Handle timezones and reuse of arguments
882 const std::string icu_time_short2 = get_ICU_time(style: as::time_short, ts: a_datetime, tz: "GMT+01:00");
883 TEST_FORMAT_CLS("{1,time=s,gmt};{1,time=s,timezone=GMT+01:00}", a_datetime, icu_time_short + ";" + icu_time_short2);
884 TEST_FORMAT_CLS("{1,time=s,gmt};{1,time=s,tz=GMT+01:00}", a_datetime, icu_time_short + ";" + icu_time_short2);
885 // Handle single quotes
886 TEST_FORMAT_CLS("{1,gmt,ftime='%H'''}", a_datetime, "15'");
887 TEST_FORMAT_CLS("{1,gmt,ftime='''%H'}", a_datetime, "'15");
888 TEST_FORMAT_CLS("{1,gmt,ftime='%H o''clock'}", a_datetime, "15 o'clock");
889
890 // Test not a year of the week
891 a_datetime = 1388491200; // 2013-12-31 12:00 - check we don't use week of year
892
893 TEST_FORMAT_CLS("{1,gmt,ftime='%Y'}", a_datetime, "2013");
894 TEST_FORMAT_CLS("{1,gmt,ftime='%y'}", a_datetime, "13");
895 TEST_FORMAT_CLS("{1,gmt,ftime='%D'}", a_datetime, "12/31/13");
896}
897
898BOOST_LOCALE_DISABLE_UNREACHABLE_CODE_WARNING
899void test_main(int argc, char** argv)
900{
901 if(argc == 2)
902 message_path = argv[1];
903
904#ifndef BOOST_LOCALE_WITH_ICU
905 std::cout << "ICU is not build... Skipping\n";
906 return;
907#endif
908 boost::locale::time_zone::global(new_tz: "GMT+4:00");
909 std::cout << "Testing char, UTF-8" << std::endl;
910 test_manip<char>();
911 test_format_class<char>();
912 std::cout << "Testing char, ISO8859-1" << std::endl;
913 test_manip<char>(e_charset: "ISO8859-1");
914 test_format_class<char>(charset: "ISO8859-1");
915
916 std::cout << "Testing wchar_t" << std::endl;
917 test_manip<wchar_t>();
918 test_format_class<wchar_t>();
919
920#ifdef BOOST_LOCALE_ENABLE_CHAR16_T
921 std::cout << "Testing char16_t" << std::endl;
922 test_manip<char16_t>();
923 test_format_class<char16_t>();
924#endif
925
926#ifdef BOOST_LOCALE_ENABLE_CHAR32_T
927 std::cout << "Testing char32_t" << std::endl;
928 test_manip<char32_t>();
929 test_format_class<char32_t>();
930#endif
931}
932
933// boostinspect:noascii
934// boostinspect:nominmax
935

source code of boost/libs/locale/test/test_formatting.cpp