xref: /haiku/src/kits/locale/FormattingConventions.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
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 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
211 		fCachedTimeFormats[s] = other.fCachedTimeFormats[s];
212 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s)
213 		fExplicitDateFormats[s] = other.fExplicitDateFormats[s];
214 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
215 		fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s];
216 }
217 
218 
219 BFormattingConventions::BFormattingConventions(const BMessage* archive)
220 	:
221 	fCachedUse24HourClock(CLOCK_HOURS_UNSET),
222 	fExplicitUse24HourClock(CLOCK_HOURS_UNSET),
223 	fUseStringsFromPreferredLanguage(false)
224 {
225 	BString conventionsID;
226 	status_t status = archive->FindString("conventions", &conventionsID);
227 	fICULocale = new icu::Locale(conventionsID);
228 
229 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) {
230 		BString format;
231 		status = archive->FindString("dateFormat", s, &format);
232 		if (status == B_OK)
233 			fExplicitDateFormats[s] = format;
234 
235 		status = archive->FindString("timeFormat", s, &format);
236 		if (status == B_OK)
237 			fExplicitTimeFormats[s] = format;
238 	}
239 
240 	if (status == B_OK) {
241 		int8 use24HourClock;
242 		status = archive->FindInt8("use24HourClock", &use24HourClock);
243 		if (status == B_OK)
244 			fExplicitUse24HourClock = use24HourClock;
245 	}
246 	if (status == B_OK) {
247 		bool useStringsFromPreferredLanguage;
248 		status = archive->FindBool("useStringsFromPreferredLanguage",
249 			&useStringsFromPreferredLanguage);
250 		if (status == B_OK)
251 			fUseStringsFromPreferredLanguage = useStringsFromPreferredLanguage;
252 	}
253 }
254 
255 
256 BFormattingConventions&
257 BFormattingConventions::operator=(const BFormattingConventions& other)
258 {
259 	if (this == &other)
260 		return *this;
261 
262 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s)
263 		fCachedDateFormats[s] = other.fCachedDateFormats[s];
264 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
265 		fCachedTimeFormats[s] = other.fCachedTimeFormats[s];
266 	fCachedNumericFormat = other.fCachedNumericFormat;
267 	fCachedMonetaryFormat = other.fCachedMonetaryFormat;
268 	fCachedUse24HourClock = other.fCachedUse24HourClock;
269 
270 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s)
271 		fExplicitDateFormats[s] = other.fExplicitDateFormats[s];
272 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
273 		fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s];
274 	fExplicitNumericFormat = other.fExplicitNumericFormat;
275 	fExplicitMonetaryFormat = other.fExplicitMonetaryFormat;
276 	fExplicitUse24HourClock = other.fExplicitUse24HourClock;
277 
278 	fUseStringsFromPreferredLanguage = other.fUseStringsFromPreferredLanguage;
279 
280 	*fICULocale = *other.fICULocale;
281 
282 	return *this;
283 }
284 
285 
286 BFormattingConventions::~BFormattingConventions()
287 {
288 	delete fICULocale;
289 }
290 
291 
292 bool
293 BFormattingConventions::operator==(const BFormattingConventions& other) const
294 {
295 	if (this == &other)
296 		return true;
297 
298 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) {
299 		if (fExplicitDateFormats[s] != other.fExplicitDateFormats[s])
300 			return false;
301 	}
302 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) {
303 		if (fExplicitTimeFormats[s] != other.fExplicitTimeFormats[s])
304 			return false;
305 	}
306 
307 	return fExplicitNumericFormat == other.fExplicitNumericFormat
308 		&& fExplicitMonetaryFormat == other.fExplicitMonetaryFormat
309 		&& fExplicitUse24HourClock == other.fExplicitUse24HourClock
310 		&& fUseStringsFromPreferredLanguage
311 			== other.fUseStringsFromPreferredLanguage
312 		&& *fICULocale == *other.fICULocale;
313 }
314 
315 
316 bool
317 BFormattingConventions::operator!=(const BFormattingConventions& other) const
318 {
319 	return !(*this == other);
320 }
321 
322 
323 const char*
324 BFormattingConventions::ID() const
325 {
326 	return fICULocale->getName();
327 }
328 
329 
330 const char*
331 BFormattingConventions::LanguageCode() const
332 {
333 	return fICULocale->getLanguage();
334 }
335 
336 
337 const char*
338 BFormattingConventions::CountryCode() const
339 {
340 	const char* country = fICULocale->getCountry();
341 	if (country == NULL || country[0] == '\0')
342 		return NULL;
343 
344 	return country;
345 }
346 
347 
348 bool
349 BFormattingConventions::AreCountrySpecific() const
350 {
351 	return CountryCode() != NULL;
352 }
353 
354 
355 status_t
356 BFormattingConventions::GetNativeName(BString& name) const
357 {
358 	UnicodeString string;
359 	fICULocale->getDisplayName(*fICULocale, string);
360 	string.toTitle(NULL, *fICULocale);
361 
362 	name.Truncate(0);
363 	BStringByteSink converter(&name);
364 	string.toUTF8(converter);
365 
366 	return B_OK;
367 }
368 
369 
370 status_t
371 BFormattingConventions::GetName(BString& name,
372 	const BLanguage* displayLanguage) const
373 {
374 	BString displayLanguageID;
375 	if (displayLanguage == NULL) {
376 		BLanguage defaultLanguage;
377 		BLocale::Default()->GetLanguage(&defaultLanguage);
378 		displayLanguageID = defaultLanguage.Code();
379 	} else {
380 		displayLanguageID = displayLanguage->Code();
381 	}
382 
383 	UnicodeString uString;
384 	fICULocale->getDisplayName(Locale(displayLanguageID.String()), uString);
385 	name.Truncate(0);
386 	BStringByteSink stringConverter(&name);
387 	uString.toUTF8(stringConverter);
388 
389 	return B_OK;
390 }
391 
392 
393 BMeasurementKind
394 BFormattingConventions::MeasurementKind() const
395 {
396 	UErrorCode error = U_ZERO_ERROR;
397 	switch (ulocdata_getMeasurementSystem(ID(), &error)) {
398 		case UMS_US:
399 			return B_US;
400 		case UMS_SI:
401 		default:
402 			return B_METRIC;
403 	}
404 }
405 
406 
407 status_t
408 BFormattingConventions::GetDateFormat(BDateFormatStyle style,
409 	BString& outFormat) const
410 {
411 	if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT)
412 		return B_BAD_VALUE;
413 
414 	outFormat = fExplicitDateFormats[style].Length()
415 		? fExplicitDateFormats[style]
416 		: fCachedDateFormats[style];
417 
418 	if (outFormat.Length() > 0)
419 		return B_OK;
420 
421 	ObjectDeleter<DateFormat> dateFormatter(
422 		DateFormat::createDateInstance((DateFormat::EStyle)style, *fICULocale));
423 	if (dateFormatter.Get() == NULL)
424 		return B_NO_MEMORY;
425 
426 	SimpleDateFormat* dateFormatterImpl
427 		= static_cast<SimpleDateFormat*>(dateFormatter.Get());
428 
429 	UnicodeString icuString;
430 	dateFormatterImpl->toPattern(icuString);
431 	BStringByteSink stringConverter(&outFormat);
432 	icuString.toUTF8(stringConverter);
433 
434 	fCachedDateFormats[style] = outFormat;
435 
436 	return B_OK;
437 }
438 
439 
440 status_t
441 BFormattingConventions::GetTimeFormat(BTimeFormatStyle style,
442 	BString& outFormat) const
443 {
444 	if (style < 0 || style >= B_TIME_FORMAT_STYLE_COUNT)
445 		return B_BAD_VALUE;
446 
447 	outFormat = fExplicitTimeFormats[style].Length()
448 		? fExplicitTimeFormats[style]
449 		: fCachedTimeFormats[style];
450 
451 	if (outFormat.Length() > 0)
452 		return B_OK;
453 
454 	ObjectDeleter<DateFormat> timeFormatter(
455 		DateFormat::createTimeInstance((DateFormat::EStyle)style, *fICULocale));
456 	if (timeFormatter.Get() == NULL)
457 		return B_NO_MEMORY;
458 
459 	SimpleDateFormat* timeFormatterImpl
460 		= static_cast<SimpleDateFormat*>(timeFormatter.Get());
461 
462 	UnicodeString icuString;
463 	timeFormatterImpl->toPattern(icuString);
464 	BStringByteSink stringConverter(&outFormat);
465 	icuString.toUTF8(stringConverter);
466 
467 	int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
468 		? fExplicitUse24HourClock : fCachedUse24HourClock;
469 	if (use24HourClock != CLOCK_HOURS_UNSET) {
470 		// adjust to 12/24-hour clock as requested
471 		bool localeUses24HourClock = !FormatUsesAmPm(outFormat);
472 		if (localeUses24HourClock) {
473 			if (use24HourClock == CLOCK_HOURS_12)
474 				CoerceFormatTo12HourClock(outFormat);
475 		} else {
476 			if (use24HourClock == CLOCK_HOURS_24)
477 				CoerceFormatTo24HourClock(outFormat);
478 		}
479 	}
480 
481 	if (style != B_FULL_TIME_FORMAT) {
482 		// use abbreviated timezone in short timezone format
483 		CoerceFormatToAbbreviatedTimezone(outFormat);
484 	}
485 
486 	fCachedTimeFormats[style] = outFormat;
487 
488 	return B_OK;
489 }
490 
491 
492 status_t
493 BFormattingConventions::GetNumericFormat(BString& outFormat) const
494 {
495 	// TODO!
496 	return B_UNSUPPORTED;
497 }
498 
499 
500 status_t
501 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const
502 {
503 	// TODO!
504 	return B_UNSUPPORTED;
505 }
506 
507 
508 void
509 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style,
510 	const BString& format)
511 {
512 	fExplicitDateFormats[style] = format;
513 }
514 
515 
516 void
517 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style,
518 	const BString& format)
519 {
520 	fExplicitTimeFormats[style] = format;
521 }
522 
523 
524 void
525 BFormattingConventions::SetExplicitNumericFormat(const BString& format)
526 {
527 	fExplicitNumericFormat = format;
528 }
529 
530 
531 void
532 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format)
533 {
534 	fExplicitMonetaryFormat = format;
535 }
536 
537 
538 bool
539 BFormattingConventions::UseStringsFromPreferredLanguage() const
540 {
541 	return fUseStringsFromPreferredLanguage;
542 }
543 
544 
545 void
546 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value)
547 {
548 	fUseStringsFromPreferredLanguage = value;
549 }
550 
551 
552 bool
553 BFormattingConventions::Use24HourClock() const
554 {
555 	int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET
556 		?  fExplicitUse24HourClock : fCachedUse24HourClock;
557 
558 	if (use24HourClock == CLOCK_HOURS_UNSET) {
559 		BString format;
560 		GetTimeFormat(B_MEDIUM_TIME_FORMAT, format);
561 		fCachedUse24HourClock
562 			= FormatUsesAmPm(format) ? CLOCK_HOURS_12 : CLOCK_HOURS_24;
563 		return fCachedUse24HourClock == CLOCK_HOURS_24;
564 	}
565 
566 	return fExplicitUse24HourClock == CLOCK_HOURS_24;
567 }
568 
569 
570 void
571 BFormattingConventions::SetExplicitUse24HourClock(bool value)
572 {
573 	int8 newUse24HourClock = value ? CLOCK_HOURS_24 : CLOCK_HOURS_12;
574 	if (fExplicitUse24HourClock == newUse24HourClock)
575 		return;
576 
577 	fExplicitUse24HourClock = newUse24HourClock;
578 
579 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
580 		fCachedTimeFormats[s].Truncate(0);
581 }
582 
583 
584 void
585 BFormattingConventions::UnsetExplicitUse24HourClock()
586 {
587 	fExplicitUse24HourClock = CLOCK_HOURS_UNSET;
588 
589 	for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s)
590 		fCachedTimeFormats[s].Truncate(0);
591 }
592 
593 
594 status_t
595 BFormattingConventions::Archive(BMessage* archive, bool deep) const
596 {
597 	status_t status = archive->AddString("conventions", fICULocale->getName());
598 	for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) {
599 		status = archive->AddString("dateFormat", fExplicitDateFormats[s]);
600 		if (status == B_OK)
601 			status = archive->AddString("timeFormat", fExplicitTimeFormats[s]);
602 	}
603 	if (status == B_OK)
604 		status = archive->AddInt8("use24HourClock", fExplicitUse24HourClock);
605 	if (status == B_OK) {
606 		status = archive->AddBool("useStringsFromPreferredLanguage",
607 			fUseStringsFromPreferredLanguage);
608 	}
609 
610 	return status;
611 }
612