xref: /haiku/src/system/libroot/add-ons/icu/ICUTimeData.cpp (revision ba0223da5d79c5cd27496ee0e5712921cebb7642)
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 
22 U_NAMESPACE_USE
23 
24 
25 namespace BPrivate {
26 namespace Libroot {
27 
28 
29 ICUTimeData::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 
57 ICUTimeData::~ICUTimeData()
58 {
59 }
60 
61 
62 void
63 ICUTimeData::Initialize(LocaleTimeDataBridge* dataBridge)
64 {
65 	fDataBridge = dataBridge;
66 }
67 
68 
69 status_t
70 ICUTimeData::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::kDefault, DateFormat::kDefault, 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 
168 status_t
169 ICUTimeData::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 
197 const char*
198 ICUTimeData::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 
266 const Locale&
267 ICUTimeData::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 
282 status_t
283 ICUTimeData::_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 
302 status_t
303 ICUTimeData::_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