]>
Commit | Line | Data |
---|---|---|
f3c0d7a5 A |
1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html | |
57a6839d A |
3 | /* |
4 | ****************************************************************************** | |
2ca993e8 A |
5 | * Copyright (C) 2014-2016, International Business Machines Corporation and |
6 | * others. All Rights Reserved. | |
57a6839d | 7 | ****************************************************************************** |
2ca993e8 A |
8 | * |
9 | * File reldatefmt.cpp | |
57a6839d A |
10 | ****************************************************************************** |
11 | */ | |
12 | ||
13 | #include "unicode/reldatefmt.h" | |
14 | ||
b331163b | 15 | #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION |
57a6839d | 16 | |
0f5d89e8 | 17 | #include <cmath> |
3d1f044b | 18 | #include <functional> |
2ca993e8 | 19 | #include "unicode/dtfmtsym.h" |
f3c0d7a5 | 20 | #include "unicode/ucasemap.h" |
2ca993e8 A |
21 | #include "unicode/ureldatefmt.h" |
22 | #include "unicode/udisplaycontext.h" | |
23 | #include "unicode/unum.h" | |
57a6839d | 24 | #include "unicode/localpointer.h" |
57a6839d | 25 | #include "unicode/plurrule.h" |
2ca993e8 | 26 | #include "unicode/simpleformatter.h" |
57a6839d A |
27 | #include "unicode/decimfmt.h" |
28 | #include "unicode/numfmt.h" | |
b331163b | 29 | #include "unicode/brkiter.h" |
2ca993e8 | 30 | #include "unicode/simpleformatter.h" |
57a6839d A |
31 | #include "uresimp.h" |
32 | #include "unicode/ures.h" | |
33 | #include "cstring.h" | |
34 | #include "ucln_in.h" | |
35 | #include "mutex.h" | |
36 | #include "charstr.h" | |
b331163b | 37 | #include "uassert.h" |
2ca993e8 A |
38 | #include "quantityformatter.h" |
39 | #include "resource.h" | |
b331163b | 40 | #include "sharedbreakiterator.h" |
57a6839d A |
41 | #include "sharedpluralrules.h" |
42 | #include "sharednumberformat.h" | |
2ca993e8 | 43 | #include "standardplural.h" |
b331163b | 44 | #include "unifiedcache.h" |
3d1f044b | 45 | #include "util.h" |
340931cb | 46 | #include "formatted_string_builder.h" |
3d1f044b A |
47 | #include "number_utypes.h" |
48 | #include "number_modifiers.h" | |
49 | #include "formattedval_impl.h" | |
50 | #include "number_utils.h" | |
57a6839d A |
51 | |
52 | // Copied from uscript_props.cpp | |
57a6839d | 53 | |
57a6839d A |
54 | U_NAMESPACE_BEGIN |
55 | ||
56 | // RelativeDateTimeFormatter specific data for a single locale | |
57 | class RelativeDateTimeCacheData: public SharedObject { | |
58 | public: | |
3d1f044b | 59 | RelativeDateTimeCacheData() : combinedDateAndTime(nullptr) { |
2ca993e8 A |
60 | // Initialize the cache arrays |
61 | for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) { | |
3d1f044b | 62 | for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) { |
2ca993e8 | 63 | for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) { |
3d1f044b A |
64 | relativeUnitsFormatters[style][relUnit][0][pl] = nullptr; |
65 | relativeUnitsFormatters[style][relUnit][1][pl] = nullptr; | |
2ca993e8 A |
66 | } |
67 | } | |
68 | } | |
69 | for (int32_t i = 0; i < UDAT_STYLE_COUNT; ++i) { | |
70 | fallBackCache[i] = -1; | |
71 | } | |
72 | } | |
57a6839d A |
73 | virtual ~RelativeDateTimeCacheData(); |
74 | ||
75 | // no numbers: e.g Next Tuesday; Yesterday; etc. | |
b331163b | 76 | UnicodeString absoluteUnits[UDAT_STYLE_COUNT][UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT]; |
57a6839d | 77 | |
2ca993e8 A |
78 | // SimpleFormatter pointers for relative unit format, |
79 | // e.g., Next Tuesday; Yesterday; etc. For third index, 0 | |
80 | // means past, e.g., 5 days ago; 1 means future, e.g., in 5 days. | |
81 | SimpleFormatter *relativeUnitsFormatters[UDAT_STYLE_COUNT] | |
3d1f044b | 82 | [UDAT_REL_UNIT_COUNT][2][StandardPlural::COUNT]; |
2ca993e8 A |
83 | |
84 | const UnicodeString& getAbsoluteUnitString(int32_t fStyle, | |
85 | UDateAbsoluteUnit unit, | |
86 | UDateDirection direction) const; | |
87 | const SimpleFormatter* getRelativeUnitFormatter(int32_t fStyle, | |
88 | UDateRelativeUnit unit, | |
89 | int32_t pastFutureIndex, | |
90 | int32_t pluralUnit) const; | |
3d1f044b A |
91 | const SimpleFormatter* getRelativeDateTimeUnitFormatter(int32_t fStyle, |
92 | URelativeDateTimeUnit unit, | |
93 | int32_t pastFutureIndex, | |
94 | int32_t pluralUnit) const; | |
2ca993e8 A |
95 | |
96 | const UnicodeString emptyString; | |
97 | ||
98 | // Mappping from source to target styles for alias fallback. | |
99 | int32_t fallBackCache[UDAT_STYLE_COUNT]; | |
57a6839d | 100 | |
2ca993e8 | 101 | void adoptCombinedDateAndTime(SimpleFormatter *fmtToAdopt) { |
57a6839d | 102 | delete combinedDateAndTime; |
2ca993e8 | 103 | combinedDateAndTime = fmtToAdopt; |
57a6839d | 104 | } |
2ca993e8 | 105 | const SimpleFormatter *getCombinedDateAndTime() const { |
57a6839d A |
106 | return combinedDateAndTime; |
107 | } | |
2ca993e8 | 108 | |
57a6839d | 109 | private: |
2ca993e8 | 110 | SimpleFormatter *combinedDateAndTime; |
57a6839d A |
111 | RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other); |
112 | RelativeDateTimeCacheData& operator=( | |
113 | const RelativeDateTimeCacheData &other); | |
114 | }; | |
115 | ||
116 | RelativeDateTimeCacheData::~RelativeDateTimeCacheData() { | |
2ca993e8 A |
117 | // clear out the cache arrays |
118 | for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) { | |
3d1f044b | 119 | for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) { |
2ca993e8 A |
120 | for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) { |
121 | delete relativeUnitsFormatters[style][relUnit][0][pl]; | |
122 | delete relativeUnitsFormatters[style][relUnit][1][pl]; | |
123 | } | |
124 | } | |
125 | } | |
57a6839d A |
126 | delete combinedDateAndTime; |
127 | } | |
128 | ||
2ca993e8 A |
129 | |
130 | // Use fallback cache for absolute units. | |
131 | const UnicodeString& RelativeDateTimeCacheData::getAbsoluteUnitString( | |
132 | int32_t fStyle, UDateAbsoluteUnit unit, UDateDirection direction) const { | |
133 | int32_t style = fStyle; | |
134 | do { | |
135 | if (!absoluteUnits[style][unit][direction].isEmpty()) { | |
136 | return absoluteUnits[style][unit][direction]; | |
137 | } | |
138 | style = fallBackCache[style]; | |
139 | } while (style != -1); | |
140 | return emptyString; | |
141 | } | |
142 | ||
2ca993e8 A |
143 | const SimpleFormatter* RelativeDateTimeCacheData::getRelativeUnitFormatter( |
144 | int32_t fStyle, | |
145 | UDateRelativeUnit unit, | |
146 | int32_t pastFutureIndex, | |
147 | int32_t pluralUnit) const { | |
3d1f044b A |
148 | URelativeDateTimeUnit rdtunit = UDAT_REL_UNIT_COUNT; |
149 | switch (unit) { | |
150 | case UDAT_RELATIVE_YEARS: rdtunit = UDAT_REL_UNIT_YEAR; break; | |
151 | case UDAT_RELATIVE_MONTHS: rdtunit = UDAT_REL_UNIT_MONTH; break; | |
152 | case UDAT_RELATIVE_WEEKS: rdtunit = UDAT_REL_UNIT_WEEK; break; | |
153 | case UDAT_RELATIVE_DAYS: rdtunit = UDAT_REL_UNIT_DAY; break; | |
154 | case UDAT_RELATIVE_HOURS: rdtunit = UDAT_REL_UNIT_HOUR; break; | |
155 | case UDAT_RELATIVE_MINUTES: rdtunit = UDAT_REL_UNIT_MINUTE; break; | |
156 | case UDAT_RELATIVE_SECONDS: rdtunit = UDAT_REL_UNIT_SECOND; break; | |
157 | default: // a unit that the above method does not handle | |
158 | return nullptr; | |
159 | } | |
160 | ||
161 | return getRelativeDateTimeUnitFormatter(fStyle, rdtunit, pastFutureIndex, pluralUnit); | |
162 | } | |
163 | ||
164 | // Use fallback cache for SimpleFormatter relativeUnits. | |
165 | const SimpleFormatter* RelativeDateTimeCacheData::getRelativeDateTimeUnitFormatter( | |
166 | int32_t fStyle, | |
167 | URelativeDateTimeUnit unit, | |
168 | int32_t pastFutureIndex, | |
169 | int32_t pluralUnit) const { | |
170 | while (true) { | |
171 | int32_t style = fStyle; | |
172 | do { | |
173 | if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) { | |
174 | return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit]; | |
175 | } | |
176 | style = fallBackCache[style]; | |
177 | } while (style != -1); | |
178 | ||
179 | if (pluralUnit == StandardPlural::OTHER) { | |
180 | break; | |
2ca993e8 | 181 | } |
3d1f044b A |
182 | pluralUnit = StandardPlural::OTHER; |
183 | } | |
184 | return nullptr; // No formatter found. | |
2ca993e8 A |
185 | } |
186 | ||
57a6839d | 187 | static UBool getStringWithFallback( |
2ca993e8 | 188 | const UResourceBundle *resource, |
57a6839d A |
189 | const char *key, |
190 | UnicodeString &result, | |
191 | UErrorCode &status) { | |
192 | int32_t len = 0; | |
193 | const UChar *resStr = ures_getStringByKeyWithFallback( | |
194 | resource, key, &len, &status); | |
195 | if (U_FAILURE(status)) { | |
196 | return FALSE; | |
197 | } | |
198 | result.setTo(TRUE, resStr, len); | |
199 | return TRUE; | |
200 | } | |
201 | ||
57a6839d A |
202 | |
203 | static UBool getStringByIndex( | |
2ca993e8 | 204 | const UResourceBundle *resource, |
57a6839d A |
205 | int32_t idx, |
206 | UnicodeString &result, | |
207 | UErrorCode &status) { | |
208 | int32_t len = 0; | |
209 | const UChar *resStr = ures_getStringByIndex( | |
210 | resource, idx, &len, &status); | |
211 | if (U_FAILURE(status)) { | |
212 | return FALSE; | |
213 | } | |
214 | result.setTo(TRUE, resStr, len); | |
215 | return TRUE; | |
216 | } | |
217 | ||
2ca993e8 | 218 | namespace { |
57a6839d | 219 | |
2ca993e8 A |
220 | /** |
221 | * Sink for enumerating all of the measurement unit display names. | |
2ca993e8 A |
222 | * |
223 | * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): | |
224 | * Only store a value if it is still missing, that is, it has not been overridden. | |
2ca993e8 | 225 | */ |
f3c0d7a5 | 226 | struct RelDateTimeFmtDataSink : public ResourceSink { |
2ca993e8 A |
227 | |
228 | /** | |
229 | * Sink for patterns for relative dates and times. For example, | |
230 | * fields/relative/... | |
231 | */ | |
232 | ||
233 | // Generic unit enum for storing Unit info. | |
234 | typedef enum RelAbsUnit { | |
235 | INVALID_UNIT = -1, | |
236 | SECOND, | |
237 | MINUTE, | |
238 | HOUR, | |
239 | DAY, | |
240 | WEEK, | |
241 | MONTH, | |
242 | QUARTER, | |
243 | YEAR, | |
244 | SUNDAY, | |
245 | MONDAY, | |
246 | TUESDAY, | |
247 | WEDNESDAY, | |
248 | THURSDAY, | |
249 | FRIDAY, | |
250 | SATURDAY | |
251 | } RelAbsUnit; | |
252 | ||
253 | static int32_t relUnitFromGeneric(RelAbsUnit genUnit) { | |
254 | // Converts the generic units to UDAT_RELATIVE version. | |
255 | switch (genUnit) { | |
256 | case SECOND: | |
3d1f044b | 257 | return UDAT_REL_UNIT_SECOND; |
2ca993e8 | 258 | case MINUTE: |
3d1f044b | 259 | return UDAT_REL_UNIT_MINUTE; |
2ca993e8 | 260 | case HOUR: |
3d1f044b | 261 | return UDAT_REL_UNIT_HOUR; |
2ca993e8 | 262 | case DAY: |
3d1f044b | 263 | return UDAT_REL_UNIT_DAY; |
2ca993e8 | 264 | case WEEK: |
3d1f044b | 265 | return UDAT_REL_UNIT_WEEK; |
2ca993e8 | 266 | case MONTH: |
3d1f044b A |
267 | return UDAT_REL_UNIT_MONTH; |
268 | case QUARTER: | |
269 | return UDAT_REL_UNIT_QUARTER; | |
2ca993e8 | 270 | case YEAR: |
3d1f044b A |
271 | return UDAT_REL_UNIT_YEAR; |
272 | case SUNDAY: | |
273 | return UDAT_REL_UNIT_SUNDAY; | |
274 | case MONDAY: | |
275 | return UDAT_REL_UNIT_MONDAY; | |
276 | case TUESDAY: | |
277 | return UDAT_REL_UNIT_TUESDAY; | |
278 | case WEDNESDAY: | |
279 | return UDAT_REL_UNIT_WEDNESDAY; | |
280 | case THURSDAY: | |
281 | return UDAT_REL_UNIT_THURSDAY; | |
282 | case FRIDAY: | |
283 | return UDAT_REL_UNIT_FRIDAY; | |
284 | case SATURDAY: | |
285 | return UDAT_REL_UNIT_SATURDAY; | |
2ca993e8 A |
286 | default: |
287 | return -1; | |
288 | } | |
57a6839d | 289 | } |
2ca993e8 A |
290 | |
291 | static int32_t absUnitFromGeneric(RelAbsUnit genUnit) { | |
292 | // Converts the generic units to UDAT_RELATIVE version. | |
293 | switch (genUnit) { | |
294 | case DAY: | |
295 | return UDAT_ABSOLUTE_DAY; | |
296 | case WEEK: | |
297 | return UDAT_ABSOLUTE_WEEK; | |
298 | case MONTH: | |
299 | return UDAT_ABSOLUTE_MONTH; | |
3d1f044b A |
300 | case QUARTER: |
301 | return UDAT_ABSOLUTE_QUARTER; | |
2ca993e8 A |
302 | case YEAR: |
303 | return UDAT_ABSOLUTE_YEAR; | |
304 | case SUNDAY: | |
305 | return UDAT_ABSOLUTE_SUNDAY; | |
306 | case MONDAY: | |
307 | return UDAT_ABSOLUTE_MONDAY; | |
308 | case TUESDAY: | |
309 | return UDAT_ABSOLUTE_TUESDAY; | |
310 | case WEDNESDAY: | |
311 | return UDAT_ABSOLUTE_WEDNESDAY; | |
312 | case THURSDAY: | |
313 | return UDAT_ABSOLUTE_THURSDAY; | |
314 | case FRIDAY: | |
315 | return UDAT_ABSOLUTE_FRIDAY; | |
316 | case SATURDAY: | |
317 | return UDAT_ABSOLUTE_SATURDAY; | |
340931cb A |
318 | case HOUR: |
319 | return UDAT_ABSOLUTE_HOUR; | |
320 | case MINUTE: | |
321 | return UDAT_ABSOLUTE_MINUTE; | |
2ca993e8 A |
322 | default: |
323 | return -1; | |
57a6839d | 324 | } |
2ca993e8 A |
325 | } |
326 | ||
327 | static int32_t keyToDirection(const char* key) { | |
328 | if (uprv_strcmp(key, "-2") == 0) { | |
329 | return UDAT_DIRECTION_LAST_2; | |
57a6839d | 330 | } |
2ca993e8 A |
331 | if (uprv_strcmp(key, "-1") == 0) { |
332 | return UDAT_DIRECTION_LAST; | |
333 | } | |
334 | if (uprv_strcmp(key, "0") == 0) { | |
335 | return UDAT_DIRECTION_THIS; | |
57a6839d | 336 | } |
2ca993e8 A |
337 | if (uprv_strcmp(key, "1") == 0) { |
338 | return UDAT_DIRECTION_NEXT; | |
339 | } | |
340 | if (uprv_strcmp(key, "2") == 0) { | |
341 | return UDAT_DIRECTION_NEXT_2; | |
342 | } | |
343 | return -1; | |
57a6839d | 344 | } |
57a6839d | 345 | |
2ca993e8 A |
346 | // Values kept between levels of parsing the CLDR data. |
347 | int32_t pastFutureIndex; // 0 == past or 1 == future | |
348 | UDateRelativeDateTimeFormatterStyle style; // {LONG, SHORT, NARROW} | |
349 | RelAbsUnit genericUnit; | |
2ca993e8 A |
350 | |
351 | RelativeDateTimeCacheData &outputData; | |
352 | ||
353 | // Constructor | |
f3c0d7a5 A |
354 | RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData) |
355 | : outputData(cacheData) { | |
2ca993e8 A |
356 | // Clear cacheData.fallBackCache |
357 | cacheData.fallBackCache[UDAT_STYLE_LONG] = -1; | |
358 | cacheData.fallBackCache[UDAT_STYLE_SHORT] = -1; | |
359 | cacheData.fallBackCache[UDAT_STYLE_NARROW] = -1; | |
57a6839d | 360 | } |
2ca993e8 A |
361 | |
362 | ~RelDateTimeFmtDataSink(); | |
363 | ||
364 | // Utility functions | |
365 | static UDateRelativeDateTimeFormatterStyle styleFromString(const char *s) { | |
3d1f044b | 366 | int32_t len = static_cast<int32_t>(uprv_strlen(s)); |
2ca993e8 A |
367 | if (len >= 7 && uprv_strcmp(s + len - 7, "-narrow") == 0) { |
368 | return UDAT_STYLE_NARROW; | |
57a6839d | 369 | } |
2ca993e8 A |
370 | if (len >= 6 && uprv_strcmp(s + len - 6, "-short") == 0) { |
371 | return UDAT_STYLE_SHORT; | |
372 | } | |
373 | return UDAT_STYLE_LONG; | |
57a6839d | 374 | } |
57a6839d | 375 | |
2ca993e8 A |
376 | static int32_t styleSuffixLength(UDateRelativeDateTimeFormatterStyle style) { |
377 | switch (style) { | |
378 | case UDAT_STYLE_NARROW: | |
379 | return 7; | |
380 | case UDAT_STYLE_SHORT: | |
381 | return 6; | |
382 | default: | |
383 | return 0; | |
384 | } | |
57a6839d | 385 | } |
57a6839d | 386 | |
2ca993e8 A |
387 | // Utility functions |
388 | static UDateRelativeDateTimeFormatterStyle styleFromAliasUnicodeString(UnicodeString s) { | |
389 | static const UChar narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077}; | |
390 | static const UChar sshort[6] = {0x002D, 0x0073, 0x0068, 0x006F, 0x0072, 0x0074,}; | |
391 | if (s.endsWith(narrow, 7)) { | |
392 | return UDAT_STYLE_NARROW; | |
393 | } | |
394 | if (s.endsWith(sshort, 6)) { | |
395 | return UDAT_STYLE_SHORT; | |
396 | } | |
397 | return UDAT_STYLE_LONG; | |
b331163b | 398 | } |
2ca993e8 A |
399 | |
400 | static RelAbsUnit unitOrNegativeFromString(const char* keyword, int32_t length) { | |
401 | // Quick check from string to enum. | |
402 | switch (length) { | |
403 | case 3: | |
404 | if (uprv_strncmp(keyword, "day", length) == 0) { | |
405 | return DAY; | |
406 | } else if (uprv_strncmp(keyword, "sun", length) == 0) { | |
407 | return SUNDAY; | |
408 | } else if (uprv_strncmp(keyword, "mon", length) == 0) { | |
409 | return MONDAY; | |
410 | } else if (uprv_strncmp(keyword, "tue", length) == 0) { | |
411 | return TUESDAY; | |
412 | } else if (uprv_strncmp(keyword, "wed", length) == 0) { | |
413 | return WEDNESDAY; | |
414 | } else if (uprv_strncmp(keyword, "thu", length) == 0) { | |
415 | return THURSDAY; | |
416 | } else if (uprv_strncmp(keyword, "fri", length) == 0) { | |
417 | return FRIDAY; | |
418 | } else if (uprv_strncmp(keyword, "sat", length) == 0) { | |
419 | return SATURDAY; | |
420 | } | |
421 | break; | |
422 | case 4: | |
423 | if (uprv_strncmp(keyword, "hour", length) == 0) { | |
424 | return HOUR; | |
425 | } else if (uprv_strncmp(keyword, "week", length) == 0) { | |
426 | return WEEK; | |
427 | } else if (uprv_strncmp(keyword, "year", length) == 0) { | |
428 | return YEAR; | |
429 | } | |
430 | break; | |
431 | case 5: | |
432 | if (uprv_strncmp(keyword, "month", length) == 0) { | |
433 | return MONTH; | |
434 | } | |
435 | break; | |
436 | case 6: | |
437 | if (uprv_strncmp(keyword, "minute", length) == 0) { | |
438 | return MINUTE; | |
439 | } else if (uprv_strncmp(keyword, "second", length) == 0) { | |
440 | return SECOND; | |
441 | } | |
442 | break; | |
443 | case 7: | |
444 | if (uprv_strncmp(keyword, "quarter", length) == 0) { | |
445 | return QUARTER; // TODO: Check @provisional | |
446 | } | |
447 | break; | |
448 | default: | |
449 | break; | |
450 | } | |
451 | return INVALID_UNIT; | |
b331163b | 452 | } |
b331163b | 453 | |
f3c0d7a5 A |
454 | void handlePlainDirection(ResourceValue &value, UErrorCode &errorCode) { |
455 | // Handle Display Name for PLAIN direction for some units. | |
456 | if (U_FAILURE(errorCode)) { return; } | |
457 | ||
458 | int32_t absUnit = absUnitFromGeneric(genericUnit); | |
459 | if (absUnit < 0) { | |
460 | return; // Not interesting. | |
461 | } | |
462 | ||
463 | // Store displayname if not set. | |
464 | if (outputData.absoluteUnits[style] | |
465 | [absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) { | |
466 | outputData.absoluteUnits[style] | |
467 | [absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode)); | |
468 | return; | |
469 | } | |
470 | } | |
471 | ||
472 | void consumeTableRelative(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
473 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
474 | if (U_FAILURE(errorCode)) { return; } | |
2ca993e8 | 475 | |
f3c0d7a5 A |
476 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { |
477 | if (value.getType() == URES_STRING) { | |
478 | int32_t direction = keyToDirection(key); | |
479 | if (direction < 0) { | |
480 | continue; | |
481 | } | |
482 | ||
483 | int32_t relUnitIndex = relUnitFromGeneric(genericUnit); | |
3d1f044b | 484 | if (relUnitIndex == UDAT_REL_UNIT_SECOND && uprv_strcmp(key, "0") == 0 && |
f3c0d7a5 A |
485 | outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN].isEmpty()) { |
486 | // Handle "NOW" | |
487 | outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW] | |
488 | [UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode)); | |
489 | } | |
490 | ||
491 | int32_t absUnitIndex = absUnitFromGeneric(genericUnit); | |
492 | if (absUnitIndex < 0) { | |
493 | continue; | |
494 | } | |
495 | // Only reset if slot is empty. | |
496 | if (outputData.absoluteUnits[style][absUnitIndex][direction].isEmpty()) { | |
497 | outputData.absoluteUnits[style][absUnitIndex] | |
498 | [direction].fastCopyFrom(value.getUnicodeString(errorCode)); | |
499 | } | |
2ca993e8 | 500 | } |
f3c0d7a5 A |
501 | } |
502 | } | |
503 | ||
504 | void consumeTimeDetail(int32_t relUnitIndex, | |
505 | const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
506 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
507 | if (U_FAILURE(errorCode)) { return; } | |
508 | ||
509 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { | |
510 | if (value.getType() == URES_STRING) { | |
511 | int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key); | |
512 | if (pluralIndex >= 0) { | |
513 | SimpleFormatter **patterns = | |
514 | outputData.relativeUnitsFormatters[style][relUnitIndex] | |
515 | [pastFutureIndex]; | |
516 | // Only set if not already established. | |
3d1f044b | 517 | if (patterns[pluralIndex] == nullptr) { |
f3c0d7a5 A |
518 | patterns[pluralIndex] = new SimpleFormatter( |
519 | value.getUnicodeString(errorCode), 0, 1, errorCode); | |
3d1f044b | 520 | if (patterns[pluralIndex] == nullptr) { |
f3c0d7a5 A |
521 | errorCode = U_MEMORY_ALLOCATION_ERROR; |
522 | } | |
523 | } | |
2ca993e8 | 524 | } |
f3c0d7a5 A |
525 | } |
526 | } | |
527 | } | |
528 | ||
529 | void consumeTableRelativeTime(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
530 | ResourceTable relativeTimeTable = value.getTable(errorCode); | |
531 | if (U_FAILURE(errorCode)) { return; } | |
532 | ||
533 | int32_t relUnitIndex = relUnitFromGeneric(genericUnit); | |
534 | if (relUnitIndex < 0) { | |
535 | return; | |
536 | } | |
537 | for (int32_t i = 0; relativeTimeTable.getKeyAndValue(i, key, value); ++i) { | |
538 | if (uprv_strcmp(key, "past") == 0) { | |
539 | pastFutureIndex = 0; | |
540 | } else if (uprv_strcmp(key, "future") == 0) { | |
541 | pastFutureIndex = 1; | |
542 | } else { | |
543 | // Unknown key. | |
544 | continue; | |
545 | } | |
546 | consumeTimeDetail(relUnitIndex, key, value, errorCode); | |
547 | } | |
548 | } | |
549 | ||
550 | void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) { | |
551 | ||
552 | UDateRelativeDateTimeFormatterStyle sourceStyle = styleFromString(key); | |
553 | const UnicodeString valueStr = value.getAliasUnicodeString(errorCode); | |
554 | if (U_FAILURE(errorCode)) { return; } | |
555 | ||
556 | UDateRelativeDateTimeFormatterStyle targetStyle = | |
557 | styleFromAliasUnicodeString(valueStr); | |
558 | ||
559 | if (sourceStyle == targetStyle) { | |
560 | errorCode = U_INVALID_FORMAT_ERROR; | |
561 | return; | |
562 | } | |
563 | if (outputData.fallBackCache[sourceStyle] != -1 && | |
564 | outputData.fallBackCache[sourceStyle] != targetStyle) { | |
565 | errorCode = U_INVALID_FORMAT_ERROR; | |
566 | return; | |
567 | } | |
568 | outputData.fallBackCache[sourceStyle] = targetStyle; | |
569 | } | |
570 | ||
571 | void consumeTimeUnit(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
572 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
573 | if (U_FAILURE(errorCode)) { return; } | |
574 | ||
575 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { | |
576 | // Handle display name. | |
577 | if (uprv_strcmp(key, "dn") == 0 && value.getType() == URES_STRING) { | |
578 | handlePlainDirection(value, errorCode); | |
579 | } | |
580 | if (value.getType() == URES_TABLE) { | |
581 | if (uprv_strcmp(key, "relative") == 0) { | |
582 | consumeTableRelative(key, value, errorCode); | |
583 | } else if (uprv_strcmp(key, "relativeTime") == 0) { | |
584 | consumeTableRelativeTime(key, value, errorCode); | |
2ca993e8 | 585 | } |
2ca993e8 A |
586 | } |
587 | } | |
b331163b | 588 | } |
b331163b | 589 | |
f3c0d7a5 A |
590 | virtual void put(const char *key, ResourceValue &value, |
591 | UBool /*noFallback*/, UErrorCode &errorCode) { | |
592 | // Main entry point to sink | |
593 | ResourceTable table = value.getTable(errorCode); | |
594 | if (U_FAILURE(errorCode)) { return; } | |
595 | for (int32_t i = 0; table.getKeyAndValue(i, key, value); ++i) { | |
596 | if (value.getType() == URES_ALIAS) { | |
597 | consumeAlias(key, value, errorCode); | |
598 | } else { | |
599 | style = styleFromString(key); | |
3d1f044b | 600 | int32_t unitSize = static_cast<int32_t>(uprv_strlen(key)) - styleSuffixLength(style); |
f3c0d7a5 A |
601 | genericUnit = unitOrNegativeFromString(key, unitSize); |
602 | if (style >= 0 && genericUnit != INVALID_UNIT) { | |
603 | consumeTimeUnit(key, value, errorCode); | |
604 | } | |
605 | } | |
606 | } | |
b331163b | 607 | } |
f3c0d7a5 | 608 | |
2ca993e8 A |
609 | }; |
610 | ||
611 | // Virtual destructors must be defined out of line. | |
2ca993e8 | 612 | RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {} |
2ca993e8 A |
613 | } // namespace |
614 | ||
0f5d89e8 | 615 | static const DateFormatSymbols::DtWidthType styleToDateFormatSymbolWidth[UDAT_STYLE_COUNT] = { |
2ca993e8 A |
616 | DateFormatSymbols::WIDE, DateFormatSymbols::SHORT, DateFormatSymbols::NARROW |
617 | }; | |
618 | ||
619 | // Get days of weeks from the DateFormatSymbols class. | |
620 | static void loadWeekdayNames(UnicodeString absoluteUnits[UDAT_STYLE_COUNT] | |
621 | [UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT], | |
622 | const char* localeId, | |
623 | UErrorCode& status) { | |
3d1f044b A |
624 | if (U_FAILURE(status)) { |
625 | return; | |
626 | } | |
2ca993e8 A |
627 | Locale locale(localeId); |
628 | DateFormatSymbols dfSym(locale, status); | |
3d1f044b A |
629 | if (U_FAILURE(status)) { |
630 | return; | |
631 | } | |
2ca993e8 A |
632 | for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) { |
633 | DateFormatSymbols::DtWidthType dtfmtWidth = styleToDateFormatSymbolWidth[style]; | |
634 | int32_t count; | |
635 | const UnicodeString* weekdayNames = | |
636 | dfSym.getWeekdays(count, DateFormatSymbols::STANDALONE, dtfmtWidth); | |
637 | for (int32_t dayIndex = UDAT_ABSOLUTE_SUNDAY; | |
638 | dayIndex <= UDAT_ABSOLUTE_SATURDAY; ++ dayIndex) { | |
639 | int32_t dateSymbolIndex = (dayIndex - UDAT_ABSOLUTE_SUNDAY) + UCAL_SUNDAY; | |
640 | absoluteUnits[style][dayIndex][UDAT_DIRECTION_PLAIN].fastCopyFrom( | |
641 | weekdayNames[dateSymbolIndex]); | |
642 | } | |
b331163b A |
643 | } |
644 | } | |
645 | ||
646 | static UBool loadUnitData( | |
647 | const UResourceBundle *resource, | |
648 | RelativeDateTimeCacheData &cacheData, | |
2ca993e8 | 649 | const char* localeId, |
b331163b | 650 | UErrorCode &status) { |
f3c0d7a5 A |
651 | |
652 | RelDateTimeFmtDataSink sink(cacheData); | |
653 | ||
654 | ures_getAllItemsWithFallback(resource, "fields", sink, status); | |
3d1f044b A |
655 | if (U_FAILURE(status)) { |
656 | return false; | |
657 | } | |
2ca993e8 A |
658 | |
659 | // Get the weekday names from DateFormatSymbols. | |
660 | loadWeekdayNames(cacheData.absoluteUnits, localeId, status); | |
57a6839d A |
661 | return U_SUCCESS(status); |
662 | } | |
663 | ||
664 | static UBool getDateTimePattern( | |
665 | const UResourceBundle *resource, | |
666 | UnicodeString &result, | |
667 | UErrorCode &status) { | |
668 | UnicodeString defaultCalendarName; | |
669 | if (!getStringWithFallback( | |
670 | resource, | |
671 | "calendar/default", | |
672 | defaultCalendarName, | |
673 | status)) { | |
674 | return FALSE; | |
675 | } | |
676 | CharString pathBuffer; | |
677 | pathBuffer.append("calendar/", status) | |
678 | .appendInvariantChars(defaultCalendarName, status) | |
679 | .append("/DateTimePatterns", status); | |
680 | LocalUResourceBundlePointer topLevel( | |
681 | ures_getByKeyWithFallback( | |
3d1f044b | 682 | resource, pathBuffer.data(), nullptr, &status)); |
57a6839d A |
683 | if (U_FAILURE(status)) { |
684 | return FALSE; | |
685 | } | |
686 | int32_t size = ures_getSize(topLevel.getAlias()); | |
687 | if (size <= 8) { | |
2ca993e8 | 688 | // Oops, size is too small to access the index that we want, fallback |
57a6839d A |
689 | // to a hard-coded value. |
690 | result = UNICODE_STRING_SIMPLE("{1} {0}"); | |
691 | return TRUE; | |
692 | } | |
693 | return getStringByIndex(topLevel.getAlias(), 8, result, status); | |
694 | } | |
695 | ||
b331163b A |
696 | template<> U_I18N_API |
697 | const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::createObject(const void * /*unused*/, UErrorCode &status) const { | |
698 | const char *localeId = fLoc.getName(); | |
3d1f044b | 699 | LocalUResourceBundlePointer topLevel(ures_open(nullptr, localeId, &status)); |
57a6839d | 700 | if (U_FAILURE(status)) { |
3d1f044b | 701 | return nullptr; |
57a6839d A |
702 | } |
703 | LocalPointer<RelativeDateTimeCacheData> result( | |
704 | new RelativeDateTimeCacheData()); | |
705 | if (result.isNull()) { | |
706 | status = U_MEMORY_ALLOCATION_ERROR; | |
3d1f044b | 707 | return nullptr; |
57a6839d A |
708 | } |
709 | if (!loadUnitData( | |
710 | topLevel.getAlias(), | |
711 | *result, | |
2ca993e8 | 712 | localeId, |
57a6839d | 713 | status)) { |
3d1f044b | 714 | return nullptr; |
57a6839d A |
715 | } |
716 | UnicodeString dateTimePattern; | |
717 | if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) { | |
3d1f044b | 718 | return nullptr; |
57a6839d A |
719 | } |
720 | result->adoptCombinedDateAndTime( | |
340931cb | 721 | new SimpleFormatter(dateTimePattern, 2, 2, TRUE, status)); |
57a6839d | 722 | if (U_FAILURE(status)) { |
3d1f044b | 723 | return nullptr; |
57a6839d | 724 | } |
b331163b | 725 | result->addRef(); |
57a6839d A |
726 | return result.orphan(); |
727 | } | |
728 | ||
3d1f044b A |
729 | |
730 | ||
731 | static constexpr number::impl::Field kRDTNumericField | |
340931cb | 732 | = StringBuilderFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD>(); |
3d1f044b A |
733 | |
734 | static constexpr number::impl::Field kRDTLiteralField | |
340931cb | 735 | = StringBuilderFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD>(); |
3d1f044b | 736 | |
340931cb | 737 | class FormattedRelativeDateTimeData : public FormattedValueStringBuilderImpl { |
3d1f044b | 738 | public: |
340931cb | 739 | FormattedRelativeDateTimeData() : FormattedValueStringBuilderImpl(kRDTNumericField) {} |
3d1f044b A |
740 | virtual ~FormattedRelativeDateTimeData(); |
741 | }; | |
742 | ||
743 | FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default; | |
744 | ||
745 | ||
746 | UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime) | |
747 | ||
748 | ||
b331163b | 749 | RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) : |
3d1f044b A |
750 | fCache(nullptr), |
751 | fNumberFormat(nullptr), | |
752 | fPluralRules(nullptr), | |
b331163b A |
753 | fStyle(UDAT_STYLE_LONG), |
754 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b A |
755 | fOptBreakIterator(nullptr) { |
756 | init(nullptr, nullptr, status); | |
57a6839d A |
757 | } |
758 | ||
b331163b A |
759 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( |
760 | const Locale& locale, UErrorCode& status) : | |
3d1f044b A |
761 | fCache(nullptr), |
762 | fNumberFormat(nullptr), | |
763 | fPluralRules(nullptr), | |
b331163b A |
764 | fStyle(UDAT_STYLE_LONG), |
765 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b | 766 | fOptBreakIterator(nullptr), |
b331163b | 767 | fLocale(locale) { |
3d1f044b | 768 | init(nullptr, nullptr, status); |
57a6839d A |
769 | } |
770 | ||
771 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
b331163b | 772 | const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) : |
3d1f044b A |
773 | fCache(nullptr), |
774 | fNumberFormat(nullptr), | |
775 | fPluralRules(nullptr), | |
b331163b A |
776 | fStyle(UDAT_STYLE_LONG), |
777 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b | 778 | fOptBreakIterator(nullptr), |
b331163b | 779 | fLocale(locale) { |
3d1f044b | 780 | init(nfToAdopt, nullptr, status); |
57a6839d A |
781 | } |
782 | ||
783 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
b331163b A |
784 | const Locale& locale, |
785 | NumberFormat *nfToAdopt, | |
786 | UDateRelativeDateTimeFormatterStyle styl, | |
787 | UDisplayContext capitalizationContext, | |
788 | UErrorCode& status) : | |
3d1f044b A |
789 | fCache(nullptr), |
790 | fNumberFormat(nullptr), | |
791 | fPluralRules(nullptr), | |
b331163b A |
792 | fStyle(styl), |
793 | fContext(capitalizationContext), | |
3d1f044b | 794 | fOptBreakIterator(nullptr), |
b331163b A |
795 | fLocale(locale) { |
796 | if (U_FAILURE(status)) { | |
797 | return; | |
798 | } | |
799 | if ((capitalizationContext >> 8) != UDISPCTX_TYPE_CAPITALIZATION) { | |
800 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
801 | return; | |
802 | } | |
803 | if (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) { | |
804 | BreakIterator *bi = BreakIterator::createSentenceInstance(locale, status); | |
805 | if (U_FAILURE(status)) { | |
806 | return; | |
807 | } | |
808 | init(nfToAdopt, bi, status); | |
809 | } else { | |
3d1f044b | 810 | init(nfToAdopt, nullptr, status); |
b331163b | 811 | } |
57a6839d A |
812 | } |
813 | ||
814 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
815 | const RelativeDateTimeFormatter& other) | |
b331163b A |
816 | : UObject(other), |
817 | fCache(other.fCache), | |
818 | fNumberFormat(other.fNumberFormat), | |
819 | fPluralRules(other.fPluralRules), | |
820 | fStyle(other.fStyle), | |
821 | fContext(other.fContext), | |
822 | fOptBreakIterator(other.fOptBreakIterator), | |
823 | fLocale(other.fLocale) { | |
824 | fCache->addRef(); | |
825 | fNumberFormat->addRef(); | |
826 | fPluralRules->addRef(); | |
3d1f044b | 827 | if (fOptBreakIterator != nullptr) { |
b331163b A |
828 | fOptBreakIterator->addRef(); |
829 | } | |
57a6839d A |
830 | } |
831 | ||
832 | RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=( | |
833 | const RelativeDateTimeFormatter& other) { | |
834 | if (this != &other) { | |
b331163b A |
835 | SharedObject::copyPtr(other.fCache, fCache); |
836 | SharedObject::copyPtr(other.fNumberFormat, fNumberFormat); | |
837 | SharedObject::copyPtr(other.fPluralRules, fPluralRules); | |
838 | SharedObject::copyPtr(other.fOptBreakIterator, fOptBreakIterator); | |
839 | fStyle = other.fStyle; | |
840 | fContext = other.fContext; | |
841 | fLocale = other.fLocale; | |
57a6839d A |
842 | } |
843 | return *this; | |
844 | } | |
845 | ||
846 | RelativeDateTimeFormatter::~RelativeDateTimeFormatter() { | |
3d1f044b | 847 | if (fCache != nullptr) { |
b331163b A |
848 | fCache->removeRef(); |
849 | } | |
3d1f044b | 850 | if (fNumberFormat != nullptr) { |
b331163b | 851 | fNumberFormat->removeRef(); |
57a6839d | 852 | } |
3d1f044b | 853 | if (fPluralRules != nullptr) { |
b331163b | 854 | fPluralRules->removeRef(); |
57a6839d | 855 | } |
3d1f044b | 856 | if (fOptBreakIterator != nullptr) { |
b331163b | 857 | fOptBreakIterator->removeRef(); |
57a6839d A |
858 | } |
859 | } | |
860 | ||
861 | const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const { | |
b331163b A |
862 | return **fNumberFormat; |
863 | } | |
864 | ||
865 | UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const { | |
866 | return fContext; | |
867 | } | |
868 | ||
869 | UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const { | |
870 | return fStyle; | |
57a6839d A |
871 | } |
872 | ||
3d1f044b A |
873 | |
874 | // To reduce boilerplate code, we use a helper function that forwards variadic | |
875 | // arguments to the formatImpl function. | |
876 | ||
877 | template<typename F, typename... Args> | |
878 | UnicodeString& RelativeDateTimeFormatter::doFormat( | |
879 | F callback, | |
880 | UnicodeString& appendTo, | |
881 | UErrorCode& status, | |
882 | Args... args) const { | |
883 | FormattedRelativeDateTimeData output; | |
884 | (this->*callback)(std::forward<Args>(args)..., output, status); | |
57a6839d A |
885 | if (U_FAILURE(status)) { |
886 | return appendTo; | |
887 | } | |
3d1f044b A |
888 | UnicodeString result = output.getStringRef().toUnicodeString(); |
889 | return appendTo.append(adjustForContext(result)); | |
890 | } | |
891 | ||
892 | template<typename F, typename... Args> | |
893 | FormattedRelativeDateTime RelativeDateTimeFormatter::doFormatToValue( | |
894 | F callback, | |
895 | UErrorCode& status, | |
896 | Args... args) const { | |
897 | if (!checkNoAdjustForContext(status)) { | |
898 | return FormattedRelativeDateTime(status); | |
899 | } | |
900 | LocalPointer<FormattedRelativeDateTimeData> output( | |
901 | new FormattedRelativeDateTimeData(), status); | |
902 | if (U_FAILURE(status)) { | |
903 | return FormattedRelativeDateTime(status); | |
904 | } | |
905 | (this->*callback)(std::forward<Args>(args)..., *output, status); | |
906 | output->getStringRef().writeTerminator(status); | |
907 | return FormattedRelativeDateTime(output.orphan()); | |
908 | } | |
909 | ||
910 | UnicodeString& RelativeDateTimeFormatter::format( | |
911 | double quantity, | |
912 | UDateDirection direction, | |
913 | UDateRelativeUnit unit, | |
914 | UnicodeString& appendTo, | |
915 | UErrorCode& status) const { | |
916 | return doFormat( | |
917 | &RelativeDateTimeFormatter::formatImpl, | |
918 | appendTo, | |
919 | status, | |
920 | quantity, | |
921 | direction, | |
922 | unit); | |
923 | } | |
924 | ||
925 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
926 | double quantity, | |
927 | UDateDirection direction, | |
928 | UDateRelativeUnit unit, | |
929 | UErrorCode& status) const { | |
930 | return doFormatToValue( | |
931 | &RelativeDateTimeFormatter::formatImpl, | |
932 | status, | |
933 | quantity, | |
934 | direction, | |
935 | unit); | |
936 | } | |
937 | ||
938 | void RelativeDateTimeFormatter::formatImpl( | |
939 | double quantity, | |
940 | UDateDirection direction, | |
941 | UDateRelativeUnit unit, | |
942 | FormattedRelativeDateTimeData& output, | |
943 | UErrorCode& status) const { | |
944 | if (U_FAILURE(status)) { | |
945 | return; | |
946 | } | |
57a6839d A |
947 | if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) { |
948 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
3d1f044b | 949 | return; |
57a6839d A |
950 | } |
951 | int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0; | |
2ca993e8 | 952 | |
3d1f044b A |
953 | StandardPlural::Form pluralForm; |
954 | QuantityFormatter::formatAndSelect( | |
955 | quantity, | |
956 | **fNumberFormat, | |
957 | **fPluralRules, | |
958 | output.getStringRef(), | |
959 | pluralForm, | |
2ca993e8 | 960 | status); |
3d1f044b A |
961 | if (U_FAILURE(status)) { |
962 | return; | |
963 | } | |
2ca993e8 A |
964 | |
965 | const SimpleFormatter* formatter = | |
3d1f044b A |
966 | fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralForm); |
967 | if (formatter == nullptr) { | |
2ca993e8 A |
968 | // TODO: WARN - look at quantity formatter's action with an error. |
969 | status = U_INVALID_FORMAT_ERROR; | |
3d1f044b | 970 | return; |
2ca993e8 | 971 | } |
3d1f044b A |
972 | |
973 | number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false); | |
974 | modifier.formatAsPrefixSuffix( | |
975 | output.getStringRef(), 0, output.getStringRef().length(), status); | |
57a6839d A |
976 | } |
977 | ||
2ca993e8 | 978 | UnicodeString& RelativeDateTimeFormatter::formatNumeric( |
3d1f044b A |
979 | double offset, |
980 | URelativeDateTimeUnit unit, | |
981 | UnicodeString& appendTo, | |
982 | UErrorCode& status) const { | |
983 | return doFormat( | |
984 | &RelativeDateTimeFormatter::formatNumericImpl, | |
985 | appendTo, | |
986 | status, | |
987 | offset, | |
988 | unit); | |
989 | } | |
990 | ||
991 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatNumericToValue( | |
992 | double offset, | |
993 | URelativeDateTimeUnit unit, | |
994 | UErrorCode& status) const { | |
995 | return doFormatToValue( | |
996 | &RelativeDateTimeFormatter::formatNumericImpl, | |
997 | status, | |
998 | offset, | |
999 | unit); | |
1000 | } | |
1001 | ||
1002 | void RelativeDateTimeFormatter::formatNumericImpl( | |
1003 | double offset, | |
1004 | URelativeDateTimeUnit unit, | |
1005 | FormattedRelativeDateTimeData& output, | |
1006 | UErrorCode& status) const { | |
2ca993e8 | 1007 | if (U_FAILURE(status)) { |
3d1f044b | 1008 | return; |
2ca993e8 A |
1009 | } |
1010 | UDateDirection direction = UDAT_DIRECTION_NEXT; | |
0f5d89e8 | 1011 | if (std::signbit(offset)) { // needed to handle -0.0 |
2ca993e8 A |
1012 | direction = UDAT_DIRECTION_LAST; |
1013 | offset = -offset; | |
1014 | } | |
3d1f044b A |
1015 | if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) { |
1016 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
1017 | return; | |
1018 | } | |
1019 | int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0; | |
1020 | ||
1021 | StandardPlural::Form pluralForm; | |
1022 | QuantityFormatter::formatAndSelect( | |
1023 | offset, | |
1024 | **fNumberFormat, | |
1025 | **fPluralRules, | |
1026 | output.getStringRef(), | |
1027 | pluralForm, | |
1028 | status); | |
1029 | if (U_FAILURE(status)) { | |
1030 | return; | |
1031 | } | |
1032 | ||
1033 | const SimpleFormatter* formatter = | |
1034 | fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralForm); | |
1035 | if (formatter == nullptr) { | |
1036 | // TODO: WARN - look at quantity formatter's action with an error. | |
1037 | status = U_INVALID_FORMAT_ERROR; | |
1038 | return; | |
1039 | } | |
1040 | ||
1041 | number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false); | |
1042 | modifier.formatAsPrefixSuffix( | |
1043 | output.getStringRef(), 0, output.getStringRef().length(), status); | |
2ca993e8 A |
1044 | } |
1045 | ||
57a6839d | 1046 | UnicodeString& RelativeDateTimeFormatter::format( |
3d1f044b A |
1047 | UDateDirection direction, |
1048 | UDateAbsoluteUnit unit, | |
1049 | UnicodeString& appendTo, | |
1050 | UErrorCode& status) const { | |
1051 | return doFormat( | |
1052 | &RelativeDateTimeFormatter::formatAbsoluteImpl, | |
1053 | appendTo, | |
1054 | status, | |
1055 | direction, | |
1056 | unit); | |
1057 | } | |
1058 | ||
1059 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
1060 | UDateDirection direction, | |
1061 | UDateAbsoluteUnit unit, | |
1062 | UErrorCode& status) const { | |
1063 | return doFormatToValue( | |
1064 | &RelativeDateTimeFormatter::formatAbsoluteImpl, | |
1065 | status, | |
1066 | direction, | |
1067 | unit); | |
1068 | } | |
1069 | ||
1070 | void RelativeDateTimeFormatter::formatAbsoluteImpl( | |
1071 | UDateDirection direction, | |
1072 | UDateAbsoluteUnit unit, | |
1073 | FormattedRelativeDateTimeData& output, | |
1074 | UErrorCode& status) const { | |
57a6839d | 1075 | if (U_FAILURE(status)) { |
3d1f044b | 1076 | return; |
57a6839d A |
1077 | } |
1078 | if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) { | |
1079 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
3d1f044b | 1080 | return; |
57a6839d | 1081 | } |
2ca993e8 A |
1082 | |
1083 | // Get string using fallback. | |
3d1f044b A |
1084 | output.getStringRef().append( |
1085 | fCache->getAbsoluteUnitString(fStyle, unit, direction), | |
1086 | kRDTLiteralField, | |
1087 | status); | |
57a6839d A |
1088 | } |
1089 | ||
2ca993e8 | 1090 | UnicodeString& RelativeDateTimeFormatter::format( |
3d1f044b A |
1091 | double offset, |
1092 | URelativeDateTimeUnit unit, | |
1093 | UnicodeString& appendTo, | |
1094 | UErrorCode& status) const { | |
1095 | return doFormat( | |
1096 | &RelativeDateTimeFormatter::formatRelativeImpl, | |
1097 | appendTo, | |
1098 | status, | |
1099 | offset, | |
1100 | unit); | |
1101 | } | |
1102 | ||
1103 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
1104 | double offset, | |
1105 | URelativeDateTimeUnit unit, | |
1106 | UErrorCode& status) const { | |
1107 | return doFormatToValue( | |
1108 | &RelativeDateTimeFormatter::formatRelativeImpl, | |
1109 | status, | |
1110 | offset, | |
1111 | unit); | |
1112 | } | |
1113 | ||
1114 | void RelativeDateTimeFormatter::formatRelativeImpl( | |
1115 | double offset, | |
1116 | URelativeDateTimeUnit unit, | |
1117 | FormattedRelativeDateTimeData& output, | |
1118 | UErrorCode& status) const { | |
2ca993e8 | 1119 | if (U_FAILURE(status)) { |
3d1f044b | 1120 | return; |
2ca993e8 A |
1121 | } |
1122 | // TODO: | |
1123 | // The full implementation of this depends on CLDR data that is not yet available, | |
1124 | // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data. | |
1125 | // In the meantime do a quick bring-up by calling the old format method; this | |
1126 | // leaves some holes (even for data that is currently available, such as quarter). | |
1127 | // When the new CLDR data is available, update the data storage accordingly, | |
1128 | // rewrite this to use it directly, and rewrite the old format method to call this | |
1129 | // new one; that is covered by http://bugs.icu-project.org/trac/ticket/12171. | |
1130 | UDateDirection direction = UDAT_DIRECTION_COUNT; | |
1131 | if (offset > -2.1 && offset < 2.1) { | |
1132 | // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST | |
1133 | double offsetx100 = offset * 100.0; | |
1134 | int32_t intoffset = (offsetx100 < 0)? (int32_t)(offsetx100-0.5) : (int32_t)(offsetx100+0.5); | |
1135 | switch (intoffset) { | |
1136 | case -200/*-2*/: direction = UDAT_DIRECTION_LAST_2; break; | |
1137 | case -100/*-1*/: direction = UDAT_DIRECTION_LAST; break; | |
1138 | case 0/* 0*/: direction = UDAT_DIRECTION_THIS; break; | |
1139 | case 100/* 1*/: direction = UDAT_DIRECTION_NEXT; break; | |
1140 | case 200/* 2*/: direction = UDAT_DIRECTION_NEXT_2; break; | |
1141 | default: break; | |
1142 | } | |
1143 | } | |
1144 | UDateAbsoluteUnit absunit = UDAT_ABSOLUTE_UNIT_COUNT; | |
1145 | switch (unit) { | |
1146 | case UDAT_REL_UNIT_YEAR: absunit = UDAT_ABSOLUTE_YEAR; break; | |
3d1f044b | 1147 | case UDAT_REL_UNIT_QUARTER: absunit = UDAT_ABSOLUTE_QUARTER; break; |
2ca993e8 A |
1148 | case UDAT_REL_UNIT_MONTH: absunit = UDAT_ABSOLUTE_MONTH; break; |
1149 | case UDAT_REL_UNIT_WEEK: absunit = UDAT_ABSOLUTE_WEEK; break; | |
1150 | case UDAT_REL_UNIT_DAY: absunit = UDAT_ABSOLUTE_DAY; break; | |
1151 | case UDAT_REL_UNIT_SECOND: | |
1152 | if (direction == UDAT_DIRECTION_THIS) { | |
1153 | absunit = UDAT_ABSOLUTE_NOW; | |
1154 | direction = UDAT_DIRECTION_PLAIN; | |
1155 | } | |
1156 | break; | |
1157 | case UDAT_REL_UNIT_SUNDAY: absunit = UDAT_ABSOLUTE_SUNDAY; break; | |
1158 | case UDAT_REL_UNIT_MONDAY: absunit = UDAT_ABSOLUTE_MONDAY; break; | |
1159 | case UDAT_REL_UNIT_TUESDAY: absunit = UDAT_ABSOLUTE_TUESDAY; break; | |
1160 | case UDAT_REL_UNIT_WEDNESDAY: absunit = UDAT_ABSOLUTE_WEDNESDAY; break; | |
1161 | case UDAT_REL_UNIT_THURSDAY: absunit = UDAT_ABSOLUTE_THURSDAY; break; | |
1162 | case UDAT_REL_UNIT_FRIDAY: absunit = UDAT_ABSOLUTE_FRIDAY; break; | |
1163 | case UDAT_REL_UNIT_SATURDAY: absunit = UDAT_ABSOLUTE_SATURDAY; break; | |
340931cb A |
1164 | case UDAT_REL_UNIT_HOUR: absunit = UDAT_ABSOLUTE_HOUR; break; |
1165 | case UDAT_REL_UNIT_MINUTE: absunit = UDAT_ABSOLUTE_MINUTE; break; | |
2ca993e8 A |
1166 | default: break; |
1167 | } | |
1168 | if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) { | |
3d1f044b A |
1169 | formatAbsoluteImpl(direction, absunit, output, status); |
1170 | if (output.getStringRef().length() != 0) { | |
1171 | return; | |
2ca993e8 A |
1172 | } |
1173 | } | |
1174 | // otherwise fallback to formatNumeric | |
3d1f044b | 1175 | formatNumericImpl(offset, unit, output, status); |
2ca993e8 A |
1176 | } |
1177 | ||
57a6839d | 1178 | UnicodeString& RelativeDateTimeFormatter::combineDateAndTime( |
2ca993e8 A |
1179 | const UnicodeString& relativeDateString, const UnicodeString& timeString, |
1180 | UnicodeString& appendTo, UErrorCode& status) const { | |
b331163b | 1181 | return fCache->getCombinedDateAndTime()->format( |
2ca993e8 | 1182 | timeString, relativeDateString, appendTo, status); |
57a6839d A |
1183 | } |
1184 | ||
3d1f044b A |
1185 | UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const { |
1186 | if (fOptBreakIterator == nullptr | |
b331163b | 1187 | || str.length() == 0 || !u_islower(str.char32At(0))) { |
3d1f044b | 1188 | return str; |
b331163b A |
1189 | } |
1190 | ||
1191 | // Must guarantee that one thread at a time accesses the shared break | |
1192 | // iterator. | |
340931cb A |
1193 | static UMutex gBrkIterMutex; |
1194 | Mutex lock(&gBrkIterMutex); | |
b331163b A |
1195 | str.toTitle( |
1196 | fOptBreakIterator->get(), | |
1197 | fLocale, | |
1198 | U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); | |
3d1f044b A |
1199 | return str; |
1200 | } | |
1201 | ||
1202 | UBool RelativeDateTimeFormatter::checkNoAdjustForContext(UErrorCode& status) const { | |
1203 | // This is unsupported because it's hard to keep fields in sync with title | |
1204 | // casing. The code could be written and tested if there is demand. | |
1205 | if (fOptBreakIterator != nullptr) { | |
1206 | status = U_UNSUPPORTED_ERROR; | |
1207 | return FALSE; | |
1208 | } | |
1209 | return TRUE; | |
b331163b A |
1210 | } |
1211 | ||
57a6839d | 1212 | void RelativeDateTimeFormatter::init( |
b331163b A |
1213 | NumberFormat *nfToAdopt, |
1214 | BreakIterator *biToAdopt, | |
1215 | UErrorCode &status) { | |
57a6839d | 1216 | LocalPointer<NumberFormat> nf(nfToAdopt); |
b331163b A |
1217 | LocalPointer<BreakIterator> bi(biToAdopt); |
1218 | UnifiedCache::getByLocale(fLocale, fCache, status); | |
1219 | if (U_FAILURE(status)) { | |
57a6839d A |
1220 | return; |
1221 | } | |
b331163b A |
1222 | const SharedPluralRules *pr = PluralRules::createSharedInstance( |
1223 | fLocale, UPLURAL_TYPE_CARDINAL, status); | |
57a6839d A |
1224 | if (U_FAILURE(status)) { |
1225 | return; | |
1226 | } | |
b331163b A |
1227 | SharedObject::copyPtr(pr, fPluralRules); |
1228 | pr->removeRef(); | |
57a6839d | 1229 | if (nf.isNull()) { |
b331163b A |
1230 | const SharedNumberFormat *shared = NumberFormat::createSharedInstance( |
1231 | fLocale, UNUM_DECIMAL, status); | |
57a6839d A |
1232 | if (U_FAILURE(status)) { |
1233 | return; | |
1234 | } | |
b331163b A |
1235 | SharedObject::copyPtr(shared, fNumberFormat); |
1236 | shared->removeRef(); | |
57a6839d A |
1237 | } else { |
1238 | SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias()); | |
3d1f044b | 1239 | if (shared == nullptr) { |
57a6839d A |
1240 | status = U_MEMORY_ALLOCATION_ERROR; |
1241 | return; | |
1242 | } | |
1243 | nf.orphan(); | |
b331163b A |
1244 | SharedObject::copyPtr(shared, fNumberFormat); |
1245 | } | |
1246 | if (bi.isNull()) { | |
1247 | SharedObject::clearPtr(fOptBreakIterator); | |
1248 | } else { | |
1249 | SharedBreakIterator *shared = new SharedBreakIterator(bi.getAlias()); | |
3d1f044b | 1250 | if (shared == nullptr) { |
b331163b A |
1251 | status = U_MEMORY_ALLOCATION_ERROR; |
1252 | return; | |
1253 | } | |
1254 | bi.orphan(); | |
1255 | SharedObject::copyPtr(shared, fOptBreakIterator); | |
57a6839d A |
1256 | } |
1257 | } | |
1258 | ||
57a6839d A |
1259 | U_NAMESPACE_END |
1260 | ||
2ca993e8 A |
1261 | // Plain C API |
1262 | ||
1263 | U_NAMESPACE_USE | |
1264 | ||
3d1f044b A |
1265 | |
1266 | // Magic number: "FRDT" (FormattedRelativeDateTime) in ASCII | |
1267 | UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL( | |
1268 | FormattedRelativeDateTime, | |
1269 | UFormattedRelativeDateTime, | |
1270 | UFormattedRelativeDateTimeImpl, | |
1271 | UFormattedRelativeDateTimeApiHelper, | |
1272 | ureldatefmt, | |
1273 | 0x46524454) | |
1274 | ||
1275 | ||
2ca993e8 A |
1276 | U_CAPI URelativeDateTimeFormatter* U_EXPORT2 |
1277 | ureldatefmt_open( const char* locale, | |
1278 | UNumberFormat* nfToAdopt, | |
1279 | UDateRelativeDateTimeFormatterStyle width, | |
1280 | UDisplayContext capitalizationContext, | |
1281 | UErrorCode* status ) | |
1282 | { | |
1283 | if (U_FAILURE(*status)) { | |
3d1f044b | 1284 | return nullptr; |
2ca993e8 A |
1285 | } |
1286 | LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale), | |
1287 | (NumberFormat*)nfToAdopt, width, | |
1288 | capitalizationContext, *status), *status); | |
1289 | if (U_FAILURE(*status)) { | |
3d1f044b | 1290 | return nullptr; |
2ca993e8 A |
1291 | } |
1292 | return (URelativeDateTimeFormatter*)formatter.orphan(); | |
1293 | } | |
1294 | ||
1295 | U_CAPI void U_EXPORT2 | |
1296 | ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt) | |
1297 | { | |
1298 | delete (RelativeDateTimeFormatter*)reldatefmt; | |
1299 | } | |
1300 | ||
1301 | U_CAPI int32_t U_EXPORT2 | |
1302 | ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt, | |
1303 | double offset, | |
1304 | URelativeDateTimeUnit unit, | |
1305 | UChar* result, | |
1306 | int32_t resultCapacity, | |
1307 | UErrorCode* status) | |
1308 | { | |
1309 | if (U_FAILURE(*status)) { | |
1310 | return 0; | |
1311 | } | |
3d1f044b | 1312 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) { |
2ca993e8 A |
1313 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1314 | return 0; | |
1315 | } | |
1316 | UnicodeString res; | |
3d1f044b A |
1317 | if (result != nullptr) { |
1318 | // nullptr destination for pure preflighting: empty dummy string | |
2ca993e8 A |
1319 | // otherwise, alias the destination buffer (copied from udat_format) |
1320 | res.setTo(result, 0, resultCapacity); | |
1321 | } | |
1322 | ((RelativeDateTimeFormatter*)reldatefmt)->formatNumeric(offset, unit, res, *status); | |
1323 | if (U_FAILURE(*status)) { | |
1324 | return 0; | |
1325 | } | |
1326 | return res.extract(result, resultCapacity, *status); | |
1327 | } | |
57a6839d | 1328 | |
3d1f044b A |
1329 | U_STABLE void U_EXPORT2 |
1330 | ureldatefmt_formatNumericToResult( | |
1331 | const URelativeDateTimeFormatter* reldatefmt, | |
1332 | double offset, | |
1333 | URelativeDateTimeUnit unit, | |
1334 | UFormattedRelativeDateTime* result, | |
1335 | UErrorCode* status) { | |
1336 | if (U_FAILURE(*status)) { | |
1337 | return; | |
1338 | } | |
1339 | auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt); | |
1340 | auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status); | |
1341 | resultImpl->fImpl = fmt->formatNumericToValue(offset, unit, *status); | |
1342 | } | |
1343 | ||
2ca993e8 A |
1344 | U_CAPI int32_t U_EXPORT2 |
1345 | ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt, | |
1346 | double offset, | |
1347 | URelativeDateTimeUnit unit, | |
1348 | UChar* result, | |
1349 | int32_t resultCapacity, | |
1350 | UErrorCode* status) | |
1351 | { | |
1352 | if (U_FAILURE(*status)) { | |
1353 | return 0; | |
1354 | } | |
3d1f044b | 1355 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) { |
2ca993e8 A |
1356 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1357 | return 0; | |
1358 | } | |
1359 | UnicodeString res; | |
3d1f044b A |
1360 | if (result != nullptr) { |
1361 | // nullptr destination for pure preflighting: empty dummy string | |
2ca993e8 A |
1362 | // otherwise, alias the destination buffer (copied from udat_format) |
1363 | res.setTo(result, 0, resultCapacity); | |
1364 | } | |
1365 | ((RelativeDateTimeFormatter*)reldatefmt)->format(offset, unit, res, *status); | |
1366 | if (U_FAILURE(*status)) { | |
1367 | return 0; | |
1368 | } | |
1369 | return res.extract(result, resultCapacity, *status); | |
1370 | } | |
1371 | ||
3d1f044b A |
1372 | U_DRAFT void U_EXPORT2 |
1373 | ureldatefmt_formatToResult( | |
1374 | const URelativeDateTimeFormatter* reldatefmt, | |
1375 | double offset, | |
1376 | URelativeDateTimeUnit unit, | |
1377 | UFormattedRelativeDateTime* result, | |
1378 | UErrorCode* status) { | |
1379 | if (U_FAILURE(*status)) { | |
1380 | return; | |
1381 | } | |
1382 | auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt); | |
1383 | auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status); | |
1384 | resultImpl->fImpl = fmt->formatToValue(offset, unit, *status); | |
1385 | } | |
1386 | ||
2ca993e8 A |
1387 | U_CAPI int32_t U_EXPORT2 |
1388 | ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt, | |
1389 | const UChar * relativeDateString, | |
1390 | int32_t relativeDateStringLen, | |
1391 | const UChar * timeString, | |
1392 | int32_t timeStringLen, | |
1393 | UChar* result, | |
1394 | int32_t resultCapacity, | |
1395 | UErrorCode* status ) | |
1396 | { | |
1397 | if (U_FAILURE(*status)) { | |
1398 | return 0; | |
1399 | } | |
3d1f044b A |
1400 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0 || |
1401 | (relativeDateString == nullptr ? relativeDateStringLen != 0 : relativeDateStringLen < -1) || | |
1402 | (timeString == nullptr ? timeStringLen != 0 : timeStringLen < -1)) { | |
2ca993e8 A |
1403 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1404 | return 0; | |
1405 | } | |
1406 | UnicodeString relDateStr((UBool)(relativeDateStringLen == -1), relativeDateString, relativeDateStringLen); | |
1407 | UnicodeString timeStr((UBool)(timeStringLen == -1), timeString, timeStringLen); | |
1408 | UnicodeString res(result, 0, resultCapacity); | |
1409 | ((RelativeDateTimeFormatter*)reldatefmt)->combineDateAndTime(relDateStr, timeStr, res, *status); | |
1410 | if (U_FAILURE(*status)) { | |
1411 | return 0; | |
1412 | } | |
1413 | return res.extract(result, resultCapacity, *status); | |
1414 | } | |
1415 | ||
1416 | #endif /* !UCONFIG_NO_FORMATTING */ |