xref: /haiku/src/kits/locale/FormattingConventions.cpp (revision d4e4909c6a3fe4290b78be2b78035c4774e3ff18)
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
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
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
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**)&currentPos)) != 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
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 
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 
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 
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&
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 
300 BFormattingConventions::~BFormattingConventions()
301 {
302 	delete fICULocale;
303 }
304 
305 
306 bool
307 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
331 BFormattingConventions::operator!=(const BFormattingConventions& other) const
332 {
333 	return !(*this == other);
334 }
335 
336 
337 const char*
338 BFormattingConventions::ID() const
339 {
340 	return fICULocale->getName();
341 }
342 
343 
344 const char*
345 BFormattingConventions::LanguageCode() const
346 {
347 	return fICULocale->getLanguage();
348 }
349 
350 
351 const char*
352 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
363 BFormattingConventions::AreCountrySpecific() const
364 {
365 	return CountryCode() != NULL;
366 }
367 
368 
369 status_t
370 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
385 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
408 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
422 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
455 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
495 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
539 BFormattingConventions::GetNumericFormat(BString& outFormat) const
540 {
541 	// TODO!
542 	return B_UNSUPPORTED;
543 }
544 
545 
546 status_t
547 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const
548 {
549 	// TODO!
550 	return B_UNSUPPORTED;
551 }
552 
553 
554 void
555 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style,
556 	const BString& format)
557 {
558 	fExplicitDateFormats[style] = format;
559 }
560 
561 
562 void
563 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style,
564 	const BString& format)
565 {
566 	fExplicitTimeFormats[style] = format;
567 }
568 
569 
570 void
571 BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle,
572 	BTimeFormatStyle timeStyle, const BString& format)
573 {
574 	fExplicitDateTimeFormats[dateStyle][timeStyle] = format;
575 }
576 
577 
578 void
579 BFormattingConventions::SetExplicitNumericFormat(const BString& format)
580 {
581 	fExplicitNumericFormat = format;
582 }
583 
584 
585 void
586 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format)
587 {
588 	fExplicitMonetaryFormat = format;
589 }
590 
591 
592 bool
593 BFormattingConventions::UseStringsFromPreferredLanguage() const
594 {
595 	return fUseStringsFromPreferredLanguage;
596 }
597 
598 
599 void
600 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value)
601 {
602 	fUseStringsFromPreferredLanguage = value;
603 }
604 
605 
606 bool
607 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
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
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
649 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
669 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