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