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