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