]>
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 A |
45 | #include "util.h" |
46 | #include "number_stringbuilder.h" | |
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; | |
318 | default: | |
319 | return -1; | |
57a6839d | 320 | } |
2ca993e8 A |
321 | } |
322 | ||
323 | static int32_t keyToDirection(const char* key) { | |
324 | if (uprv_strcmp(key, "-2") == 0) { | |
325 | return UDAT_DIRECTION_LAST_2; | |
57a6839d | 326 | } |
2ca993e8 A |
327 | if (uprv_strcmp(key, "-1") == 0) { |
328 | return UDAT_DIRECTION_LAST; | |
329 | } | |
330 | if (uprv_strcmp(key, "0") == 0) { | |
331 | return UDAT_DIRECTION_THIS; | |
57a6839d | 332 | } |
2ca993e8 A |
333 | if (uprv_strcmp(key, "1") == 0) { |
334 | return UDAT_DIRECTION_NEXT; | |
335 | } | |
336 | if (uprv_strcmp(key, "2") == 0) { | |
337 | return UDAT_DIRECTION_NEXT_2; | |
338 | } | |
339 | return -1; | |
57a6839d | 340 | } |
57a6839d | 341 | |
2ca993e8 A |
342 | // Values kept between levels of parsing the CLDR data. |
343 | int32_t pastFutureIndex; // 0 == past or 1 == future | |
344 | UDateRelativeDateTimeFormatterStyle style; // {LONG, SHORT, NARROW} | |
345 | RelAbsUnit genericUnit; | |
2ca993e8 A |
346 | |
347 | RelativeDateTimeCacheData &outputData; | |
348 | ||
349 | // Constructor | |
f3c0d7a5 A |
350 | RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData) |
351 | : outputData(cacheData) { | |
2ca993e8 A |
352 | // Clear cacheData.fallBackCache |
353 | cacheData.fallBackCache[UDAT_STYLE_LONG] = -1; | |
354 | cacheData.fallBackCache[UDAT_STYLE_SHORT] = -1; | |
355 | cacheData.fallBackCache[UDAT_STYLE_NARROW] = -1; | |
57a6839d | 356 | } |
2ca993e8 A |
357 | |
358 | ~RelDateTimeFmtDataSink(); | |
359 | ||
360 | // Utility functions | |
361 | static UDateRelativeDateTimeFormatterStyle styleFromString(const char *s) { | |
3d1f044b | 362 | int32_t len = static_cast<int32_t>(uprv_strlen(s)); |
2ca993e8 A |
363 | if (len >= 7 && uprv_strcmp(s + len - 7, "-narrow") == 0) { |
364 | return UDAT_STYLE_NARROW; | |
57a6839d | 365 | } |
2ca993e8 A |
366 | if (len >= 6 && uprv_strcmp(s + len - 6, "-short") == 0) { |
367 | return UDAT_STYLE_SHORT; | |
368 | } | |
369 | return UDAT_STYLE_LONG; | |
57a6839d | 370 | } |
57a6839d | 371 | |
2ca993e8 A |
372 | static int32_t styleSuffixLength(UDateRelativeDateTimeFormatterStyle style) { |
373 | switch (style) { | |
374 | case UDAT_STYLE_NARROW: | |
375 | return 7; | |
376 | case UDAT_STYLE_SHORT: | |
377 | return 6; | |
378 | default: | |
379 | return 0; | |
380 | } | |
57a6839d | 381 | } |
57a6839d | 382 | |
2ca993e8 A |
383 | // Utility functions |
384 | static UDateRelativeDateTimeFormatterStyle styleFromAliasUnicodeString(UnicodeString s) { | |
385 | static const UChar narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077}; | |
386 | static const UChar sshort[6] = {0x002D, 0x0073, 0x0068, 0x006F, 0x0072, 0x0074,}; | |
387 | if (s.endsWith(narrow, 7)) { | |
388 | return UDAT_STYLE_NARROW; | |
389 | } | |
390 | if (s.endsWith(sshort, 6)) { | |
391 | return UDAT_STYLE_SHORT; | |
392 | } | |
393 | return UDAT_STYLE_LONG; | |
b331163b | 394 | } |
2ca993e8 A |
395 | |
396 | static RelAbsUnit unitOrNegativeFromString(const char* keyword, int32_t length) { | |
397 | // Quick check from string to enum. | |
398 | switch (length) { | |
399 | case 3: | |
400 | if (uprv_strncmp(keyword, "day", length) == 0) { | |
401 | return DAY; | |
402 | } else if (uprv_strncmp(keyword, "sun", length) == 0) { | |
403 | return SUNDAY; | |
404 | } else if (uprv_strncmp(keyword, "mon", length) == 0) { | |
405 | return MONDAY; | |
406 | } else if (uprv_strncmp(keyword, "tue", length) == 0) { | |
407 | return TUESDAY; | |
408 | } else if (uprv_strncmp(keyword, "wed", length) == 0) { | |
409 | return WEDNESDAY; | |
410 | } else if (uprv_strncmp(keyword, "thu", length) == 0) { | |
411 | return THURSDAY; | |
412 | } else if (uprv_strncmp(keyword, "fri", length) == 0) { | |
413 | return FRIDAY; | |
414 | } else if (uprv_strncmp(keyword, "sat", length) == 0) { | |
415 | return SATURDAY; | |
416 | } | |
417 | break; | |
418 | case 4: | |
419 | if (uprv_strncmp(keyword, "hour", length) == 0) { | |
420 | return HOUR; | |
421 | } else if (uprv_strncmp(keyword, "week", length) == 0) { | |
422 | return WEEK; | |
423 | } else if (uprv_strncmp(keyword, "year", length) == 0) { | |
424 | return YEAR; | |
425 | } | |
426 | break; | |
427 | case 5: | |
428 | if (uprv_strncmp(keyword, "month", length) == 0) { | |
429 | return MONTH; | |
430 | } | |
431 | break; | |
432 | case 6: | |
433 | if (uprv_strncmp(keyword, "minute", length) == 0) { | |
434 | return MINUTE; | |
435 | } else if (uprv_strncmp(keyword, "second", length) == 0) { | |
436 | return SECOND; | |
437 | } | |
438 | break; | |
439 | case 7: | |
440 | if (uprv_strncmp(keyword, "quarter", length) == 0) { | |
441 | return QUARTER; // TODO: Check @provisional | |
442 | } | |
443 | break; | |
444 | default: | |
445 | break; | |
446 | } | |
447 | return INVALID_UNIT; | |
b331163b | 448 | } |
b331163b | 449 | |
f3c0d7a5 A |
450 | void handlePlainDirection(ResourceValue &value, UErrorCode &errorCode) { |
451 | // Handle Display Name for PLAIN direction for some units. | |
452 | if (U_FAILURE(errorCode)) { return; } | |
453 | ||
454 | int32_t absUnit = absUnitFromGeneric(genericUnit); | |
455 | if (absUnit < 0) { | |
456 | return; // Not interesting. | |
457 | } | |
458 | ||
459 | // Store displayname if not set. | |
460 | if (outputData.absoluteUnits[style] | |
461 | [absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) { | |
462 | outputData.absoluteUnits[style] | |
463 | [absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode)); | |
464 | return; | |
465 | } | |
466 | } | |
467 | ||
468 | void consumeTableRelative(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
469 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
470 | if (U_FAILURE(errorCode)) { return; } | |
2ca993e8 | 471 | |
f3c0d7a5 A |
472 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { |
473 | if (value.getType() == URES_STRING) { | |
474 | int32_t direction = keyToDirection(key); | |
475 | if (direction < 0) { | |
476 | continue; | |
477 | } | |
478 | ||
479 | int32_t relUnitIndex = relUnitFromGeneric(genericUnit); | |
3d1f044b | 480 | if (relUnitIndex == UDAT_REL_UNIT_SECOND && uprv_strcmp(key, "0") == 0 && |
f3c0d7a5 A |
481 | outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN].isEmpty()) { |
482 | // Handle "NOW" | |
483 | outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW] | |
484 | [UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode)); | |
485 | } | |
486 | ||
487 | int32_t absUnitIndex = absUnitFromGeneric(genericUnit); | |
488 | if (absUnitIndex < 0) { | |
489 | continue; | |
490 | } | |
491 | // Only reset if slot is empty. | |
492 | if (outputData.absoluteUnits[style][absUnitIndex][direction].isEmpty()) { | |
493 | outputData.absoluteUnits[style][absUnitIndex] | |
494 | [direction].fastCopyFrom(value.getUnicodeString(errorCode)); | |
495 | } | |
2ca993e8 | 496 | } |
f3c0d7a5 A |
497 | } |
498 | } | |
499 | ||
500 | void consumeTimeDetail(int32_t relUnitIndex, | |
501 | const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
502 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
503 | if (U_FAILURE(errorCode)) { return; } | |
504 | ||
505 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { | |
506 | if (value.getType() == URES_STRING) { | |
507 | int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key); | |
508 | if (pluralIndex >= 0) { | |
509 | SimpleFormatter **patterns = | |
510 | outputData.relativeUnitsFormatters[style][relUnitIndex] | |
511 | [pastFutureIndex]; | |
512 | // Only set if not already established. | |
3d1f044b | 513 | if (patterns[pluralIndex] == nullptr) { |
f3c0d7a5 A |
514 | patterns[pluralIndex] = new SimpleFormatter( |
515 | value.getUnicodeString(errorCode), 0, 1, errorCode); | |
3d1f044b | 516 | if (patterns[pluralIndex] == nullptr) { |
f3c0d7a5 A |
517 | errorCode = U_MEMORY_ALLOCATION_ERROR; |
518 | } | |
519 | } | |
2ca993e8 | 520 | } |
f3c0d7a5 A |
521 | } |
522 | } | |
523 | } | |
524 | ||
525 | void consumeTableRelativeTime(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
526 | ResourceTable relativeTimeTable = value.getTable(errorCode); | |
527 | if (U_FAILURE(errorCode)) { return; } | |
528 | ||
529 | int32_t relUnitIndex = relUnitFromGeneric(genericUnit); | |
530 | if (relUnitIndex < 0) { | |
531 | return; | |
532 | } | |
533 | for (int32_t i = 0; relativeTimeTable.getKeyAndValue(i, key, value); ++i) { | |
534 | if (uprv_strcmp(key, "past") == 0) { | |
535 | pastFutureIndex = 0; | |
536 | } else if (uprv_strcmp(key, "future") == 0) { | |
537 | pastFutureIndex = 1; | |
538 | } else { | |
539 | // Unknown key. | |
540 | continue; | |
541 | } | |
542 | consumeTimeDetail(relUnitIndex, key, value, errorCode); | |
543 | } | |
544 | } | |
545 | ||
546 | void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) { | |
547 | ||
548 | UDateRelativeDateTimeFormatterStyle sourceStyle = styleFromString(key); | |
549 | const UnicodeString valueStr = value.getAliasUnicodeString(errorCode); | |
550 | if (U_FAILURE(errorCode)) { return; } | |
551 | ||
552 | UDateRelativeDateTimeFormatterStyle targetStyle = | |
553 | styleFromAliasUnicodeString(valueStr); | |
554 | ||
555 | if (sourceStyle == targetStyle) { | |
556 | errorCode = U_INVALID_FORMAT_ERROR; | |
557 | return; | |
558 | } | |
559 | if (outputData.fallBackCache[sourceStyle] != -1 && | |
560 | outputData.fallBackCache[sourceStyle] != targetStyle) { | |
561 | errorCode = U_INVALID_FORMAT_ERROR; | |
562 | return; | |
563 | } | |
564 | outputData.fallBackCache[sourceStyle] = targetStyle; | |
565 | } | |
566 | ||
567 | void consumeTimeUnit(const char *key, ResourceValue &value, UErrorCode &errorCode) { | |
568 | ResourceTable unitTypesTable = value.getTable(errorCode); | |
569 | if (U_FAILURE(errorCode)) { return; } | |
570 | ||
571 | for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { | |
572 | // Handle display name. | |
573 | if (uprv_strcmp(key, "dn") == 0 && value.getType() == URES_STRING) { | |
574 | handlePlainDirection(value, errorCode); | |
575 | } | |
576 | if (value.getType() == URES_TABLE) { | |
577 | if (uprv_strcmp(key, "relative") == 0) { | |
578 | consumeTableRelative(key, value, errorCode); | |
579 | } else if (uprv_strcmp(key, "relativeTime") == 0) { | |
580 | consumeTableRelativeTime(key, value, errorCode); | |
2ca993e8 | 581 | } |
2ca993e8 A |
582 | } |
583 | } | |
b331163b | 584 | } |
b331163b | 585 | |
f3c0d7a5 A |
586 | virtual void put(const char *key, ResourceValue &value, |
587 | UBool /*noFallback*/, UErrorCode &errorCode) { | |
588 | // Main entry point to sink | |
589 | ResourceTable table = value.getTable(errorCode); | |
590 | if (U_FAILURE(errorCode)) { return; } | |
591 | for (int32_t i = 0; table.getKeyAndValue(i, key, value); ++i) { | |
592 | if (value.getType() == URES_ALIAS) { | |
593 | consumeAlias(key, value, errorCode); | |
594 | } else { | |
595 | style = styleFromString(key); | |
3d1f044b | 596 | int32_t unitSize = static_cast<int32_t>(uprv_strlen(key)) - styleSuffixLength(style); |
f3c0d7a5 A |
597 | genericUnit = unitOrNegativeFromString(key, unitSize); |
598 | if (style >= 0 && genericUnit != INVALID_UNIT) { | |
599 | consumeTimeUnit(key, value, errorCode); | |
600 | } | |
601 | } | |
602 | } | |
b331163b | 603 | } |
f3c0d7a5 | 604 | |
2ca993e8 A |
605 | }; |
606 | ||
607 | // Virtual destructors must be defined out of line. | |
2ca993e8 | 608 | RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {} |
2ca993e8 A |
609 | } // namespace |
610 | ||
0f5d89e8 | 611 | static const DateFormatSymbols::DtWidthType styleToDateFormatSymbolWidth[UDAT_STYLE_COUNT] = { |
2ca993e8 A |
612 | DateFormatSymbols::WIDE, DateFormatSymbols::SHORT, DateFormatSymbols::NARROW |
613 | }; | |
614 | ||
615 | // Get days of weeks from the DateFormatSymbols class. | |
616 | static void loadWeekdayNames(UnicodeString absoluteUnits[UDAT_STYLE_COUNT] | |
617 | [UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT], | |
618 | const char* localeId, | |
619 | UErrorCode& status) { | |
3d1f044b A |
620 | if (U_FAILURE(status)) { |
621 | return; | |
622 | } | |
2ca993e8 A |
623 | Locale locale(localeId); |
624 | DateFormatSymbols dfSym(locale, status); | |
3d1f044b A |
625 | if (U_FAILURE(status)) { |
626 | return; | |
627 | } | |
2ca993e8 A |
628 | for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) { |
629 | DateFormatSymbols::DtWidthType dtfmtWidth = styleToDateFormatSymbolWidth[style]; | |
630 | int32_t count; | |
631 | const UnicodeString* weekdayNames = | |
632 | dfSym.getWeekdays(count, DateFormatSymbols::STANDALONE, dtfmtWidth); | |
633 | for (int32_t dayIndex = UDAT_ABSOLUTE_SUNDAY; | |
634 | dayIndex <= UDAT_ABSOLUTE_SATURDAY; ++ dayIndex) { | |
635 | int32_t dateSymbolIndex = (dayIndex - UDAT_ABSOLUTE_SUNDAY) + UCAL_SUNDAY; | |
636 | absoluteUnits[style][dayIndex][UDAT_DIRECTION_PLAIN].fastCopyFrom( | |
637 | weekdayNames[dateSymbolIndex]); | |
638 | } | |
b331163b A |
639 | } |
640 | } | |
641 | ||
642 | static UBool loadUnitData( | |
643 | const UResourceBundle *resource, | |
644 | RelativeDateTimeCacheData &cacheData, | |
2ca993e8 | 645 | const char* localeId, |
b331163b | 646 | UErrorCode &status) { |
f3c0d7a5 A |
647 | |
648 | RelDateTimeFmtDataSink sink(cacheData); | |
649 | ||
650 | ures_getAllItemsWithFallback(resource, "fields", sink, status); | |
3d1f044b A |
651 | if (U_FAILURE(status)) { |
652 | return false; | |
653 | } | |
2ca993e8 A |
654 | |
655 | // Get the weekday names from DateFormatSymbols. | |
656 | loadWeekdayNames(cacheData.absoluteUnits, localeId, status); | |
57a6839d A |
657 | return U_SUCCESS(status); |
658 | } | |
659 | ||
660 | static UBool getDateTimePattern( | |
661 | const UResourceBundle *resource, | |
662 | UnicodeString &result, | |
663 | UErrorCode &status) { | |
664 | UnicodeString defaultCalendarName; | |
665 | if (!getStringWithFallback( | |
666 | resource, | |
667 | "calendar/default", | |
668 | defaultCalendarName, | |
669 | status)) { | |
670 | return FALSE; | |
671 | } | |
672 | CharString pathBuffer; | |
673 | pathBuffer.append("calendar/", status) | |
674 | .appendInvariantChars(defaultCalendarName, status) | |
675 | .append("/DateTimePatterns", status); | |
676 | LocalUResourceBundlePointer topLevel( | |
677 | ures_getByKeyWithFallback( | |
3d1f044b | 678 | resource, pathBuffer.data(), nullptr, &status)); |
57a6839d A |
679 | if (U_FAILURE(status)) { |
680 | return FALSE; | |
681 | } | |
682 | int32_t size = ures_getSize(topLevel.getAlias()); | |
683 | if (size <= 8) { | |
2ca993e8 | 684 | // Oops, size is too small to access the index that we want, fallback |
57a6839d A |
685 | // to a hard-coded value. |
686 | result = UNICODE_STRING_SIMPLE("{1} {0}"); | |
687 | return TRUE; | |
688 | } | |
689 | return getStringByIndex(topLevel.getAlias(), 8, result, status); | |
690 | } | |
691 | ||
b331163b A |
692 | template<> U_I18N_API |
693 | const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::createObject(const void * /*unused*/, UErrorCode &status) const { | |
694 | const char *localeId = fLoc.getName(); | |
3d1f044b | 695 | LocalUResourceBundlePointer topLevel(ures_open(nullptr, localeId, &status)); |
57a6839d | 696 | if (U_FAILURE(status)) { |
3d1f044b | 697 | return nullptr; |
57a6839d A |
698 | } |
699 | LocalPointer<RelativeDateTimeCacheData> result( | |
700 | new RelativeDateTimeCacheData()); | |
701 | if (result.isNull()) { | |
702 | status = U_MEMORY_ALLOCATION_ERROR; | |
3d1f044b | 703 | return nullptr; |
57a6839d A |
704 | } |
705 | if (!loadUnitData( | |
706 | topLevel.getAlias(), | |
707 | *result, | |
2ca993e8 | 708 | localeId, |
57a6839d | 709 | status)) { |
3d1f044b | 710 | return nullptr; |
57a6839d A |
711 | } |
712 | UnicodeString dateTimePattern; | |
713 | if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) { | |
3d1f044b | 714 | return nullptr; |
57a6839d A |
715 | } |
716 | result->adoptCombinedDateAndTime( | |
2ca993e8 | 717 | new SimpleFormatter(dateTimePattern, 2, 2, status)); |
57a6839d | 718 | if (U_FAILURE(status)) { |
3d1f044b | 719 | return nullptr; |
57a6839d | 720 | } |
b331163b | 721 | result->addRef(); |
57a6839d A |
722 | return result.orphan(); |
723 | } | |
724 | ||
3d1f044b A |
725 | |
726 | ||
727 | static constexpr number::impl::Field kRDTNumericField | |
728 | = number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD>(); | |
729 | ||
730 | static constexpr number::impl::Field kRDTLiteralField | |
731 | = number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD>(); | |
732 | ||
733 | class FormattedRelativeDateTimeData : public FormattedValueNumberStringBuilderImpl { | |
734 | public: | |
735 | FormattedRelativeDateTimeData() : FormattedValueNumberStringBuilderImpl(kRDTNumericField) {} | |
736 | virtual ~FormattedRelativeDateTimeData(); | |
737 | }; | |
738 | ||
739 | FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default; | |
740 | ||
741 | ||
742 | UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime) | |
743 | ||
744 | ||
b331163b | 745 | RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) : |
3d1f044b A |
746 | fCache(nullptr), |
747 | fNumberFormat(nullptr), | |
748 | fPluralRules(nullptr), | |
b331163b A |
749 | fStyle(UDAT_STYLE_LONG), |
750 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b A |
751 | fOptBreakIterator(nullptr) { |
752 | init(nullptr, nullptr, status); | |
57a6839d A |
753 | } |
754 | ||
b331163b A |
755 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( |
756 | const Locale& locale, UErrorCode& status) : | |
3d1f044b A |
757 | fCache(nullptr), |
758 | fNumberFormat(nullptr), | |
759 | fPluralRules(nullptr), | |
b331163b A |
760 | fStyle(UDAT_STYLE_LONG), |
761 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b | 762 | fOptBreakIterator(nullptr), |
b331163b | 763 | fLocale(locale) { |
3d1f044b | 764 | init(nullptr, nullptr, status); |
57a6839d A |
765 | } |
766 | ||
767 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
b331163b | 768 | const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) : |
3d1f044b A |
769 | fCache(nullptr), |
770 | fNumberFormat(nullptr), | |
771 | fPluralRules(nullptr), | |
b331163b A |
772 | fStyle(UDAT_STYLE_LONG), |
773 | fContext(UDISPCTX_CAPITALIZATION_NONE), | |
3d1f044b | 774 | fOptBreakIterator(nullptr), |
b331163b | 775 | fLocale(locale) { |
3d1f044b | 776 | init(nfToAdopt, nullptr, status); |
57a6839d A |
777 | } |
778 | ||
779 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
b331163b A |
780 | const Locale& locale, |
781 | NumberFormat *nfToAdopt, | |
782 | UDateRelativeDateTimeFormatterStyle styl, | |
783 | UDisplayContext capitalizationContext, | |
784 | UErrorCode& status) : | |
3d1f044b A |
785 | fCache(nullptr), |
786 | fNumberFormat(nullptr), | |
787 | fPluralRules(nullptr), | |
b331163b A |
788 | fStyle(styl), |
789 | fContext(capitalizationContext), | |
3d1f044b | 790 | fOptBreakIterator(nullptr), |
b331163b A |
791 | fLocale(locale) { |
792 | if (U_FAILURE(status)) { | |
793 | return; | |
794 | } | |
795 | if ((capitalizationContext >> 8) != UDISPCTX_TYPE_CAPITALIZATION) { | |
796 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
797 | return; | |
798 | } | |
799 | if (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) { | |
800 | BreakIterator *bi = BreakIterator::createSentenceInstance(locale, status); | |
801 | if (U_FAILURE(status)) { | |
802 | return; | |
803 | } | |
804 | init(nfToAdopt, bi, status); | |
805 | } else { | |
3d1f044b | 806 | init(nfToAdopt, nullptr, status); |
b331163b | 807 | } |
57a6839d A |
808 | } |
809 | ||
810 | RelativeDateTimeFormatter::RelativeDateTimeFormatter( | |
811 | const RelativeDateTimeFormatter& other) | |
b331163b A |
812 | : UObject(other), |
813 | fCache(other.fCache), | |
814 | fNumberFormat(other.fNumberFormat), | |
815 | fPluralRules(other.fPluralRules), | |
816 | fStyle(other.fStyle), | |
817 | fContext(other.fContext), | |
818 | fOptBreakIterator(other.fOptBreakIterator), | |
819 | fLocale(other.fLocale) { | |
820 | fCache->addRef(); | |
821 | fNumberFormat->addRef(); | |
822 | fPluralRules->addRef(); | |
3d1f044b | 823 | if (fOptBreakIterator != nullptr) { |
b331163b A |
824 | fOptBreakIterator->addRef(); |
825 | } | |
57a6839d A |
826 | } |
827 | ||
828 | RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=( | |
829 | const RelativeDateTimeFormatter& other) { | |
830 | if (this != &other) { | |
b331163b A |
831 | SharedObject::copyPtr(other.fCache, fCache); |
832 | SharedObject::copyPtr(other.fNumberFormat, fNumberFormat); | |
833 | SharedObject::copyPtr(other.fPluralRules, fPluralRules); | |
834 | SharedObject::copyPtr(other.fOptBreakIterator, fOptBreakIterator); | |
835 | fStyle = other.fStyle; | |
836 | fContext = other.fContext; | |
837 | fLocale = other.fLocale; | |
57a6839d A |
838 | } |
839 | return *this; | |
840 | } | |
841 | ||
842 | RelativeDateTimeFormatter::~RelativeDateTimeFormatter() { | |
3d1f044b | 843 | if (fCache != nullptr) { |
b331163b A |
844 | fCache->removeRef(); |
845 | } | |
3d1f044b | 846 | if (fNumberFormat != nullptr) { |
b331163b | 847 | fNumberFormat->removeRef(); |
57a6839d | 848 | } |
3d1f044b | 849 | if (fPluralRules != nullptr) { |
b331163b | 850 | fPluralRules->removeRef(); |
57a6839d | 851 | } |
3d1f044b | 852 | if (fOptBreakIterator != nullptr) { |
b331163b | 853 | fOptBreakIterator->removeRef(); |
57a6839d A |
854 | } |
855 | } | |
856 | ||
857 | const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const { | |
b331163b A |
858 | return **fNumberFormat; |
859 | } | |
860 | ||
861 | UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const { | |
862 | return fContext; | |
863 | } | |
864 | ||
865 | UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const { | |
866 | return fStyle; | |
57a6839d A |
867 | } |
868 | ||
3d1f044b A |
869 | |
870 | // To reduce boilerplate code, we use a helper function that forwards variadic | |
871 | // arguments to the formatImpl function. | |
872 | ||
873 | template<typename F, typename... Args> | |
874 | UnicodeString& RelativeDateTimeFormatter::doFormat( | |
875 | F callback, | |
876 | UnicodeString& appendTo, | |
877 | UErrorCode& status, | |
878 | Args... args) const { | |
879 | FormattedRelativeDateTimeData output; | |
880 | (this->*callback)(std::forward<Args>(args)..., output, status); | |
57a6839d A |
881 | if (U_FAILURE(status)) { |
882 | return appendTo; | |
883 | } | |
3d1f044b A |
884 | UnicodeString result = output.getStringRef().toUnicodeString(); |
885 | return appendTo.append(adjustForContext(result)); | |
886 | } | |
887 | ||
888 | template<typename F, typename... Args> | |
889 | FormattedRelativeDateTime RelativeDateTimeFormatter::doFormatToValue( | |
890 | F callback, | |
891 | UErrorCode& status, | |
892 | Args... args) const { | |
893 | if (!checkNoAdjustForContext(status)) { | |
894 | return FormattedRelativeDateTime(status); | |
895 | } | |
896 | LocalPointer<FormattedRelativeDateTimeData> output( | |
897 | new FormattedRelativeDateTimeData(), status); | |
898 | if (U_FAILURE(status)) { | |
899 | return FormattedRelativeDateTime(status); | |
900 | } | |
901 | (this->*callback)(std::forward<Args>(args)..., *output, status); | |
902 | output->getStringRef().writeTerminator(status); | |
903 | return FormattedRelativeDateTime(output.orphan()); | |
904 | } | |
905 | ||
906 | UnicodeString& RelativeDateTimeFormatter::format( | |
907 | double quantity, | |
908 | UDateDirection direction, | |
909 | UDateRelativeUnit unit, | |
910 | UnicodeString& appendTo, | |
911 | UErrorCode& status) const { | |
912 | return doFormat( | |
913 | &RelativeDateTimeFormatter::formatImpl, | |
914 | appendTo, | |
915 | status, | |
916 | quantity, | |
917 | direction, | |
918 | unit); | |
919 | } | |
920 | ||
921 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
922 | double quantity, | |
923 | UDateDirection direction, | |
924 | UDateRelativeUnit unit, | |
925 | UErrorCode& status) const { | |
926 | return doFormatToValue( | |
927 | &RelativeDateTimeFormatter::formatImpl, | |
928 | status, | |
929 | quantity, | |
930 | direction, | |
931 | unit); | |
932 | } | |
933 | ||
934 | void RelativeDateTimeFormatter::formatImpl( | |
935 | double quantity, | |
936 | UDateDirection direction, | |
937 | UDateRelativeUnit unit, | |
938 | FormattedRelativeDateTimeData& output, | |
939 | UErrorCode& status) const { | |
940 | if (U_FAILURE(status)) { | |
941 | return; | |
942 | } | |
57a6839d A |
943 | if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) { |
944 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
3d1f044b | 945 | return; |
57a6839d A |
946 | } |
947 | int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0; | |
2ca993e8 | 948 | |
3d1f044b A |
949 | StandardPlural::Form pluralForm; |
950 | QuantityFormatter::formatAndSelect( | |
951 | quantity, | |
952 | **fNumberFormat, | |
953 | **fPluralRules, | |
954 | output.getStringRef(), | |
955 | pluralForm, | |
2ca993e8 | 956 | status); |
3d1f044b A |
957 | if (U_FAILURE(status)) { |
958 | return; | |
959 | } | |
2ca993e8 A |
960 | |
961 | const SimpleFormatter* formatter = | |
3d1f044b A |
962 | fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralForm); |
963 | if (formatter == nullptr) { | |
2ca993e8 A |
964 | // TODO: WARN - look at quantity formatter's action with an error. |
965 | status = U_INVALID_FORMAT_ERROR; | |
3d1f044b | 966 | return; |
2ca993e8 | 967 | } |
3d1f044b A |
968 | |
969 | number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false); | |
970 | modifier.formatAsPrefixSuffix( | |
971 | output.getStringRef(), 0, output.getStringRef().length(), status); | |
57a6839d A |
972 | } |
973 | ||
2ca993e8 | 974 | UnicodeString& RelativeDateTimeFormatter::formatNumeric( |
3d1f044b A |
975 | double offset, |
976 | URelativeDateTimeUnit unit, | |
977 | UnicodeString& appendTo, | |
978 | UErrorCode& status) const { | |
979 | return doFormat( | |
980 | &RelativeDateTimeFormatter::formatNumericImpl, | |
981 | appendTo, | |
982 | status, | |
983 | offset, | |
984 | unit); | |
985 | } | |
986 | ||
987 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatNumericToValue( | |
988 | double offset, | |
989 | URelativeDateTimeUnit unit, | |
990 | UErrorCode& status) const { | |
991 | return doFormatToValue( | |
992 | &RelativeDateTimeFormatter::formatNumericImpl, | |
993 | status, | |
994 | offset, | |
995 | unit); | |
996 | } | |
997 | ||
998 | void RelativeDateTimeFormatter::formatNumericImpl( | |
999 | double offset, | |
1000 | URelativeDateTimeUnit unit, | |
1001 | FormattedRelativeDateTimeData& output, | |
1002 | UErrorCode& status) const { | |
2ca993e8 | 1003 | if (U_FAILURE(status)) { |
3d1f044b | 1004 | return; |
2ca993e8 A |
1005 | } |
1006 | UDateDirection direction = UDAT_DIRECTION_NEXT; | |
0f5d89e8 | 1007 | if (std::signbit(offset)) { // needed to handle -0.0 |
2ca993e8 A |
1008 | direction = UDAT_DIRECTION_LAST; |
1009 | offset = -offset; | |
1010 | } | |
3d1f044b A |
1011 | if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) { |
1012 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
1013 | return; | |
1014 | } | |
1015 | int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0; | |
1016 | ||
1017 | StandardPlural::Form pluralForm; | |
1018 | QuantityFormatter::formatAndSelect( | |
1019 | offset, | |
1020 | **fNumberFormat, | |
1021 | **fPluralRules, | |
1022 | output.getStringRef(), | |
1023 | pluralForm, | |
1024 | status); | |
1025 | if (U_FAILURE(status)) { | |
1026 | return; | |
1027 | } | |
1028 | ||
1029 | const SimpleFormatter* formatter = | |
1030 | fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralForm); | |
1031 | if (formatter == nullptr) { | |
1032 | // TODO: WARN - look at quantity formatter's action with an error. | |
1033 | status = U_INVALID_FORMAT_ERROR; | |
1034 | return; | |
1035 | } | |
1036 | ||
1037 | number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false); | |
1038 | modifier.formatAsPrefixSuffix( | |
1039 | output.getStringRef(), 0, output.getStringRef().length(), status); | |
2ca993e8 A |
1040 | } |
1041 | ||
57a6839d | 1042 | UnicodeString& RelativeDateTimeFormatter::format( |
3d1f044b A |
1043 | UDateDirection direction, |
1044 | UDateAbsoluteUnit unit, | |
1045 | UnicodeString& appendTo, | |
1046 | UErrorCode& status) const { | |
1047 | return doFormat( | |
1048 | &RelativeDateTimeFormatter::formatAbsoluteImpl, | |
1049 | appendTo, | |
1050 | status, | |
1051 | direction, | |
1052 | unit); | |
1053 | } | |
1054 | ||
1055 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
1056 | UDateDirection direction, | |
1057 | UDateAbsoluteUnit unit, | |
1058 | UErrorCode& status) const { | |
1059 | return doFormatToValue( | |
1060 | &RelativeDateTimeFormatter::formatAbsoluteImpl, | |
1061 | status, | |
1062 | direction, | |
1063 | unit); | |
1064 | } | |
1065 | ||
1066 | void RelativeDateTimeFormatter::formatAbsoluteImpl( | |
1067 | UDateDirection direction, | |
1068 | UDateAbsoluteUnit unit, | |
1069 | FormattedRelativeDateTimeData& output, | |
1070 | UErrorCode& status) const { | |
57a6839d | 1071 | if (U_FAILURE(status)) { |
3d1f044b | 1072 | return; |
57a6839d A |
1073 | } |
1074 | if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) { | |
1075 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
3d1f044b | 1076 | return; |
57a6839d | 1077 | } |
2ca993e8 A |
1078 | |
1079 | // Get string using fallback. | |
3d1f044b A |
1080 | output.getStringRef().append( |
1081 | fCache->getAbsoluteUnitString(fStyle, unit, direction), | |
1082 | kRDTLiteralField, | |
1083 | status); | |
57a6839d A |
1084 | } |
1085 | ||
2ca993e8 | 1086 | UnicodeString& RelativeDateTimeFormatter::format( |
3d1f044b A |
1087 | double offset, |
1088 | URelativeDateTimeUnit unit, | |
1089 | UnicodeString& appendTo, | |
1090 | UErrorCode& status) const { | |
1091 | return doFormat( | |
1092 | &RelativeDateTimeFormatter::formatRelativeImpl, | |
1093 | appendTo, | |
1094 | status, | |
1095 | offset, | |
1096 | unit); | |
1097 | } | |
1098 | ||
1099 | FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue( | |
1100 | double offset, | |
1101 | URelativeDateTimeUnit unit, | |
1102 | UErrorCode& status) const { | |
1103 | return doFormatToValue( | |
1104 | &RelativeDateTimeFormatter::formatRelativeImpl, | |
1105 | status, | |
1106 | offset, | |
1107 | unit); | |
1108 | } | |
1109 | ||
1110 | void RelativeDateTimeFormatter::formatRelativeImpl( | |
1111 | double offset, | |
1112 | URelativeDateTimeUnit unit, | |
1113 | FormattedRelativeDateTimeData& output, | |
1114 | UErrorCode& status) const { | |
2ca993e8 | 1115 | if (U_FAILURE(status)) { |
3d1f044b | 1116 | return; |
2ca993e8 A |
1117 | } |
1118 | // TODO: | |
1119 | // The full implementation of this depends on CLDR data that is not yet available, | |
1120 | // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data. | |
1121 | // In the meantime do a quick bring-up by calling the old format method; this | |
1122 | // leaves some holes (even for data that is currently available, such as quarter). | |
1123 | // When the new CLDR data is available, update the data storage accordingly, | |
1124 | // rewrite this to use it directly, and rewrite the old format method to call this | |
1125 | // new one; that is covered by http://bugs.icu-project.org/trac/ticket/12171. | |
1126 | UDateDirection direction = UDAT_DIRECTION_COUNT; | |
1127 | if (offset > -2.1 && offset < 2.1) { | |
1128 | // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST | |
1129 | double offsetx100 = offset * 100.0; | |
1130 | int32_t intoffset = (offsetx100 < 0)? (int32_t)(offsetx100-0.5) : (int32_t)(offsetx100+0.5); | |
1131 | switch (intoffset) { | |
1132 | case -200/*-2*/: direction = UDAT_DIRECTION_LAST_2; break; | |
1133 | case -100/*-1*/: direction = UDAT_DIRECTION_LAST; break; | |
1134 | case 0/* 0*/: direction = UDAT_DIRECTION_THIS; break; | |
1135 | case 100/* 1*/: direction = UDAT_DIRECTION_NEXT; break; | |
1136 | case 200/* 2*/: direction = UDAT_DIRECTION_NEXT_2; break; | |
1137 | default: break; | |
1138 | } | |
1139 | } | |
1140 | UDateAbsoluteUnit absunit = UDAT_ABSOLUTE_UNIT_COUNT; | |
1141 | switch (unit) { | |
1142 | case UDAT_REL_UNIT_YEAR: absunit = UDAT_ABSOLUTE_YEAR; break; | |
3d1f044b | 1143 | case UDAT_REL_UNIT_QUARTER: absunit = UDAT_ABSOLUTE_QUARTER; break; |
2ca993e8 A |
1144 | case UDAT_REL_UNIT_MONTH: absunit = UDAT_ABSOLUTE_MONTH; break; |
1145 | case UDAT_REL_UNIT_WEEK: absunit = UDAT_ABSOLUTE_WEEK; break; | |
1146 | case UDAT_REL_UNIT_DAY: absunit = UDAT_ABSOLUTE_DAY; break; | |
1147 | case UDAT_REL_UNIT_SECOND: | |
1148 | if (direction == UDAT_DIRECTION_THIS) { | |
1149 | absunit = UDAT_ABSOLUTE_NOW; | |
1150 | direction = UDAT_DIRECTION_PLAIN; | |
1151 | } | |
1152 | break; | |
1153 | case UDAT_REL_UNIT_SUNDAY: absunit = UDAT_ABSOLUTE_SUNDAY; break; | |
1154 | case UDAT_REL_UNIT_MONDAY: absunit = UDAT_ABSOLUTE_MONDAY; break; | |
1155 | case UDAT_REL_UNIT_TUESDAY: absunit = UDAT_ABSOLUTE_TUESDAY; break; | |
1156 | case UDAT_REL_UNIT_WEDNESDAY: absunit = UDAT_ABSOLUTE_WEDNESDAY; break; | |
1157 | case UDAT_REL_UNIT_THURSDAY: absunit = UDAT_ABSOLUTE_THURSDAY; break; | |
1158 | case UDAT_REL_UNIT_FRIDAY: absunit = UDAT_ABSOLUTE_FRIDAY; break; | |
1159 | case UDAT_REL_UNIT_SATURDAY: absunit = UDAT_ABSOLUTE_SATURDAY; break; | |
1160 | default: break; | |
1161 | } | |
1162 | if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) { | |
3d1f044b A |
1163 | formatAbsoluteImpl(direction, absunit, output, status); |
1164 | if (output.getStringRef().length() != 0) { | |
1165 | return; | |
2ca993e8 A |
1166 | } |
1167 | } | |
1168 | // otherwise fallback to formatNumeric | |
3d1f044b | 1169 | formatNumericImpl(offset, unit, output, status); |
2ca993e8 A |
1170 | } |
1171 | ||
57a6839d | 1172 | UnicodeString& RelativeDateTimeFormatter::combineDateAndTime( |
2ca993e8 A |
1173 | const UnicodeString& relativeDateString, const UnicodeString& timeString, |
1174 | UnicodeString& appendTo, UErrorCode& status) const { | |
b331163b | 1175 | return fCache->getCombinedDateAndTime()->format( |
2ca993e8 | 1176 | timeString, relativeDateString, appendTo, status); |
57a6839d A |
1177 | } |
1178 | ||
3d1f044b A |
1179 | UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const { |
1180 | if (fOptBreakIterator == nullptr | |
b331163b | 1181 | || str.length() == 0 || !u_islower(str.char32At(0))) { |
3d1f044b | 1182 | return str; |
b331163b A |
1183 | } |
1184 | ||
1185 | // Must guarantee that one thread at a time accesses the shared break | |
1186 | // iterator. | |
3d1f044b A |
1187 | static UMutex *gBrkIterMutex = STATIC_NEW(UMutex); |
1188 | Mutex lock(gBrkIterMutex); | |
b331163b A |
1189 | str.toTitle( |
1190 | fOptBreakIterator->get(), | |
1191 | fLocale, | |
1192 | U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); | |
3d1f044b A |
1193 | return str; |
1194 | } | |
1195 | ||
1196 | UBool RelativeDateTimeFormatter::checkNoAdjustForContext(UErrorCode& status) const { | |
1197 | // This is unsupported because it's hard to keep fields in sync with title | |
1198 | // casing. The code could be written and tested if there is demand. | |
1199 | if (fOptBreakIterator != nullptr) { | |
1200 | status = U_UNSUPPORTED_ERROR; | |
1201 | return FALSE; | |
1202 | } | |
1203 | return TRUE; | |
b331163b A |
1204 | } |
1205 | ||
57a6839d | 1206 | void RelativeDateTimeFormatter::init( |
b331163b A |
1207 | NumberFormat *nfToAdopt, |
1208 | BreakIterator *biToAdopt, | |
1209 | UErrorCode &status) { | |
57a6839d | 1210 | LocalPointer<NumberFormat> nf(nfToAdopt); |
b331163b A |
1211 | LocalPointer<BreakIterator> bi(biToAdopt); |
1212 | UnifiedCache::getByLocale(fLocale, fCache, status); | |
1213 | if (U_FAILURE(status)) { | |
57a6839d A |
1214 | return; |
1215 | } | |
b331163b A |
1216 | const SharedPluralRules *pr = PluralRules::createSharedInstance( |
1217 | fLocale, UPLURAL_TYPE_CARDINAL, status); | |
57a6839d A |
1218 | if (U_FAILURE(status)) { |
1219 | return; | |
1220 | } | |
b331163b A |
1221 | SharedObject::copyPtr(pr, fPluralRules); |
1222 | pr->removeRef(); | |
57a6839d | 1223 | if (nf.isNull()) { |
b331163b A |
1224 | const SharedNumberFormat *shared = NumberFormat::createSharedInstance( |
1225 | fLocale, UNUM_DECIMAL, status); | |
57a6839d A |
1226 | if (U_FAILURE(status)) { |
1227 | return; | |
1228 | } | |
b331163b A |
1229 | SharedObject::copyPtr(shared, fNumberFormat); |
1230 | shared->removeRef(); | |
57a6839d A |
1231 | } else { |
1232 | SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias()); | |
3d1f044b | 1233 | if (shared == nullptr) { |
57a6839d A |
1234 | status = U_MEMORY_ALLOCATION_ERROR; |
1235 | return; | |
1236 | } | |
1237 | nf.orphan(); | |
b331163b A |
1238 | SharedObject::copyPtr(shared, fNumberFormat); |
1239 | } | |
1240 | if (bi.isNull()) { | |
1241 | SharedObject::clearPtr(fOptBreakIterator); | |
1242 | } else { | |
1243 | SharedBreakIterator *shared = new SharedBreakIterator(bi.getAlias()); | |
3d1f044b | 1244 | if (shared == nullptr) { |
b331163b A |
1245 | status = U_MEMORY_ALLOCATION_ERROR; |
1246 | return; | |
1247 | } | |
1248 | bi.orphan(); | |
1249 | SharedObject::copyPtr(shared, fOptBreakIterator); | |
57a6839d A |
1250 | } |
1251 | } | |
1252 | ||
57a6839d A |
1253 | U_NAMESPACE_END |
1254 | ||
2ca993e8 A |
1255 | // Plain C API |
1256 | ||
1257 | U_NAMESPACE_USE | |
1258 | ||
3d1f044b A |
1259 | |
1260 | // Magic number: "FRDT" (FormattedRelativeDateTime) in ASCII | |
1261 | UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL( | |
1262 | FormattedRelativeDateTime, | |
1263 | UFormattedRelativeDateTime, | |
1264 | UFormattedRelativeDateTimeImpl, | |
1265 | UFormattedRelativeDateTimeApiHelper, | |
1266 | ureldatefmt, | |
1267 | 0x46524454) | |
1268 | ||
1269 | ||
2ca993e8 A |
1270 | U_CAPI URelativeDateTimeFormatter* U_EXPORT2 |
1271 | ureldatefmt_open( const char* locale, | |
1272 | UNumberFormat* nfToAdopt, | |
1273 | UDateRelativeDateTimeFormatterStyle width, | |
1274 | UDisplayContext capitalizationContext, | |
1275 | UErrorCode* status ) | |
1276 | { | |
1277 | if (U_FAILURE(*status)) { | |
3d1f044b | 1278 | return nullptr; |
2ca993e8 A |
1279 | } |
1280 | LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale), | |
1281 | (NumberFormat*)nfToAdopt, width, | |
1282 | capitalizationContext, *status), *status); | |
1283 | if (U_FAILURE(*status)) { | |
3d1f044b | 1284 | return nullptr; |
2ca993e8 A |
1285 | } |
1286 | return (URelativeDateTimeFormatter*)formatter.orphan(); | |
1287 | } | |
1288 | ||
1289 | U_CAPI void U_EXPORT2 | |
1290 | ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt) | |
1291 | { | |
1292 | delete (RelativeDateTimeFormatter*)reldatefmt; | |
1293 | } | |
1294 | ||
1295 | U_CAPI int32_t U_EXPORT2 | |
1296 | ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt, | |
1297 | double offset, | |
1298 | URelativeDateTimeUnit unit, | |
1299 | UChar* result, | |
1300 | int32_t resultCapacity, | |
1301 | UErrorCode* status) | |
1302 | { | |
1303 | if (U_FAILURE(*status)) { | |
1304 | return 0; | |
1305 | } | |
3d1f044b | 1306 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) { |
2ca993e8 A |
1307 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1308 | return 0; | |
1309 | } | |
1310 | UnicodeString res; | |
3d1f044b A |
1311 | if (result != nullptr) { |
1312 | // nullptr destination for pure preflighting: empty dummy string | |
2ca993e8 A |
1313 | // otherwise, alias the destination buffer (copied from udat_format) |
1314 | res.setTo(result, 0, resultCapacity); | |
1315 | } | |
1316 | ((RelativeDateTimeFormatter*)reldatefmt)->formatNumeric(offset, unit, res, *status); | |
1317 | if (U_FAILURE(*status)) { | |
1318 | return 0; | |
1319 | } | |
1320 | return res.extract(result, resultCapacity, *status); | |
1321 | } | |
57a6839d | 1322 | |
3d1f044b A |
1323 | U_STABLE void U_EXPORT2 |
1324 | ureldatefmt_formatNumericToResult( | |
1325 | const URelativeDateTimeFormatter* reldatefmt, | |
1326 | double offset, | |
1327 | URelativeDateTimeUnit unit, | |
1328 | UFormattedRelativeDateTime* result, | |
1329 | UErrorCode* status) { | |
1330 | if (U_FAILURE(*status)) { | |
1331 | return; | |
1332 | } | |
1333 | auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt); | |
1334 | auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status); | |
1335 | resultImpl->fImpl = fmt->formatNumericToValue(offset, unit, *status); | |
1336 | } | |
1337 | ||
2ca993e8 A |
1338 | U_CAPI int32_t U_EXPORT2 |
1339 | ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt, | |
1340 | double offset, | |
1341 | URelativeDateTimeUnit unit, | |
1342 | UChar* result, | |
1343 | int32_t resultCapacity, | |
1344 | UErrorCode* status) | |
1345 | { | |
1346 | if (U_FAILURE(*status)) { | |
1347 | return 0; | |
1348 | } | |
3d1f044b | 1349 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) { |
2ca993e8 A |
1350 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1351 | return 0; | |
1352 | } | |
1353 | UnicodeString res; | |
3d1f044b A |
1354 | if (result != nullptr) { |
1355 | // nullptr destination for pure preflighting: empty dummy string | |
2ca993e8 A |
1356 | // otherwise, alias the destination buffer (copied from udat_format) |
1357 | res.setTo(result, 0, resultCapacity); | |
1358 | } | |
1359 | ((RelativeDateTimeFormatter*)reldatefmt)->format(offset, unit, res, *status); | |
1360 | if (U_FAILURE(*status)) { | |
1361 | return 0; | |
1362 | } | |
1363 | return res.extract(result, resultCapacity, *status); | |
1364 | } | |
1365 | ||
3d1f044b A |
1366 | U_DRAFT void U_EXPORT2 |
1367 | ureldatefmt_formatToResult( | |
1368 | const URelativeDateTimeFormatter* reldatefmt, | |
1369 | double offset, | |
1370 | URelativeDateTimeUnit unit, | |
1371 | UFormattedRelativeDateTime* result, | |
1372 | UErrorCode* status) { | |
1373 | if (U_FAILURE(*status)) { | |
1374 | return; | |
1375 | } | |
1376 | auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt); | |
1377 | auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status); | |
1378 | resultImpl->fImpl = fmt->formatToValue(offset, unit, *status); | |
1379 | } | |
1380 | ||
2ca993e8 A |
1381 | U_CAPI int32_t U_EXPORT2 |
1382 | ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt, | |
1383 | const UChar * relativeDateString, | |
1384 | int32_t relativeDateStringLen, | |
1385 | const UChar * timeString, | |
1386 | int32_t timeStringLen, | |
1387 | UChar* result, | |
1388 | int32_t resultCapacity, | |
1389 | UErrorCode* status ) | |
1390 | { | |
1391 | if (U_FAILURE(*status)) { | |
1392 | return 0; | |
1393 | } | |
3d1f044b A |
1394 | if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0 || |
1395 | (relativeDateString == nullptr ? relativeDateStringLen != 0 : relativeDateStringLen < -1) || | |
1396 | (timeString == nullptr ? timeStringLen != 0 : timeStringLen < -1)) { | |
2ca993e8 A |
1397 | *status = U_ILLEGAL_ARGUMENT_ERROR; |
1398 | return 0; | |
1399 | } | |
1400 | UnicodeString relDateStr((UBool)(relativeDateStringLen == -1), relativeDateString, relativeDateStringLen); | |
1401 | UnicodeString timeStr((UBool)(timeStringLen == -1), timeString, timeStringLen); | |
1402 | UnicodeString res(result, 0, resultCapacity); | |
1403 | ((RelativeDateTimeFormatter*)reldatefmt)->combineDateAndTime(relDateStr, timeStr, res, *status); | |
1404 | if (U_FAILURE(*status)) { | |
1405 | return 0; | |
1406 | } | |
1407 | return res.extract(result, resultCapacity, *status); | |
1408 | } | |
1409 | ||
1410 | #endif /* !UCONFIG_NO_FORMATTING */ |