xref: /haiku/src/system/libroot/add-ons/icu/ICUTimeData.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
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 
20 namespace BPrivate {
21 namespace Libroot {
22 
23 
24 ICUTimeData::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 
52 ICUTimeData::~ICUTimeData()
53 {
54 }
55 
56 
57 void
58 ICUTimeData::Initialize(LocaleTimeDataBridge* dataBridge)
59 {
60 	fDataBridge = dataBridge;
61 }
62 
63 
64 status_t
65 ICUTimeData::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 
163 status_t
164 ICUTimeData::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 
192 const char*
193 ICUTimeData::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 
261 const Locale&
262 ICUTimeData::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 
277 status_t
278 ICUTimeData::_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 
297 status_t
298 ICUTimeData::_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