|
1 /* |
|
2 ******************************************************************************* |
|
3 * Copyright (C) 2007-2013, International Business Machines Corporation and |
|
4 * others. All Rights Reserved. |
|
5 ******************************************************************************* |
|
6 */ |
|
7 |
|
8 #include "unicode/utypes.h" |
|
9 |
|
10 #if !UCONFIG_NO_FORMATTING |
|
11 |
|
12 #include <stdlib.h> |
|
13 |
|
14 #include "reldtfmt.h" |
|
15 #include "unicode/datefmt.h" |
|
16 #include "unicode/smpdtfmt.h" |
|
17 #include "unicode/msgfmt.h" |
|
18 |
|
19 #include "gregoimp.h" // for CalendarData |
|
20 #include "cmemory.h" |
|
21 #include "uresimp.h" |
|
22 |
|
23 U_NAMESPACE_BEGIN |
|
24 |
|
25 |
|
26 /** |
|
27 * An array of URelativeString structs is used to store the resource data loaded out of the bundle. |
|
28 */ |
|
29 struct URelativeString { |
|
30 int32_t offset; /** offset of this item, such as, the relative date **/ |
|
31 int32_t len; /** length of the string **/ |
|
32 const UChar* string; /** string, or NULL if not set **/ |
|
33 }; |
|
34 |
|
35 static const char DT_DateTimePatternsTag[]="DateTimePatterns"; |
|
36 |
|
37 |
|
38 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat) |
|
39 |
|
40 RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) : |
|
41 DateFormat(other), fDateTimeFormatter(NULL), fDatePattern(other.fDatePattern), |
|
42 fTimePattern(other.fTimePattern), fCombinedFormat(NULL), |
|
43 fDateStyle(other.fDateStyle), fLocale(other.fLocale), |
|
44 fDayMin(other.fDayMin), fDayMax(other.fDayMax), |
|
45 fDatesLen(other.fDatesLen), fDates(NULL) |
|
46 { |
|
47 if(other.fDateTimeFormatter != NULL) { |
|
48 fDateTimeFormatter = (SimpleDateFormat*)other.fDateTimeFormatter->clone(); |
|
49 } |
|
50 if(other.fCombinedFormat != NULL) { |
|
51 fCombinedFormat = (MessageFormat*)other.fCombinedFormat->clone(); |
|
52 } |
|
53 if (fDatesLen > 0) { |
|
54 fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); |
|
55 uprv_memcpy(fDates, other.fDates, sizeof(fDates[0])*fDatesLen); |
|
56 } |
|
57 } |
|
58 |
|
59 RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, |
|
60 const Locale& locale, UErrorCode& status) : |
|
61 DateFormat(), fDateTimeFormatter(NULL), fDatePattern(), fTimePattern(), fCombinedFormat(NULL), |
|
62 fDateStyle(dateStyle), fLocale(locale), fDatesLen(0), fDates(NULL) |
|
63 { |
|
64 if(U_FAILURE(status) ) { |
|
65 return; |
|
66 } |
|
67 |
|
68 if (timeStyle < UDAT_NONE || timeStyle > UDAT_SHORT) { |
|
69 // don't support other time styles (e.g. relative styles), for now |
|
70 status = U_ILLEGAL_ARGUMENT_ERROR; |
|
71 return; |
|
72 } |
|
73 UDateFormatStyle baseDateStyle = (dateStyle > UDAT_SHORT)? (UDateFormatStyle)(dateStyle & ~UDAT_RELATIVE): dateStyle; |
|
74 DateFormat * df; |
|
75 // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern). |
|
76 // We do need to get separate patterns for the date & time styles. |
|
77 if (baseDateStyle != UDAT_NONE) { |
|
78 df = createDateInstance((EStyle)baseDateStyle, locale); |
|
79 fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df); |
|
80 if (fDateTimeFormatter == NULL) { |
|
81 status = U_UNSUPPORTED_ERROR; |
|
82 return; |
|
83 } |
|
84 fDateTimeFormatter->toPattern(fDatePattern); |
|
85 if (timeStyle != UDAT_NONE) { |
|
86 df = createTimeInstance((EStyle)timeStyle, locale); |
|
87 SimpleDateFormat *sdf = dynamic_cast<SimpleDateFormat *>(df); |
|
88 if (sdf != NULL) { |
|
89 sdf->toPattern(fTimePattern); |
|
90 delete sdf; |
|
91 } |
|
92 } |
|
93 } else { |
|
94 // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter |
|
95 df = createTimeInstance((EStyle)timeStyle, locale); |
|
96 fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df); |
|
97 if (fDateTimeFormatter == NULL) { |
|
98 status = U_UNSUPPORTED_ERROR; |
|
99 return; |
|
100 } |
|
101 fDateTimeFormatter->toPattern(fTimePattern); |
|
102 } |
|
103 |
|
104 // Initialize the parent fCalendar, so that parse() works correctly. |
|
105 initializeCalendar(NULL, locale, status); |
|
106 loadDates(status); |
|
107 } |
|
108 |
|
109 RelativeDateFormat::~RelativeDateFormat() { |
|
110 delete fDateTimeFormatter; |
|
111 delete fCombinedFormat; |
|
112 uprv_free(fDates); |
|
113 } |
|
114 |
|
115 |
|
116 Format* RelativeDateFormat::clone(void) const { |
|
117 return new RelativeDateFormat(*this); |
|
118 } |
|
119 |
|
120 UBool RelativeDateFormat::operator==(const Format& other) const { |
|
121 if(DateFormat::operator==(other)) { |
|
122 // DateFormat::operator== guarantees following cast is safe |
|
123 RelativeDateFormat* that = (RelativeDateFormat*)&other; |
|
124 return (fDateStyle==that->fDateStyle && |
|
125 fDatePattern==that->fDatePattern && |
|
126 fTimePattern==that->fTimePattern && |
|
127 fLocale==that->fLocale); |
|
128 } |
|
129 return FALSE; |
|
130 } |
|
131 |
|
132 static const UChar APOSTROPHE = (UChar)0x0027; |
|
133 |
|
134 UnicodeString& RelativeDateFormat::format( Calendar& cal, |
|
135 UnicodeString& appendTo, |
|
136 FieldPosition& pos) const { |
|
137 |
|
138 UErrorCode status = U_ZERO_ERROR; |
|
139 UnicodeString relativeDayString; |
|
140 |
|
141 // calculate the difference, in days, between 'cal' and now. |
|
142 int dayDiff = dayDifference(cal, status); |
|
143 |
|
144 // look up string |
|
145 int32_t len = 0; |
|
146 const UChar *theString = getStringForDay(dayDiff, len, status); |
|
147 if(U_SUCCESS(status) && (theString!=NULL)) { |
|
148 // found a relative string |
|
149 relativeDayString.setTo(theString, len); |
|
150 } |
|
151 |
|
152 if (fDatePattern.isEmpty()) { |
|
153 fDateTimeFormatter->applyPattern(fTimePattern); |
|
154 fDateTimeFormatter->format(cal,appendTo,pos); |
|
155 } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { |
|
156 if (relativeDayString.length() > 0) { |
|
157 appendTo.append(relativeDayString); |
|
158 } else { |
|
159 fDateTimeFormatter->applyPattern(fDatePattern); |
|
160 fDateTimeFormatter->format(cal,appendTo,pos); |
|
161 } |
|
162 } else { |
|
163 UnicodeString datePattern; |
|
164 if (relativeDayString.length() > 0) { |
|
165 // Need to quote the relativeDayString to make it a legal date pattern |
|
166 relativeDayString.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2)); // double any existing APOSTROPHE |
|
167 relativeDayString.insert(0, APOSTROPHE); // add APOSTROPHE at beginning... |
|
168 relativeDayString.append(APOSTROPHE); // and at end |
|
169 datePattern.setTo(relativeDayString); |
|
170 } else { |
|
171 datePattern.setTo(fDatePattern); |
|
172 } |
|
173 UnicodeString combinedPattern; |
|
174 Formattable timeDatePatterns[] = { fTimePattern, datePattern }; |
|
175 fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, pos, status); // pos is ignored by this |
|
176 fDateTimeFormatter->applyPattern(combinedPattern); |
|
177 fDateTimeFormatter->format(cal,appendTo,pos); |
|
178 } |
|
179 |
|
180 return appendTo; |
|
181 } |
|
182 |
|
183 |
|
184 |
|
185 UnicodeString& |
|
186 RelativeDateFormat::format(const Formattable& obj, |
|
187 UnicodeString& appendTo, |
|
188 FieldPosition& pos, |
|
189 UErrorCode& status) const |
|
190 { |
|
191 // this is just here to get around the hiding problem |
|
192 // (the previous format() override would hide the version of |
|
193 // format() on DateFormat that this function correspond to, so we |
|
194 // have to redefine it here) |
|
195 return DateFormat::format(obj, appendTo, pos, status); |
|
196 } |
|
197 |
|
198 |
|
199 void RelativeDateFormat::parse( const UnicodeString& text, |
|
200 Calendar& cal, |
|
201 ParsePosition& pos) const { |
|
202 |
|
203 int32_t startIndex = pos.getIndex(); |
|
204 if (fDatePattern.isEmpty()) { |
|
205 // no date pattern, try parsing as time |
|
206 fDateTimeFormatter->applyPattern(fTimePattern); |
|
207 fDateTimeFormatter->parse(text,cal,pos); |
|
208 } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { |
|
209 // no time pattern or way to combine, try parsing as date |
|
210 // first check whether text matches a relativeDayString |
|
211 UBool matchedRelative = FALSE; |
|
212 for (int n=0; n < fDatesLen && !matchedRelative; n++) { |
|
213 if (fDates[n].string != NULL && |
|
214 text.compare(startIndex, fDates[n].len, fDates[n].string) == 0) { |
|
215 // it matched, handle the relative day string |
|
216 UErrorCode status = U_ZERO_ERROR; |
|
217 matchedRelative = TRUE; |
|
218 |
|
219 // Set the calendar to now+offset |
|
220 cal.setTime(Calendar::getNow(),status); |
|
221 cal.add(UCAL_DATE,fDates[n].offset, status); |
|
222 |
|
223 if(U_FAILURE(status)) { |
|
224 // failure in setting calendar field, set offset to beginning of rel day string |
|
225 pos.setErrorIndex(startIndex); |
|
226 } else { |
|
227 pos.setIndex(startIndex + fDates[n].len); |
|
228 } |
|
229 } |
|
230 } |
|
231 if (!matchedRelative) { |
|
232 // just parse as normal date |
|
233 fDateTimeFormatter->applyPattern(fDatePattern); |
|
234 fDateTimeFormatter->parse(text,cal,pos); |
|
235 } |
|
236 } else { |
|
237 // Here we replace any relativeDayString in text with the equivalent date |
|
238 // formatted per fDatePattern, then parse text normally using the combined pattern. |
|
239 UnicodeString modifiedText(text); |
|
240 FieldPosition fPos; |
|
241 int32_t dateStart = 0, origDateLen = 0, modDateLen = 0; |
|
242 UErrorCode status = U_ZERO_ERROR; |
|
243 for (int n=0; n < fDatesLen; n++) { |
|
244 int32_t relativeStringOffset; |
|
245 if (fDates[n].string != NULL && |
|
246 (relativeStringOffset = modifiedText.indexOf(fDates[n].string, fDates[n].len, startIndex)) >= startIndex) { |
|
247 // it matched, replace the relative date with a real one for parsing |
|
248 UnicodeString dateString; |
|
249 Calendar * tempCal = cal.clone(); |
|
250 |
|
251 // Set the calendar to now+offset |
|
252 tempCal->setTime(Calendar::getNow(),status); |
|
253 tempCal->add(UCAL_DATE,fDates[n].offset, status); |
|
254 if(U_FAILURE(status)) { |
|
255 pos.setErrorIndex(startIndex); |
|
256 delete tempCal; |
|
257 return; |
|
258 } |
|
259 |
|
260 fDateTimeFormatter->applyPattern(fDatePattern); |
|
261 fDateTimeFormatter->format(*tempCal, dateString, fPos); |
|
262 dateStart = relativeStringOffset; |
|
263 origDateLen = fDates[n].len; |
|
264 modDateLen = dateString.length(); |
|
265 modifiedText.replace(dateStart, origDateLen, dateString); |
|
266 delete tempCal; |
|
267 break; |
|
268 } |
|
269 } |
|
270 UnicodeString combinedPattern; |
|
271 Formattable timeDatePatterns[] = { fTimePattern, fDatePattern }; |
|
272 fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, fPos, status); // pos is ignored by this |
|
273 fDateTimeFormatter->applyPattern(combinedPattern); |
|
274 fDateTimeFormatter->parse(modifiedText,cal,pos); |
|
275 |
|
276 // Adjust offsets |
|
277 UBool noError = (pos.getErrorIndex() < 0); |
|
278 int32_t offset = (noError)? pos.getIndex(): pos.getErrorIndex(); |
|
279 if (offset >= dateStart + modDateLen) { |
|
280 // offset at or after the end of the replaced text, |
|
281 // correct by the difference between original and replacement |
|
282 offset -= (modDateLen - origDateLen); |
|
283 } else if (offset >= dateStart) { |
|
284 // offset in the replaced text, set it to the beginning of that text |
|
285 // (i.e. the beginning of the relative day string) |
|
286 offset = dateStart; |
|
287 } |
|
288 if (noError) { |
|
289 pos.setIndex(offset); |
|
290 } else { |
|
291 pos.setErrorIndex(offset); |
|
292 } |
|
293 } |
|
294 } |
|
295 |
|
296 UDate |
|
297 RelativeDateFormat::parse( const UnicodeString& text, |
|
298 ParsePosition& pos) const { |
|
299 // redefined here because the other parse() function hides this function's |
|
300 // cunterpart on DateFormat |
|
301 return DateFormat::parse(text, pos); |
|
302 } |
|
303 |
|
304 UDate |
|
305 RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const |
|
306 { |
|
307 // redefined here because the other parse() function hides this function's |
|
308 // counterpart on DateFormat |
|
309 return DateFormat::parse(text, status); |
|
310 } |
|
311 |
|
312 |
|
313 const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const { |
|
314 if(U_FAILURE(status)) { |
|
315 return NULL; |
|
316 } |
|
317 |
|
318 // Is it outside the resource bundle's range? |
|
319 if(day < fDayMin || day > fDayMax) { |
|
320 return NULL; // don't have it. |
|
321 } |
|
322 |
|
323 // Linear search the held strings |
|
324 for(int n=0;n<fDatesLen;n++) { |
|
325 if(fDates[n].offset == day) { |
|
326 len = fDates[n].len; |
|
327 return fDates[n].string; |
|
328 } |
|
329 } |
|
330 |
|
331 return NULL; // not found. |
|
332 } |
|
333 |
|
334 UnicodeString& |
|
335 RelativeDateFormat::toPattern(UnicodeString& result, UErrorCode& status) const |
|
336 { |
|
337 if (!U_FAILURE(status)) { |
|
338 result.remove(); |
|
339 if (fDatePattern.isEmpty()) { |
|
340 result.setTo(fTimePattern); |
|
341 } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { |
|
342 result.setTo(fDatePattern); |
|
343 } else { |
|
344 Formattable timeDatePatterns[] = { fTimePattern, fDatePattern }; |
|
345 FieldPosition pos; |
|
346 fCombinedFormat->format(timeDatePatterns, 2, result, pos, status); |
|
347 } |
|
348 } |
|
349 return result; |
|
350 } |
|
351 |
|
352 UnicodeString& |
|
353 RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const |
|
354 { |
|
355 if (!U_FAILURE(status)) { |
|
356 result.remove(); |
|
357 result.setTo(fDatePattern); |
|
358 } |
|
359 return result; |
|
360 } |
|
361 |
|
362 UnicodeString& |
|
363 RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const |
|
364 { |
|
365 if (!U_FAILURE(status)) { |
|
366 result.remove(); |
|
367 result.setTo(fTimePattern); |
|
368 } |
|
369 return result; |
|
370 } |
|
371 |
|
372 void |
|
373 RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status) |
|
374 { |
|
375 if (!U_FAILURE(status)) { |
|
376 fDatePattern.setTo(datePattern); |
|
377 fTimePattern.setTo(timePattern); |
|
378 } |
|
379 } |
|
380 |
|
381 const DateFormatSymbols* |
|
382 RelativeDateFormat::getDateFormatSymbols() const |
|
383 { |
|
384 return fDateTimeFormatter->getDateFormatSymbols(); |
|
385 } |
|
386 |
|
387 void RelativeDateFormat::loadDates(UErrorCode &status) { |
|
388 CalendarData calData(fLocale, "gregorian", status); |
|
389 |
|
390 UErrorCode tempStatus = status; |
|
391 UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus); |
|
392 if(U_SUCCESS(tempStatus)) { |
|
393 int32_t patternsSize = ures_getSize(dateTimePatterns); |
|
394 if (patternsSize > kDateTime) { |
|
395 int32_t resStrLen = 0; |
|
396 |
|
397 int32_t glueIndex = kDateTime; |
|
398 if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) { |
|
399 // Get proper date time format |
|
400 switch (fDateStyle) { |
|
401 case kFullRelative: |
|
402 case kFull: |
|
403 glueIndex = kDateTimeOffset + kFull; |
|
404 break; |
|
405 case kLongRelative: |
|
406 case kLong: |
|
407 glueIndex = kDateTimeOffset + kLong; |
|
408 break; |
|
409 case kMediumRelative: |
|
410 case kMedium: |
|
411 glueIndex = kDateTimeOffset + kMedium; |
|
412 break; |
|
413 case kShortRelative: |
|
414 case kShort: |
|
415 glueIndex = kDateTimeOffset + kShort; |
|
416 break; |
|
417 default: |
|
418 break; |
|
419 } |
|
420 } |
|
421 |
|
422 const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus); |
|
423 fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus); |
|
424 } |
|
425 } |
|
426 |
|
427 UResourceBundle *rb = ures_open(NULL, fLocale.getBaseName(), &status); |
|
428 UResourceBundle *sb = ures_getByKeyWithFallback(rb, "fields", NULL, &status); |
|
429 rb = ures_getByKeyWithFallback(sb, "day", rb, &status); |
|
430 sb = ures_getByKeyWithFallback(rb, "relative", sb, &status); |
|
431 ures_close(rb); |
|
432 // set up min/max |
|
433 fDayMin=-1; |
|
434 fDayMax=1; |
|
435 |
|
436 if(U_FAILURE(status)) { |
|
437 fDatesLen=0; |
|
438 ures_close(sb); |
|
439 return; |
|
440 } |
|
441 |
|
442 fDatesLen = ures_getSize(sb); |
|
443 fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); |
|
444 |
|
445 // Load in each item into the array... |
|
446 int n = 0; |
|
447 |
|
448 UResourceBundle *subString = NULL; |
|
449 |
|
450 while(ures_hasNext(sb) && U_SUCCESS(status)) { // iterate over items |
|
451 subString = ures_getNextResource(sb, subString, &status); |
|
452 |
|
453 if(U_FAILURE(status) || (subString==NULL)) break; |
|
454 |
|
455 // key = offset # |
|
456 const char *key = ures_getKey(subString); |
|
457 |
|
458 // load the string and length |
|
459 int32_t aLen; |
|
460 const UChar* aString = ures_getString(subString, &aLen, &status); |
|
461 |
|
462 if(U_FAILURE(status) || aString == NULL) break; |
|
463 |
|
464 // calculate the offset |
|
465 int32_t offset = atoi(key); |
|
466 |
|
467 // set min/max |
|
468 if(offset < fDayMin) { |
|
469 fDayMin = offset; |
|
470 } |
|
471 if(offset > fDayMax) { |
|
472 fDayMax = offset; |
|
473 } |
|
474 |
|
475 // copy the string pointer |
|
476 fDates[n].offset = offset; |
|
477 fDates[n].string = aString; |
|
478 fDates[n].len = aLen; |
|
479 |
|
480 n++; |
|
481 } |
|
482 ures_close(subString); |
|
483 ures_close(sb); |
|
484 |
|
485 // the fDates[] array could be sorted here, for direct access. |
|
486 } |
|
487 |
|
488 |
|
489 // this should to be in DateFormat, instead it was copied from SimpleDateFormat. |
|
490 |
|
491 Calendar* |
|
492 RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status) |
|
493 { |
|
494 if(!U_FAILURE(status)) { |
|
495 fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status); |
|
496 } |
|
497 if (U_SUCCESS(status) && fCalendar == NULL) { |
|
498 status = U_MEMORY_ALLOCATION_ERROR; |
|
499 } |
|
500 return fCalendar; |
|
501 } |
|
502 |
|
503 int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) { |
|
504 if(U_FAILURE(status)) { |
|
505 return 0; |
|
506 } |
|
507 // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type |
|
508 Calendar *nowCal = cal.clone(); |
|
509 nowCal->setTime(Calendar::getNow(), status); |
|
510 |
|
511 // For the day difference, we are interested in the difference in the (modified) julian day number |
|
512 // which is midnight to midnight. Using fieldDifference() is NOT correct here, because |
|
513 // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow". |
|
514 int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status); |
|
515 |
|
516 delete nowCal; |
|
517 return dayDiff; |
|
518 } |
|
519 |
|
520 U_NAMESPACE_END |
|
521 |
|
522 #endif |
|
523 |