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
36U_NAMESPACE_USE
37
38
39// #pragma mark - helpers
40
41
42static bool
43FormatUsesAmPm(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
65static void
66CoerceFormatTo12HourClock(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
96static void
97CoerceFormatTo24HourClock(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
153static void
154CoerceFormatToAbbreviatedTimezone(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
183enum ClockHoursState {
184	CLOCK_HOURS_UNSET = 0,
185	CLOCK_HOURS_24,
186	CLOCK_HOURS_12
187};
188
189
190BFormattingConventions::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
200BFormattingConventions::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
228BFormattingConventions::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
265BFormattingConventions&
266BFormattingConventions::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
300BFormattingConventions::~BFormattingConventions()
301{
302	delete fICULocale;
303}
304
305
306bool
307BFormattingConventions::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
330bool
331BFormattingConventions::operator!=(const BFormattingConventions& other) const
332{
333	return !(*this == other);
334}
335
336
337const char*
338BFormattingConventions::ID() const
339{
340	return fICULocale->getName();
341}
342
343
344const char*
345BFormattingConventions::LanguageCode() const
346{
347	return fICULocale->getLanguage();
348}
349
350
351const char*
352BFormattingConventions::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
362bool
363BFormattingConventions::AreCountrySpecific() const
364{
365	return CountryCode() != NULL;
366}
367
368
369status_t
370BFormattingConventions::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
384status_t
385BFormattingConventions::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
407BMeasurementKind
408BFormattingConventions::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
421status_t
422BFormattingConventions::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
454status_t
455BFormattingConventions::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
494status_t
495BFormattingConventions::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
538status_t
539BFormattingConventions::GetNumericFormat(BString& outFormat) const
540{
541	// TODO!
542	return B_UNSUPPORTED;
543}
544
545
546status_t
547BFormattingConventions::GetMonetaryFormat(BString& outFormat) const
548{
549	// TODO!
550	return B_UNSUPPORTED;
551}
552
553
554void
555BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style,
556	const BString& format)
557{
558	fExplicitDateFormats[style] = format;
559}
560
561
562void
563BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style,
564	const BString& format)
565{
566	fExplicitTimeFormats[style] = format;
567}
568
569
570void
571BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle,
572	BTimeFormatStyle timeStyle, const BString& format)
573{
574	fExplicitDateTimeFormats[dateStyle][timeStyle] = format;
575}
576
577
578void
579BFormattingConventions::SetExplicitNumericFormat(const BString& format)
580{
581	fExplicitNumericFormat = format;
582}
583
584
585void
586BFormattingConventions::SetExplicitMonetaryFormat(const BString& format)
587{
588	fExplicitMonetaryFormat = format;
589}
590
591
592bool
593BFormattingConventions::UseStringsFromPreferredLanguage() const
594{
595	return fUseStringsFromPreferredLanguage;
596}
597
598
599void
600BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value)
601{
602	fUseStringsFromPreferredLanguage = value;
603}
604
605
606bool
607BFormattingConventions::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
624void
625BFormattingConventions::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
638void
639BFormattingConventions::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
648status_t
649BFormattingConventions::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
668void
669BFormattingConventions::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