1//
2// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
3// Copyright (c) 2021-2023 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/icu/formatter.hpp"
9#include <boost/locale/formatting.hpp>
10#include <boost/locale/info.hpp>
11#include "boost/locale/icu/formatters_cache.hpp"
12#include "boost/locale/icu/icu_util.hpp"
13#include "boost/locale/icu/time_zone.hpp"
14#include "boost/locale/icu/uconv.hpp"
15#include "boost/locale/util/foreach_char.hpp"
16#include <limits>
17#include <memory>
18#ifdef BOOST_MSVC
19# pragma warning(push)
20# pragma warning(disable : 4251) // "identifier" : class "type" needs to have dll-interface...
21#endif
22#include <unicode/datefmt.h>
23#include <unicode/decimfmt.h>
24#include <unicode/numfmt.h>
25#include <unicode/rbnf.h>
26#include <unicode/smpdtfmt.h>
27
28#ifdef BOOST_MSVC
29# pragma warning(pop)
30# pragma warning(disable : 4244) // lose data
31#endif
32
33namespace boost { namespace locale { namespace impl_icu {
34
35 namespace {
36 // Set the min/max fraction digits for the NumberFormat
37 void set_fraction_digits(icu::NumberFormat& nf, const std::ios_base::fmtflags how, std::streamsize precision)
38 {
39#if BOOST_LOCALE_ICU_VERSION >= 5601
40 // Since ICU 56.1 the integer part counts to the fraction part
41 if(how == std::ios_base::scientific)
42 precision += nf.getMaximumIntegerDigits();
43#endif
44 nf.setMaximumFractionDigits(precision);
45 if(how == std::ios_base::scientific || how == std::ios_base::fixed)
46 nf.setMinimumFractionDigits(precision);
47 else
48 nf.setMinimumFractionDigits(0);
49 }
50 } // namespace
51
52 template<typename CharType>
53 class number_format : public formatter<CharType> {
54 public:
55 typedef std::basic_string<CharType> string_type;
56
57 number_format(icu::NumberFormat& fmt, std::string codepage) : cvt_(codepage), icu_fmt_(fmt) {}
58
59 string_type format(double value, size_t& code_points) const override { return do_format(value, code_points); }
60 string_type format(int64_t value, size_t& code_points) const override { return do_format(value, code_points); }
61 string_type format(int32_t value, size_t& code_points) const override { return do_format(value, code_points); }
62 size_t parse(const string_type& str, double& value) const override { return do_parse(str, value); }
63 size_t parse(const string_type& str, int64_t& value) const override { return do_parse(str, value); }
64 size_t parse(const string_type& str, int32_t& value) const override { return do_parse(str, value); }
65
66 private:
67 bool get_value(double& v, icu::Formattable& fmt) const
68 {
69 UErrorCode err = U_ZERO_ERROR;
70 v = fmt.getDouble(status&: err);
71 if(U_FAILURE(code: err))
72 return false;
73 return true;
74 }
75
76 bool get_value(int64_t& v, icu::Formattable& fmt) const
77 {
78 UErrorCode err = U_ZERO_ERROR;
79 v = fmt.getInt64(status&: err);
80 if(U_FAILURE(code: err))
81 return false;
82 return true;
83 }
84
85 bool get_value(int32_t& v, icu::Formattable& fmt) const
86 {
87 UErrorCode err = U_ZERO_ERROR;
88 v = fmt.getLong(status&: err);
89 if(U_FAILURE(code: err))
90 return false;
91 return true;
92 }
93
94 template<typename ValueType>
95 string_type do_format(ValueType value, size_t& code_points) const
96 {
97 icu::UnicodeString tmp;
98 icu_fmt_.format(value, tmp);
99 code_points = tmp.countChar32();
100 return cvt_.std(tmp);
101 }
102
103 template<typename ValueType>
104 size_t do_parse(const string_type& str, ValueType& v) const
105 {
106 icu::Formattable val;
107 icu::ParsePosition pp;
108 icu::UnicodeString tmp = cvt_.icu(str.data(), str.data() + str.size());
109
110 icu_fmt_.parse(text: tmp, result&: val, parsePosition&: pp);
111
112 ValueType tmp_v;
113
114 if(pp.getIndex() == 0 || !get_value(tmp_v, val))
115 return 0;
116 size_t cut = cvt_.cut(tmp, str.data(), str.data() + str.size(), pp.getIndex());
117 if(cut == 0)
118 return 0;
119 v = tmp_v;
120 return cut;
121 }
122
123 icu_std_converter<CharType> cvt_;
124 icu::NumberFormat& icu_fmt_;
125 };
126
127 template<typename CharType>
128 class date_format : public formatter<CharType> {
129 public:
130 typedef std::basic_string<CharType> string_type;
131
132 string_type format(double value, size_t& code_points) const override { return do_format(value, codepoints&: code_points); }
133 string_type format(int64_t value, size_t& code_points) const override { return do_format(value, codepoints&: code_points); }
134
135 string_type format(int32_t value, size_t& code_points) const override { return do_format(value, codepoints&: code_points); }
136
137 size_t parse(const string_type& str, double& value) const override { return do_parse(str, value); }
138 size_t parse(const string_type& str, int64_t& value) const override { return do_parse(str, value); }
139 size_t parse(const string_type& str, int32_t& value) const override { return do_parse(str, value); }
140
141 date_format(icu::DateFormat& fmt, const std::string& encoding) : cvt_(encoding), icu_fmt_(fmt) {}
142 date_format(std::unique_ptr<icu::DateFormat> fmt, const std::string& encoding) :
143 cvt_(encoding), icu_fmt_holder_(std::move(fmt)), icu_fmt_(*icu_fmt_holder_)
144 {}
145
146 private:
147 template<typename ValueType>
148 size_t do_parse(const string_type& str, ValueType& value) const
149 {
150 icu::ParsePosition pp;
151 icu::UnicodeString tmp = cvt_.icu(str.data(), str.data() + str.size());
152
153 UDate udate = icu_fmt_.parse(text: tmp, pos&: pp);
154 if(pp.getIndex() == 0)
155 return 0;
156 double date = udate / 1000.0;
157 typedef std::numeric_limits<ValueType> limits_type;
158 // Explicit cast to double to avoid warnings changing value (e.g. for INT64_MAX -> double)
159 if(date > static_cast<double>(limits_type::max()) || date < static_cast<double>(limits_type::min()))
160 return 0;
161 size_t cut = cvt_.cut(tmp, str.data(), str.data() + str.size(), pp.getIndex());
162 if(cut == 0)
163 return 0;
164 // Handle the edge case where the double is slightly out of range and hence the cast would be UB
165 // by rounding to the min/max values
166 if(date == static_cast<double>(limits_type::max()))
167 value = limits_type::max();
168 else if(date == static_cast<double>(limits_type::min()))
169 value = limits_type::min();
170 else
171 value = static_cast<ValueType>(date);
172 return cut;
173 }
174
175 string_type do_format(double value, size_t& codepoints) const
176 {
177 UDate date = value * 1000.0; // UDate is time_t in milliseconds
178 icu::UnicodeString tmp;
179 icu_fmt_.format(date, appendTo&: tmp);
180 codepoints = tmp.countChar32();
181 return cvt_.std(tmp);
182 }
183
184 icu_std_converter<CharType> cvt_;
185 std::unique_ptr<icu::DateFormat> icu_fmt_holder_;
186 icu::DateFormat& icu_fmt_;
187 };
188
189 icu::UnicodeString strftime_symbol_to_icu(const char c, const formatters_cache& cache)
190 {
191 switch(c) {
192 case 'a': // Abbr Weekday
193 return "EE";
194 case 'A': // Full Weekday
195 return "EEEE";
196 case 'b': // Abbr Month
197 return "MMM";
198 case 'B': // Full Month
199 return "MMMM";
200 case 'c': // DateTime
201 return cache.default_date_time_format();
202 // not supported by ICU ;(
203 // case 'C': // Century -> 1980 -> 19
204 case 'd': // Day of Month [01,31]
205 return "dd";
206 case 'D': // %m/%d/%y
207 return "MM/dd/yy";
208 case 'e': // Day of Month [1,31]
209 return "d";
210 case 'h': // == b
211 return "MMM";
212 case 'H': // 24 clock hour 00,23
213 return "HH";
214 case 'I': // 12 clock hour 01,12
215 return "hh";
216 case 'j': // day of year 001,366
217 return "D";
218 case 'm': // month as [01,12]
219 return "MM";
220 case 'M': // minute [00,59]
221 return "mm";
222 case 'n': // \n
223 return "\n";
224 case 'p': // am-pm
225 return "a";
226 case 'r': // time with AM/PM %I:%M:%S %p
227 return "hh:mm:ss a";
228 case 'R': // %H:%M
229 return "HH:mm";
230 case 'S': // second [00,61]
231 return "ss";
232 case 't': // \t
233 return "\t";
234 case 'T': // %H:%M:%S
235 return "HH:mm:ss";
236 /* case 'u': // weekday 1,7 1=Monday
237 case 'U': // week number of year [00,53] Sunday first
238 case 'V': // week number of year [01,53] Monday first
239 case 'w': // weekday 0,7 0=Sunday
240 case 'W': // week number of year [00,53] Monday first, */
241 case 'x': // Date
242 return cache.default_date_format();
243 case 'X': // Time
244 return cache.default_time_format();
245 case 'y': // Year [00-99]
246 return "yy";
247 case 'Y': // Year 1998
248 return "yyyy";
249 case 'Z': // timezone
250 return "vvvv";
251 case '%': // %
252 return "%";
253 default: return "";
254 }
255 }
256
257 icu::UnicodeString strftime_to_icu(const icu::UnicodeString& ftime, const formatters_cache& cache)
258 {
259 const unsigned len = ftime.length();
260 icu::UnicodeString result;
261 bool escaped = false;
262 for(unsigned i = 0; i < len; i++) {
263 UChar c = ftime[i];
264 if(c == '%') {
265 i++;
266 c = ftime[i];
267 if(c == 'E' || c == 'O') {
268 i++;
269 c = ftime[i];
270 }
271 if(escaped) {
272 result += "'";
273 escaped = false;
274 }
275 result += strftime_symbol_to_icu(c, cache);
276 } else if(c == '\'')
277 result += "''";
278 else {
279 if(!escaped) {
280 result += "'";
281 escaped = true;
282 }
283 result += c;
284 }
285 }
286 if(escaped)
287 result += "'";
288 return result;
289 }
290
291 format_len time_flags_to_len(const uint64_t time_flags)
292 {
293 switch(time_flags) {
294 using namespace boost::locale::flags;
295 case time_short: return format_len::Short;
296 case time_medium: return format_len::Medium;
297 case time_long: return format_len::Long;
298 case time_full: return format_len::Full;
299 default: return format_len::Medium;
300 }
301 }
302 format_len date_flags_to_len(const uint64_t date_flags)
303 {
304 switch(date_flags) {
305 using namespace boost::locale::flags;
306 case date_short: return format_len::Short;
307 case date_medium: return format_len::Medium;
308 case date_long: return format_len::Long;
309 case date_full: return format_len::Full;
310 default: return format_len::Medium;
311 }
312 }
313 icu::DateFormat::EStyle time_flags_to_icu_len(const uint64_t time_flags)
314 {
315 switch(time_flags) {
316 using namespace boost::locale::flags;
317 case time_short: return icu::DateFormat::kShort;
318 case time_medium: return icu::DateFormat::kMedium;
319 case time_long: return icu::DateFormat::kLong;
320 case time_full: return icu::DateFormat::kFull;
321 case time_default:
322 default: return icu::DateFormat::kDefault;
323 }
324 }
325 icu::DateFormat::EStyle date_flags_to_icu_len(const uint64_t date_flags)
326 {
327 switch(date_flags) {
328 using namespace boost::locale::flags;
329 case date_short: return icu::DateFormat::kShort;
330 case date_medium: return icu::DateFormat::kMedium;
331 case date_long: return icu::DateFormat::kLong;
332 case date_full: return icu::DateFormat::kFull;
333 case date_default:
334 default: return icu::DateFormat::kDefault;
335 }
336 }
337
338 template<typename CharType>
339 std::unique_ptr<formatter<CharType>>
340 formatter<CharType>::create(std::ios_base& ios, const icu::Locale& locale, const std::string& encoding)
341 {
342 using ptr_type = std::unique_ptr<formatter<CharType>>;
343
344 const ios_info& info = ios_info::get(ios);
345 const formatters_cache& cache = std::use_facet<formatters_cache>(loc: ios.getloc());
346
347 const uint64_t disp = info.display_flags();
348 switch(disp) {
349 using namespace boost::locale::flags;
350 case posix:
351 BOOST_ASSERT_MSG(false, "Shouldn't try to create a posix formatter"); // LCOV_EXCL_LINE
352 break; // LCOV_EXCL_LINE
353 case number: {
354 const std::ios_base::fmtflags how = (ios.flags() & std::ios_base::floatfield);
355 icu::NumberFormat& nf =
356 cache.number_format(type: (how == std::ios_base::scientific) ? num_fmt_type::sci : num_fmt_type::number);
357 set_fraction_digits(nf, how, precision: ios.precision());
358 return ptr_type(new number_format<CharType>(nf, encoding));
359 }
360 case currency: {
361 icu::NumberFormat& nf = cache.number_format(
362 type: (info.currency_flags() == currency_iso) ? num_fmt_type::curr_iso : num_fmt_type::curr_nat);
363 return ptr_type(new number_format<CharType>(nf, encoding));
364 }
365 case percent: {
366 icu::NumberFormat& nf = cache.number_format(type: num_fmt_type::percent);
367 set_fraction_digits(nf, how: ios.flags() & std::ios_base::floatfield, precision: ios.precision());
368 return ptr_type(new number_format<CharType>(nf, encoding));
369 }
370 case spellout:
371 return ptr_type(new number_format<CharType>(cache.number_format(type: num_fmt_type::spell), encoding));
372 case ordinal:
373 return ptr_type(new number_format<CharType>(cache.number_format(type: num_fmt_type::ordinal), encoding));
374 case date:
375 case time:
376 case datetime:
377 case strftime: {
378 using namespace flags;
379 std::unique_ptr<icu::DateFormat> new_df;
380 icu::DateFormat* df = nullptr;
381 // try to use cached first
382 {
383 icu::SimpleDateFormat* sdf = cache.date_formatter();
384 if(sdf) {
385 icu::UnicodeString pattern;
386 switch(disp) {
387 case date: pattern = cache.date_format(f: date_flags_to_len(date_flags: info.date_flags())); break;
388 case time: pattern = cache.time_format(f: time_flags_to_len(time_flags: info.time_flags())); break;
389 case datetime:
390 pattern = cache.date_time_format(d: date_flags_to_len(date_flags: info.date_flags()),
391 t: time_flags_to_len(time_flags: info.time_flags()));
392 break;
393 case strftime: {
394 icu_std_converter<CharType> cvt_(encoding);
395 const std::basic_string<CharType> f = info.date_time_pattern<CharType>();
396 pattern = strftime_to_icu(cvt_.icu(f.c_str(), f.c_str() + f.size()), locale);
397 } break;
398 }
399 if(!pattern.isEmpty()) {
400 sdf->applyPattern(pattern);
401 df = sdf;
402 }
403 }
404 }
405
406 if(!df) {
407 switch(disp) {
408 case date:
409 new_df.reset(
410 p: icu::DateFormat::createDateInstance(style: date_flags_to_icu_len(date_flags: info.date_flags()), aLocale: locale));
411 break;
412 case time:
413 new_df.reset(
414 p: icu::DateFormat::createTimeInstance(style: time_flags_to_icu_len(time_flags: info.time_flags()), aLocale: locale));
415 break;
416 case datetime:
417 new_df.reset(
418 p: icu::DateFormat::createDateTimeInstance(dateStyle: date_flags_to_icu_len(date_flags: info.date_flags()),
419 timeStyle: time_flags_to_icu_len(time_flags: info.time_flags()),
420 aLocale: locale));
421 break;
422 case strftime: {
423 icu_std_converter<CharType> cvt_(encoding);
424 const std::basic_string<CharType> f = info.date_time_pattern<CharType>();
425 icu::UnicodeString pattern =
426 strftime_to_icu(cvt_.icu(f.data(), f.data() + f.size()), locale);
427 UErrorCode err = U_ZERO_ERROR;
428 new_df.reset(p: new icu::SimpleDateFormat(pattern, locale, err));
429 if(U_FAILURE(code: err))
430 return nullptr;
431 } break;
432 }
433 df = new_df.get();
434 BOOST_ASSERT_MSG(df, "Failed to create date/time formatter");
435 }
436
437 df->adoptTimeZone(zoneToAdopt: get_time_zone(time_zone: info.time_zone()));
438
439 // Depending if we own formatter or not
440 if(new_df)
441 return ptr_type(new date_format<CharType>(std::move(new_df), encoding));
442 else
443 return ptr_type(new date_format<CharType>(*df, encoding));
444 } break;
445 }
446
447 return nullptr; // LCOV_EXCL_LINE
448 }
449
450#define BOOST_LOCALE_INSTANTIATE(CHAR) template class formatter<CHAR>;
451 BOOST_LOCALE_FOREACH_CHAR_STRING(BOOST_LOCALE_INSTANTIATE)
452
453}}} // namespace boost::locale::impl_icu
454
455// boostinspect:nominmax
456

source code of boost/libs/locale/src/boost/locale/icu/formatter.cpp