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