xref: /haiku/src/kits/locale/FormattingConventions.cpp (revision d231c2a7a54fd2f3d003a5d2cb8717614fc17626)
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 			fExplicitDateFormats[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 	int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
479 		? fExplicitUse24HourClock : fCachedUse24HourClock;
480 	if (use24HourClock != CLOCK_HOURS_UNSET) {
481 		// adjust to 12/24-hour clock as requested
482 		bool localeUses24HourClock = !FormatUsesAmPm(outFormat);
483 		if (localeUses24HourClock) {
484 			if (use24HourClock == CLOCK_HOURS_12)
485 				CoerceFormatTo12HourClock(outFormat);
486 		} else {
487 			if (use24HourClock == CLOCK_HOURS_24)
488 				CoerceFormatTo24HourClock(outFormat);
489 		}
490 	}
491 
492 	if (style != B_FULL_TIME_FORMAT) {
493 		// use abbreviated timezone in short timezone format
494 		CoerceFormatToAbbreviatedTimezone(outFormat);
495 	}
496 
497 	fCachedTimeFormats[style] = outFormat;
498 
499 	return B_OK;
500 }
501 
502 
503 status_t
504 BFormattingConventions::GetDateTimeFormat(BDateFormatStyle dateStyle,
505 	BTimeFormatStyle timeStyle, BString& outFormat) const
506 {
507 	if (dateStyle < 0 || dateStyle >= B_DATE_FORMAT_STYLE_COUNT)
508 		return B_BAD_VALUE;
509 
510 	if (timeStyle < 0 || timeStyle >= B_TIME_FORMAT_STYLE_COUNT)
511 		return B_BAD_VALUE;
512 
513 	outFormat = fExplicitDateTimeFormats[dateStyle][timeStyle].Length()
514 		? fExplicitDateTimeFormats[dateStyle][timeStyle]
515 		: fCachedDateTimeFormats[dateStyle][timeStyle];
516 
517 	if (outFormat.Length() > 0)
518 		return B_OK;
519 
520 	ObjectDeleter<DateFormat> dateFormatter(
521 		DateFormat::createDateTimeInstance((DateFormat::EStyle)dateStyle,
522 			(DateFormat::EStyle)timeStyle, *fICULocale));
523 	if (dateFormatter.Get() == NULL)
524 		return B_NO_MEMORY;
525 
526 	SimpleDateFormat* dateFormatterImpl
527 		= static_cast<SimpleDateFormat*>(dateFormatter.Get());
528 
529 	UnicodeString icuString;
530 	dateFormatterImpl->toPattern(icuString);
531 	BStringByteSink stringConverter(&outFormat);
532 	icuString.toUTF8(stringConverter);
533 
534 	fCachedDateTimeFormats[dateStyle][timeStyle] = outFormat;
535 
536 	return B_OK;
537 }
538 
539 
540 status_t
541 BFormattingConventions::GetNumericFormat(BString& outFormat) const
542 {
543 	// TODO!
544 	return B_UNSUPPORTED;
545 }
546 
547 
548 status_t
549 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const
550 {
551 	// TODO!
552 	return B_UNSUPPORTED;
553 }
554 
555 
556 void
557 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style,
558 	const BString& format)
559 {
560 	fExplicitDateFormats[style] = format;
561 }
562 
563 
564 void
565 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style,
566 	const BString& format)
567 {
568 	fExplicitTimeFormats[style] = format;
569 }
570 
571 
572 void
573 BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle,
574 	BTimeFormatStyle timeStyle, const BString& format)
575 {
576 	fExplicitDateTimeFormats[dateStyle][timeStyle] = format;
577 }
578 
579 
580 void
581 BFormattingConventions::SetExplicitNumericFormat(const BString& format)
582 {
583 	fExplicitNumericFormat = format;
584 }
585 
586 
587 void
588 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format)
589 {
590 	fExplicitMonetaryFormat = format;
591 }
592 
593 
594 bool
595 BFormattingConventions::UseStringsFromPreferredLanguage() const
596 {
597 	return fUseStringsFromPreferredLanguage;
598 }
599 
600 
601 void
602 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value)
603 {
604 	fUseStringsFromPreferredLanguage = value;
605 }
606 
607 
608 bool
609 BFormattingConventions::Use24HourClock() const
610 {
611 	int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
612 		?  fExplicitUse24HourClock : fCachedUse24HourClock;
613 
614 	if (use24HourClock == CLOCK_HOURS_UNSET) {
615 		BString format;
616 		GetTimeFormat(B_MEDIUM_TIME_FORMAT, format);
617 		fCachedUse24HourClock
618 			= FormatUsesAmPm(format) ? CLOCK_HOURS_12 : CLOCK_HOURS_24;
619 		return fCachedUse24HourClock == CLOCK_HOURS_24;
620 	}
621 
622 	return fExplicitUse24HourClock == CLOCK_HOURS_24;
623 }
624 
625 
626 void
627 BFormattingConventions::SetExplicitUse24HourClock(bool value)
628 {
629 	int8 newUse24HourClock = value ? CLOCK_HOURS_24 : CLOCK_HOURS_12;
630 	if (fExplicitUse24HourClock == newUse24HourClock)
631 		return;
632 
633 	fExplicitUse24HourClock = newUse24HourClock;
634 
635 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
636 		fCachedTimeFormats[s].Truncate(0);
637 }
638 
639 
640 void
641 BFormattingConventions::UnsetExplicitUse24HourClock()
642 {
643 	fExplicitUse24HourClock = CLOCK_HOURS_UNSET;
644 
645 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
646 		fCachedTimeFormats[s].Truncate(0);
647 }
648 
649 
650 status_t
651 BFormattingConventions::Archive(BMessage* archive, bool deep) const
652 {
653 	status_t status = archive->AddString("conventions", fICULocale->getName());
654 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) {
655 		status = archive->AddString("dateFormat", fExplicitDateFormats[s]);
656 		if (status == B_OK)
657 			status = archive->AddString("timeFormat", fExplicitTimeFormats[s]);
658 	}
659 	if (status == B_OK)
660 		status = archive->AddInt8("use24HourClock", fExplicitUse24HourClock);
661 	if (status == B_OK) {
662 		status = archive->AddBool("useStringsFromPreferredLanguage",
663 			fUseStringsFromPreferredLanguage);
664 	}
665 
666 	return status;
667 }
668