1 /*
2 * Copyright 2003-2009, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2009-2010, Adrien Destugues, pulkomandy@gmail.com.
4 * Copyright 2010-2011, Oliver Tappe <zooey@hirschkaefer.de>.
5 * Distributed under the terms of the MIT License.
6 */
7
8
9 #include <unicode/uversion.h>
10 #include <FormattingConventions.h>
11
12 #include <AutoDeleter.h>
13 #include <IconUtils.h>
14 #include <List.h>
15 #include <Language.h>
16 #include <Locale.h>
17 #include <LocaleRoster.h>
18 #include <Resources.h>
19 #include <String.h>
20 #include <UnicodeChar.h>
21
22 #include <unicode/datefmt.h>
23 #include <unicode/locid.h>
24 #include <unicode/smpdtfmt.h>
25 #include <unicode/ulocdata.h>
26 #include <ICUWrapper.h>
27
28 #include <iostream>
29 #include <map>
30 #include <monetary.h>
31 #include <new>
32 #include <stdarg.h>
33 #include <stdlib.h>
34
35
36 U_NAMESPACE_USE
37
38
39 // #pragma mark - helpers
40
41
42 static bool
FormatUsesAmPm(const BString & format)43 FormatUsesAmPm(const BString& format)
44 {
45 if (format.Length() == 0)
46 return false;
47
48 bool inQuote = false;
49 for (const char* s = format.String(); *s != '\0'; ++s) {
50 switch (*s) {
51 case '\'':
52 inQuote = !inQuote;
53 break;
54 case 'a':
55 if (!inQuote)
56 return true;
57 break;
58 }
59 }
60
61 return false;
62 }
63
64
65 static void
CoerceFormatTo12HourClock(BString & format)66 CoerceFormatTo12HourClock(BString& format)
67 {
68 char* s = format.LockBuffer(format.Length());
69 if (s == NULL)
70 return;
71
72 // change format to use h instead of H, k instead of K, and append an
73 // am/pm marker
74 bool inQuote = false;
75 for (; *s != '\0'; ++s) {
76 switch (*s) {
77 case '\'':
78 inQuote = !inQuote;
79 break;
80 case 'H':
81 if (!inQuote)
82 *s = 'h';
83 break;
84 case 'K':
85 if (!inQuote)
86 *s = 'k';
87 break;
88 }
89 }
90 format.UnlockBuffer(format.Length());
91
92 format.Append(" a");
93 }
94
95
96 static void
CoerceFormatTo24HourClock(BString & format)97 CoerceFormatTo24HourClock(BString& format)
98 {
99 char* buffer = format.LockBuffer(format.Length());
100 char* currentPos = buffer;
101 if (currentPos == NULL)
102 return;
103
104 // change the format to use H instead of h, K instead of k, and determine
105 // and remove the am/pm marker (including leading whitespace)
106 bool inQuote = false;
107 bool lastWasWhitespace = false;
108 uint32 ch;
109 const char* amPmStartPos = NULL;
110 const char* amPmEndPos = NULL;
111 const char* lastWhitespaceStart = NULL;
112 for (char* previousPos = currentPos; (ch = BUnicodeChar::FromUTF8(
113 (const char**)¤tPos)) != 0; previousPos = currentPos) {
114 switch (ch) {
115 case '\'':
116 inQuote = !inQuote;
117 break;
118 case 'h':
119 if (!inQuote)
120 *previousPos = 'H';
121 break;
122 case 'k':
123 if (!inQuote)
124 *previousPos = 'K';
125 break;
126 case 'a':
127 if (!inQuote) {
128 if (lastWasWhitespace)
129 amPmStartPos = lastWhitespaceStart;
130 else
131 amPmStartPos = previousPos;
132 amPmEndPos = currentPos;
133 }
134 break;
135 default:
136 if (!inQuote && BUnicodeChar::IsWhitespace(ch)) {
137 if (!lastWasWhitespace) {
138 lastWhitespaceStart = previousPos;
139 lastWasWhitespace = true;
140 }
141 continue;
142 }
143 }
144 lastWasWhitespace = false;
145 }
146
147 format.UnlockBuffer(format.Length());
148 if (amPmStartPos != NULL && amPmEndPos > amPmStartPos)
149 format.Remove(amPmStartPos - buffer, amPmEndPos - amPmStartPos);
150 }
151
152
153 static void
CoerceFormatToAbbreviatedTimezone(BString & format)154 CoerceFormatToAbbreviatedTimezone(BString& format)
155 {
156 char* s = format.LockBuffer(format.Length());
157 if (s == NULL)
158 return;
159
160 // replace a single 'z' with 'V'
161 bool inQuote = false;
162 bool lastWasZ = false;
163 for (; *s != '\0'; ++s) {
164 switch (*s) {
165 case '\'':
166 inQuote = !inQuote;
167 break;
168 case 'z':
169 if (!inQuote && !lastWasZ && *(s+1) != 'z')
170 *s = 'V';
171 lastWasZ = true;
172 continue;
173 }
174 lastWasZ = false;
175 }
176 format.UnlockBuffer(format.Length());
177 }
178
179
180 // #pragma mark - BFormattingConventions
181
182
183 enum ClockHoursState {
184 CLOCK_HOURS_UNSET = 0,
185 CLOCK_HOURS_24,
186 CLOCK_HOURS_12
187 };
188
189
BFormattingConventions(const char * id)190 BFormattingConventions::BFormattingConventions(const char* id)
191 :
192 fCachedUse24HourClock(CLOCK_HOURS_UNSET),
193 fExplicitUse24HourClock(CLOCK_HOURS_UNSET),
194 fUseStringsFromPreferredLanguage(false),
195 fICULocale(new icu::Locale(id))
196 {
197 }
198
199
BFormattingConventions(const BFormattingConventions & other)200 BFormattingConventions::BFormattingConventions(
201 const BFormattingConventions& other)
202 :
203 fCachedNumericFormat(other.fCachedNumericFormat),
204 fCachedMonetaryFormat(other.fCachedMonetaryFormat),
205 fCachedUse24HourClock(other.fCachedUse24HourClock),
206 fExplicitNumericFormat(other.fExplicitNumericFormat),
207 fExplicitMonetaryFormat(other.fExplicitMonetaryFormat),
208 fExplicitUse24HourClock(other.fExplicitUse24HourClock),
209 fUseStringsFromPreferredLanguage(other.fUseStringsFromPreferredLanguage),
210 fICULocale(new icu::Locale(*other.fICULocale))
211 {
212 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) {
213 fCachedDateFormats[s] = other.fCachedDateFormats[s];
214 fExplicitDateFormats[s] = other.fExplicitDateFormats[s];
215
216 for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) {
217 fCachedDateTimeFormats[s][t] = other.fCachedDateFormats[s][t];
218 fExplicitDateTimeFormats[s][t] = other.fExplicitDateFormats[s][t];
219 }
220 }
221 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) {
222 fCachedTimeFormats[s] = other.fCachedTimeFormats[s];
223 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s];
224 }
225 }
226
227
BFormattingConventions(const BMessage * archive)228 BFormattingConventions::BFormattingConventions(const BMessage* archive)
229 :
230 fCachedUse24HourClock(CLOCK_HOURS_UNSET),
231 fExplicitUse24HourClock(CLOCK_HOURS_UNSET),
232 fUseStringsFromPreferredLanguage(false)
233 {
234 BString conventionsID;
235 status_t status = archive->FindString("conventions", &conventionsID);
236 fICULocale = new icu::Locale(conventionsID);
237
238 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) {
239 BString format;
240 status = archive->FindString("dateFormat", s, &format);
241 if (status == B_OK)
242 fExplicitDateFormats[s] = format;
243
244 status = archive->FindString("timeFormat", s, &format);
245 if (status == B_OK)
246 fExplicitTimeFormats[s] = format;
247 }
248
249 if (status == B_OK) {
250 int8 use24HourClock;
251 status = archive->FindInt8("use24HourClock", &use24HourClock);
252 if (status == B_OK)
253 fExplicitUse24HourClock = use24HourClock;
254 }
255 if (status == B_OK) {
256 bool useStringsFromPreferredLanguage;
257 status = archive->FindBool("useStringsFromPreferredLanguage",
258 &useStringsFromPreferredLanguage);
259 if (status == B_OK)
260 fUseStringsFromPreferredLanguage = useStringsFromPreferredLanguage;
261 }
262 }
263
264
265 BFormattingConventions&
operator =(const BFormattingConventions & other)266 BFormattingConventions::operator=(const BFormattingConventions& other)
267 {
268 if (this == &other)
269 return *this;
270
271 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) {
272 fCachedDateFormats[s] = other.fCachedDateFormats[s];
273 fExplicitDateFormats[s] = other.fExplicitDateFormats[s];
274 for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) {
275 fCachedDateTimeFormats[s][t] = other.fCachedDateTimeFormats[s][t];
276 fExplicitDateTimeFormats[s][t]
277 = other.fExplicitDateTimeFormats[s][t];
278 }
279 }
280 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) {
281 fCachedTimeFormats[s] = other.fCachedTimeFormats[s];
282 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s];
283 }
284 fCachedNumericFormat = other.fCachedNumericFormat;
285 fCachedMonetaryFormat = other.fCachedMonetaryFormat;
286 fCachedUse24HourClock = other.fCachedUse24HourClock;
287
288 fExplicitNumericFormat = other.fExplicitNumericFormat;
289 fExplicitMonetaryFormat = other.fExplicitMonetaryFormat;
290 fExplicitUse24HourClock = other.fExplicitUse24HourClock;
291
292 fUseStringsFromPreferredLanguage = other.fUseStringsFromPreferredLanguage;
293
294 *fICULocale = *other.fICULocale;
295
296 return *this;
297 }
298
299
~BFormattingConventions()300 BFormattingConventions::~BFormattingConventions()
301 {
302 delete fICULocale;
303 }
304
305
306 bool
operator ==(const BFormattingConventions & other) const307 BFormattingConventions::operator==(const BFormattingConventions& other) const
308 {
309 if (this == &other)
310 return true;
311
312 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) {
313 if (fExplicitDateFormats[s] != other.fExplicitDateFormats[s])
314 return false;
315 }
316 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) {
317 if (fExplicitTimeFormats[s] != other.fExplicitTimeFormats[s])
318 return false;
319 }
320
321 return fExplicitNumericFormat == other.fExplicitNumericFormat
322 && fExplicitMonetaryFormat == other.fExplicitMonetaryFormat
323 && fExplicitUse24HourClock == other.fExplicitUse24HourClock
324 && fUseStringsFromPreferredLanguage
325 == other.fUseStringsFromPreferredLanguage
326 && *fICULocale == *other.fICULocale;
327 }
328
329
330 bool
operator !=(const BFormattingConventions & other) const331 BFormattingConventions::operator!=(const BFormattingConventions& other) const
332 {
333 return !(*this == other);
334 }
335
336
337 const char*
ID() const338 BFormattingConventions::ID() const
339 {
340 return fICULocale->getName();
341 }
342
343
344 const char*
LanguageCode() const345 BFormattingConventions::LanguageCode() const
346 {
347 return fICULocale->getLanguage();
348 }
349
350
351 const char*
CountryCode() const352 BFormattingConventions::CountryCode() const
353 {
354 const char* country = fICULocale->getCountry();
355 if (country == NULL || country[0] == '\0')
356 return NULL;
357
358 return country;
359 }
360
361
362 bool
AreCountrySpecific() const363 BFormattingConventions::AreCountrySpecific() const
364 {
365 return CountryCode() != NULL;
366 }
367
368
369 status_t
GetNativeName(BString & name) const370 BFormattingConventions::GetNativeName(BString& name) const
371 {
372 UnicodeString string;
373 fICULocale->getDisplayName(*fICULocale, string);
374 string.toTitle(NULL, *fICULocale);
375
376 name.Truncate(0);
377 BStringByteSink converter(&name);
378 string.toUTF8(converter);
379
380 return B_OK;
381 }
382
383
384 status_t
GetName(BString & name,const BLanguage * displayLanguage) const385 BFormattingConventions::GetName(BString& name,
386 const BLanguage* displayLanguage) const
387 {
388 BString displayLanguageID;
389 if (displayLanguage == NULL) {
390 BLanguage defaultLanguage;
391 BLocale::Default()->GetLanguage(&defaultLanguage);
392 displayLanguageID = defaultLanguage.Code();
393 } else {
394 displayLanguageID = displayLanguage->Code();
395 }
396
397 UnicodeString uString;
398 fICULocale->getDisplayName(Locale(displayLanguageID.String()), uString);
399 name.Truncate(0);
400 BStringByteSink stringConverter(&name);
401 uString.toUTF8(stringConverter);
402
403 return B_OK;
404 }
405
406
407 BMeasurementKind
MeasurementKind() const408 BFormattingConventions::MeasurementKind() const
409 {
410 UErrorCode error = U_ZERO_ERROR;
411 switch (ulocdata_getMeasurementSystem(ID(), &error)) {
412 case UMS_US:
413 return B_US;
414 case UMS_SI:
415 default:
416 return B_METRIC;
417 }
418 }
419
420
421 status_t
GetDateFormat(BDateFormatStyle style,BString & outFormat) const422 BFormattingConventions::GetDateFormat(BDateFormatStyle style,
423 BString& outFormat) const
424 {
425 if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT)
426 return B_BAD_VALUE;
427
428 outFormat = fExplicitDateFormats[style].Length()
429 ? fExplicitDateFormats[style]
430 : fCachedDateFormats[style];
431
432 if (outFormat.Length() > 0)
433 return B_OK;
434
435 ObjectDeleter<DateFormat> dateFormatter(
436 DateFormat::createDateInstance((DateFormat::EStyle)style, *fICULocale));
437 if (!dateFormatter.IsSet())
438 return B_NO_MEMORY;
439
440 SimpleDateFormat* dateFormatterImpl
441 = static_cast<SimpleDateFormat*>(dateFormatter.Get());
442
443 UnicodeString icuString;
444 dateFormatterImpl->toPattern(icuString);
445 BStringByteSink stringConverter(&outFormat);
446 icuString.toUTF8(stringConverter);
447
448 fCachedDateFormats[style] = outFormat;
449
450 return B_OK;
451 }
452
453
454 status_t
GetTimeFormat(BTimeFormatStyle style,BString & outFormat) const455 BFormattingConventions::GetTimeFormat(BTimeFormatStyle style,
456 BString& outFormat) const
457 {
458 if (style < 0 || style >= B_TIME_FORMAT_STYLE_COUNT)
459 return B_BAD_VALUE;
460
461 outFormat = fExplicitTimeFormats[style].Length()
462 ? fExplicitTimeFormats[style]
463 : fCachedTimeFormats[style];
464
465 if (outFormat.Length() > 0)
466 return B_OK;
467
468 ObjectDeleter<DateFormat> timeFormatter(
469 DateFormat::createTimeInstance((DateFormat::EStyle)style, *fICULocale));
470 if (!timeFormatter.IsSet())
471 return B_NO_MEMORY;
472
473 SimpleDateFormat* timeFormatterImpl
474 = static_cast<SimpleDateFormat*>(timeFormatter.Get());
475
476 UnicodeString icuString;
477 timeFormatterImpl->toPattern(icuString);
478 BStringByteSink stringConverter(&outFormat);
479 icuString.toUTF8(stringConverter);
480
481 CoerceFormatForClock(outFormat);
482
483 if (style != B_FULL_TIME_FORMAT) {
484 // use abbreviated timezone in short timezone format
485 CoerceFormatToAbbreviatedTimezone(outFormat);
486 }
487
488 fCachedTimeFormats[style] = outFormat;
489
490 return B_OK;
491 }
492
493
494 status_t
GetDateTimeFormat(BDateFormatStyle dateStyle,BTimeFormatStyle timeStyle,BString & outFormat) const495 BFormattingConventions::GetDateTimeFormat(BDateFormatStyle dateStyle,
496 BTimeFormatStyle timeStyle, BString& outFormat) const
497 {
498 if (dateStyle < 0 || dateStyle >= B_DATE_FORMAT_STYLE_COUNT)
499 return B_BAD_VALUE;
500
501 if (timeStyle < 0 || timeStyle >= B_TIME_FORMAT_STYLE_COUNT)
502 return B_BAD_VALUE;
503
504 outFormat = fExplicitDateTimeFormats[dateStyle][timeStyle].Length()
505 ? fExplicitDateTimeFormats[dateStyle][timeStyle]
506 : fCachedDateTimeFormats[dateStyle][timeStyle];
507
508 if (outFormat.Length() > 0)
509 return B_OK;
510
511 ObjectDeleter<DateFormat> dateFormatter(
512 DateFormat::createDateTimeInstance((DateFormat::EStyle)dateStyle,
513 (DateFormat::EStyle)timeStyle, *fICULocale));
514 if (!dateFormatter.IsSet())
515 return B_NO_MEMORY;
516
517 SimpleDateFormat* dateFormatterImpl
518 = static_cast<SimpleDateFormat*>(dateFormatter.Get());
519
520 UnicodeString icuString;
521 dateFormatterImpl->toPattern(icuString);
522 BStringByteSink stringConverter(&outFormat);
523 icuString.toUTF8(stringConverter);
524
525 CoerceFormatForClock(outFormat);
526
527 if (dateStyle != B_FULL_DATE_FORMAT) {
528 // use abbreviated timezone in short timezone format
529 CoerceFormatToAbbreviatedTimezone(outFormat);
530 }
531
532 fCachedDateTimeFormats[dateStyle][timeStyle] = outFormat;
533
534 return B_OK;
535 }
536
537
538 status_t
GetNumericFormat(BString & outFormat) const539 BFormattingConventions::GetNumericFormat(BString& outFormat) const
540 {
541 // TODO!
542 return B_UNSUPPORTED;
543 }
544
545
546 status_t
GetMonetaryFormat(BString & outFormat) const547 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const
548 {
549 // TODO!
550 return B_UNSUPPORTED;
551 }
552
553
554 void
SetExplicitDateFormat(BDateFormatStyle style,const BString & format)555 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style,
556 const BString& format)
557 {
558 fExplicitDateFormats[style] = format;
559 }
560
561
562 void
SetExplicitTimeFormat(BTimeFormatStyle style,const BString & format)563 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style,
564 const BString& format)
565 {
566 fExplicitTimeFormats[style] = format;
567 }
568
569
570 void
SetExplicitDateTimeFormat(BDateFormatStyle dateStyle,BTimeFormatStyle timeStyle,const BString & format)571 BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle,
572 BTimeFormatStyle timeStyle, const BString& format)
573 {
574 fExplicitDateTimeFormats[dateStyle][timeStyle] = format;
575 }
576
577
578 void
SetExplicitNumericFormat(const BString & format)579 BFormattingConventions::SetExplicitNumericFormat(const BString& format)
580 {
581 fExplicitNumericFormat = format;
582 }
583
584
585 void
SetExplicitMonetaryFormat(const BString & format)586 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format)
587 {
588 fExplicitMonetaryFormat = format;
589 }
590
591
592 bool
UseStringsFromPreferredLanguage() const593 BFormattingConventions::UseStringsFromPreferredLanguage() const
594 {
595 return fUseStringsFromPreferredLanguage;
596 }
597
598
599 void
SetUseStringsFromPreferredLanguage(bool value)600 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value)
601 {
602 fUseStringsFromPreferredLanguage = value;
603 }
604
605
606 bool
Use24HourClock() const607 BFormattingConventions::Use24HourClock() const
608 {
609 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
610 ? fExplicitUse24HourClock : fCachedUse24HourClock;
611
612 if (use24HourClock == CLOCK_HOURS_UNSET) {
613 BString format;
614 GetTimeFormat(B_MEDIUM_TIME_FORMAT, format);
615 fCachedUse24HourClock
616 = FormatUsesAmPm(format) ? CLOCK_HOURS_12 : CLOCK_HOURS_24;
617 return fCachedUse24HourClock == CLOCK_HOURS_24;
618 }
619
620 return fExplicitUse24HourClock == CLOCK_HOURS_24;
621 }
622
623
624 void
SetExplicitUse24HourClock(bool value)625 BFormattingConventions::SetExplicitUse24HourClock(bool value)
626 {
627 int8 newUse24HourClock = value ? CLOCK_HOURS_24 : CLOCK_HOURS_12;
628 if (fExplicitUse24HourClock == newUse24HourClock)
629 return;
630
631 fExplicitUse24HourClock = newUse24HourClock;
632
633 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
634 fCachedTimeFormats[s].Truncate(0);
635 }
636
637
638 void
UnsetExplicitUse24HourClock()639 BFormattingConventions::UnsetExplicitUse24HourClock()
640 {
641 fExplicitUse24HourClock = CLOCK_HOURS_UNSET;
642
643 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
644 fCachedTimeFormats[s].Truncate(0);
645 }
646
647
648 status_t
Archive(BMessage * archive,bool deep) const649 BFormattingConventions::Archive(BMessage* archive, bool deep) const
650 {
651 status_t status = archive->AddString("conventions", fICULocale->getName());
652 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) {
653 status = archive->AddString("dateFormat", fExplicitDateFormats[s]);
654 if (status == B_OK)
655 status = archive->AddString("timeFormat", fExplicitTimeFormats[s]);
656 }
657 if (status == B_OK)
658 status = archive->AddInt8("use24HourClock", fExplicitUse24HourClock);
659 if (status == B_OK) {
660 status = archive->AddBool("useStringsFromPreferredLanguage",
661 fUseStringsFromPreferredLanguage);
662 }
663
664 return status;
665 }
666
667
668 void
CoerceFormatForClock(BString & outFormat) const669 BFormattingConventions::CoerceFormatForClock(BString& outFormat) const
670 {
671 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
672 ? fExplicitUse24HourClock : fCachedUse24HourClock;
673 if (use24HourClock != CLOCK_HOURS_UNSET) {
674 // adjust to 12/24-hour clock as requested
675 bool localeUses24HourClock = !FormatUsesAmPm(outFormat);
676 if (localeUses24HourClock) {
677 if (use24HourClock == CLOCK_HOURS_12)
678 CoerceFormatTo12HourClock(outFormat);
679 } else {
680 if (use24HourClock == CLOCK_HOURS_24)
681 CoerceFormatTo24HourClock(outFormat);
682 }
683 }
684 }
685
686