]> git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/reldtfmt.cpp
ICU-511.32.tar.gz
[apple/icu.git] / icuSources / i18n / reldtfmt.cpp
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