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