1/*
2 * Copyright 2010-2011, Oliver Tappe, zooey@hirschkaefer.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "ICUTimeData.h"
8
9#include <langinfo.h>
10#include <strings.h>
11
12#include <unicode/dtfmtsym.h>
13#include <unicode/gregocal.h>
14#include <unicode/smpdtfmt.h>
15
16#include <AutoDeleter.h>
17
18#include "ICUMessagesData.h"
19
20namespace BPrivate {
21namespace Libroot {
22
23
24ICUTimeData::ICUTimeData(pthread_key_t tlsKey, struct lc_time_t& lcTimeInfo,
25	const ICUMessagesData& messagesData)
26	:
27	inherited(tlsKey),
28	fLCTimeInfo(lcTimeInfo),
29	fDataBridge(NULL),
30	fMessagesData(messagesData)
31{
32	for (int i = 0; i < 12; ++i) {
33		fLCTimeInfo.mon[i] = fMon[i];
34		fLCTimeInfo.month[i] = fMonth[i];
35		fLCTimeInfo.alt_month[i] = fAltMonth[i];
36	}
37	for (int i = 0; i < 7; ++i) {
38		fLCTimeInfo.wday[i] = fWday[i];
39		fLCTimeInfo.weekday[i] = fWeekday[i];
40	}
41	fLCTimeInfo.X_fmt = fTimeFormat;
42	fLCTimeInfo.x_fmt = fDateFormat;
43	fLCTimeInfo.c_fmt = fDateTimeFormat;
44	fLCTimeInfo.am = fAm;
45	fLCTimeInfo.pm = fPm;
46	fLCTimeInfo.date_fmt = fDateTimeZoneFormat;
47	fLCTimeInfo.md_order = fMonthDayOrder;
48	fLCTimeInfo.ampm_fmt = fAmPmFormat;
49}
50
51
52ICUTimeData::~ICUTimeData()
53{
54}
55
56
57void
58ICUTimeData::Initialize(LocaleTimeDataBridge* dataBridge)
59{
60	fDataBridge = dataBridge;
61}
62
63
64status_t
65ICUTimeData::SetTo(const Locale& locale, const char* posixLocaleName)
66{
67	status_t result = inherited::SetTo(locale, posixLocaleName);
68	if (result != B_OK)
69		return result;
70
71	UErrorCode icuStatus = U_ZERO_ERROR;
72	DateFormatSymbols formatSymbols(ICULocaleForStrings(), icuStatus);
73	if (!U_SUCCESS(icuStatus))
74		return B_UNSUPPORTED;
75
76	int count = 0;
77	const UnicodeString* strings = formatSymbols.getShortMonths(count);
78	result = _SetLCTimeEntries(strings, fMon[0], sizeof(fMon[0]), count, 12);
79
80	if (result == B_OK) {
81		strings = formatSymbols.getMonths(count);
82		result = _SetLCTimeEntries(strings, fMonth[0], sizeof(fMonth[0]), count,
83			12);
84	}
85
86	if (result == B_OK) {
87		strings = formatSymbols.getShortWeekdays(count);
88		if (count == 8 && strings[0].length() == 0) {
89			// ICUs weekday arrays are 1-based
90			strings++;
91			count = 7;
92		}
93		result
94			= _SetLCTimeEntries(strings, fWday[0], sizeof(fWday[0]), count, 7);
95	}
96
97	if (result == B_OK) {
98		strings = formatSymbols.getWeekdays(count);
99		if (count == 8 && strings[0].length() == 0) {
100			// ICUs weekday arrays are 1-based
101			strings++;
102			count = 7;
103		}
104		result = _SetLCTimeEntries(strings, fWeekday[0], sizeof(fWeekday[0]),
105			count, 7);
106	}
107
108	if (result == B_OK) {
109		try {
110			DateFormat* format = DateFormat::createTimeInstance(
111				DateFormat::kDefault, fLocale);
112			result = _SetLCTimePattern(format, fTimeFormat, sizeof(fTimeFormat));
113			delete format;
114		} catch(...) {
115			result = B_NO_MEMORY;
116		}
117	}
118
119	if (result == B_OK) {
120		try {
121			DateFormat* format = DateFormat::createDateInstance(
122				DateFormat::kDefault, fLocale);
123			result = _SetLCTimePattern(format, fDateFormat, sizeof(fDateFormat));
124			delete format;
125		} catch(...) {
126			result = B_NO_MEMORY;
127		}
128	}
129
130	if (result == B_OK) {
131		try {
132			DateFormat* format = DateFormat::createDateTimeInstance(
133				DateFormat::kFull, DateFormat::kFull, fLocale);
134			result = _SetLCTimePattern(format, fDateTimeFormat,
135				sizeof(fDateTimeFormat));
136			delete format;
137		} catch(...) {
138			result = B_NO_MEMORY;
139		}
140	}
141
142	if (result == B_OK) {
143		strings = formatSymbols.getAmPmStrings(count);
144		result = _SetLCTimeEntries(strings, fAm, sizeof(fAm), 1, 1);
145		if (result == B_OK)
146			result = _SetLCTimeEntries(&strings[1], fPm, sizeof(fPm), 1, 1);
147	}
148
149	if (result == B_OK) {
150		strings = formatSymbols.getMonths(count, DateFormatSymbols::STANDALONE,
151			DateFormatSymbols::WIDE);
152		result = _SetLCTimeEntries(strings, fAltMonth[0], sizeof(fAltMonth[0]),
153			count, 12);
154	}
155
156	strcpy(fAmPmFormat, fDataBridge->posixLCTimeInfo->ampm_fmt);
157		// ICU does not provide anything for this (and that makes sense, too)
158
159	return result;
160}
161
162
163status_t
164ICUTimeData::SetToPosix()
165{
166	status_t result = inherited::SetToPosix();
167
168	if (result == B_OK) {
169		for (int i = 0; i < 12; ++i) {
170			strcpy(fMon[i], fDataBridge->posixLCTimeInfo->mon[i]);
171			strcpy(fMonth[i], fDataBridge->posixLCTimeInfo->month[i]);
172			strcpy(fAltMonth[i], fDataBridge->posixLCTimeInfo->alt_month[i]);
173		}
174		for (int i = 0; i < 7; ++i) {
175			strcpy(fWday[i], fDataBridge->posixLCTimeInfo->wday[i]);
176			strcpy(fWeekday[i], fDataBridge->posixLCTimeInfo->weekday[i]);
177		}
178		strcpy(fTimeFormat, fDataBridge->posixLCTimeInfo->X_fmt);
179		strcpy(fDateFormat, fDataBridge->posixLCTimeInfo->x_fmt);
180		strcpy(fDateTimeFormat, fDataBridge->posixLCTimeInfo->c_fmt);
181		strcpy(fAm, fDataBridge->posixLCTimeInfo->am);
182		strcpy(fPm, fDataBridge->posixLCTimeInfo->pm);
183		strcpy(fDateTimeZoneFormat, fDataBridge->posixLCTimeInfo->date_fmt);
184		strcpy(fMonthDayOrder, fDataBridge->posixLCTimeInfo->md_order);
185		strcpy(fAmPmFormat, fDataBridge->posixLCTimeInfo->ampm_fmt);
186	}
187
188	return result;
189}
190
191
192const char*
193ICUTimeData::GetLanginfo(int index)
194{
195	switch(index) {
196		case D_T_FMT:
197			return fDateTimeFormat;
198		case D_FMT:
199			return fDateFormat;
200		case T_FMT:
201			return fTimeFormat;
202		case T_FMT_AMPM:
203			return fAmPmFormat;
204		case AM_STR:
205			return fAm;
206		case PM_STR:
207			return fPm;
208
209		case DAY_1:
210		case DAY_2:
211		case DAY_3:
212		case DAY_4:
213		case DAY_5:
214		case DAY_6:
215		case DAY_7:
216			return fWeekday[index - DAY_1];
217
218		case ABDAY_1:
219		case ABDAY_2:
220		case ABDAY_3:
221		case ABDAY_4:
222		case ABDAY_5:
223		case ABDAY_6:
224		case ABDAY_7:
225			return fWday[index - ABDAY_1];
226
227		case MON_1:
228		case MON_2:
229		case MON_3:
230		case MON_4:
231		case MON_5:
232		case MON_6:
233		case MON_7:
234		case MON_8:
235		case MON_9:
236		case MON_10:
237		case MON_11:
238		case MON_12:
239			return fMonth[index - MON_1];
240
241		case ABMON_1:
242		case ABMON_2:
243		case ABMON_3:
244		case ABMON_4:
245		case ABMON_5:
246		case ABMON_6:
247		case ABMON_7:
248		case ABMON_8:
249		case ABMON_9:
250		case ABMON_10:
251		case ABMON_11:
252		case ABMON_12:
253			return fMon[index - ABMON_1];
254
255		default:
256			return "";
257	}
258}
259
260
261const Locale&
262ICUTimeData::ICULocaleForStrings() const
263{
264	// check if the date strings should be taken from the messages-locale
265	// or from the time-locale (default)
266	UErrorCode icuStatus = U_ZERO_ERROR;
267	char stringsValue[16];
268	fLocale.getKeywordValue("strings", stringsValue, sizeof(stringsValue),
269		icuStatus);
270	if (U_SUCCESS(icuStatus) && strcasecmp(stringsValue, "messages") == 0)
271		return fMessagesData.ICULocale();
272	else
273		return fLocale;
274}
275
276
277status_t
278ICUTimeData::_SetLCTimeEntries(const UnicodeString* strings, char* destination,
279	int entrySize, int count, int maxCount)
280{
281	if (strings == NULL)
282		return B_ERROR;
283
284	status_t result = B_OK;
285	if (count > maxCount)
286		count = maxCount;
287	for (int32 i = 0; result == B_OK && i < count; ++i) {
288		result = _ConvertUnicodeStringToLocaleconvEntry(strings[i], destination,
289			entrySize);
290		destination += entrySize;
291	}
292
293	return result;
294}
295
296
297status_t
298ICUTimeData::_SetLCTimePattern(DateFormat* format, char* destination,
299	int destinationSize)
300{
301	SimpleDateFormat* simpleFormat = dynamic_cast<SimpleDateFormat*>(format);
302	if (!simpleFormat)
303		return B_BAD_TYPE;
304
305	// convert ICU-type pattern to posix (i.e. strftime()) format string
306	UnicodeString icuPattern;
307	simpleFormat->toPattern(icuPattern);
308	UnicodeString posixPattern;
309	if (icuPattern.length() > 0) {
310		UChar lastCharSeen = 0;
311		int lastCharCount = 1;
312		bool inSingleQuotes = false;
313		bool inDoubleQuotes = false;
314		// we loop one character past the end on purpose, which will result in a
315		// final -1 char to be processed, which in turn will let us handle the
316		// last character (via lastCharSeen)
317		for (int i = 0; i <= icuPattern.length(); ++i) {
318			UChar currChar = icuPattern.charAt(i);
319			if (lastCharSeen != 0 && currChar == lastCharSeen) {
320				lastCharCount++;
321				continue;
322			}
323
324			if (!inSingleQuotes && !inDoubleQuotes) {
325				switch (lastCharSeen) {
326					case L'a':
327						posixPattern.append(UnicodeString("%p", ""));
328						break;
329					case L'd':
330						if (lastCharCount == 2)
331							posixPattern.append(UnicodeString("%d", ""));
332						else
333							posixPattern.append(UnicodeString("%e", ""));
334						break;
335					case L'D':
336						posixPattern.append(UnicodeString("%j", ""));
337						break;
338					case L'c':
339						// fall through, to handle 'c' the same as 'e'
340					case L'e':
341						if (lastCharCount == 4)
342							posixPattern.append(UnicodeString("%A", ""));
343						else if (lastCharCount <= 2)
344							posixPattern.append(UnicodeString("%u", ""));
345						else
346							posixPattern.append(UnicodeString("%a", ""));
347						break;
348					case L'E':
349						if (lastCharCount == 4)
350							posixPattern.append(UnicodeString("%A", ""));
351						else
352							posixPattern.append(UnicodeString("%a", ""));
353						break;
354					case L'k':
355						// fall through, to handle 'k' the same as 'h'
356					case L'h':
357						if (lastCharCount == 2)
358							posixPattern.append(UnicodeString("%I", ""));
359						else
360							posixPattern.append(UnicodeString("%l", ""));
361						break;
362					case L'H':
363						if (lastCharCount == 2)
364							posixPattern.append(UnicodeString("%H", ""));
365						else
366							posixPattern.append(UnicodeString("%k", ""));
367						break;
368					case L'm':
369						posixPattern.append(UnicodeString("%M", ""));
370						break;
371					case L'L':
372						// fall through, to handle 'L' the same as 'M'
373					case L'M':
374						if (lastCharCount == 4)
375							posixPattern.append(UnicodeString("%B", ""));
376						else if (lastCharCount == 3)
377							posixPattern.append(UnicodeString("%b", ""));
378						else
379							posixPattern.append(UnicodeString("%m", ""));
380						break;
381					case L's':
382						posixPattern.append(UnicodeString("%S", ""));
383						break;
384					case L'w':
385						posixPattern.append(UnicodeString("%V", ""));
386						break;
387					case L'y':
388						if (lastCharCount == 2)
389							posixPattern.append(UnicodeString("%y", ""));
390						else
391							posixPattern.append(UnicodeString("%Y", ""));
392						break;
393					case L'Y':
394						posixPattern.append(UnicodeString("%G", ""));
395						break;
396					case L'z':
397						posixPattern.append(UnicodeString("%Z", ""));
398						break;
399					case L'Z':
400						posixPattern.append(UnicodeString("%z", ""));
401						break;
402					default:
403						if (lastCharSeen != 0)
404							posixPattern.append(lastCharSeen);
405				}
406			} else {
407				if (lastCharSeen != 0)
408					posixPattern.append(lastCharSeen);
409			}
410
411			if (currChar == L'"') {
412				inDoubleQuotes = !inDoubleQuotes;
413				lastCharSeen = 0;
414			} else if (currChar == L'\'') {
415				inSingleQuotes = !inSingleQuotes;
416				lastCharSeen = 0;
417			} else
418				lastCharSeen = currChar;
419
420			lastCharCount = 1;
421		}
422	}
423
424	return _ConvertUnicodeStringToLocaleconvEntry(posixPattern, destination,
425		destinationSize);
426}
427
428
429}	// namespace Libroot
430}	// namespace BPrivate
431