xref: /haiku/src/system/libroot/add-ons/icu/ICUTimeConversion.cpp (revision 9a6a20d4689307142a7ed26a1437ba47e244e73f)
1 /*
2  * Copyright 2010-2011, Oliver Tappe, zooey@hirschkaefer.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "ICUTimeConversion.h"
8 
9 #include <math.h>
10 #include <string.h>
11 #include <strings.h>
12 
13 #include <unicode/gregocal.h>
14 
15 
16 U_NAMESPACE_USE
17 
18 
19 namespace BPrivate {
20 namespace Libroot {
21 
22 
23 ICUTimeConversion::ICUTimeConversion(const ICUTimeData& timeData)
24 	:
25 	fTimeData(timeData),
26 	fDataBridge(NULL),
27 	fTimeZone(NULL)
28 {
29 	fTimeZoneID[0] = '\0';
30 }
31 
32 
33 ICUTimeConversion::~ICUTimeConversion()
34 {
35 	delete fTimeZone;
36 }
37 
38 
39 void
40 ICUTimeConversion::Initialize(TimeConversionDataBridge* dataBridge)
41 {
42 	fDataBridge = dataBridge;
43 }
44 
45 
46 status_t
47 ICUTimeConversion::TZSet(const char* timeZoneID, const char* tz)
48 {
49 	bool offsetHasBeenSet = false;
50 	bool timeZoneIdMatches = false;
51 
52 	// The given TZ environment variable's content overrides the default
53 	// system timezone.
54 	if (tz != NULL) {
55 		// If the value given in the TZ env-var starts with a colon, that
56 		// value is implementation specific, we expect a full timezone ID.
57 		if (*tz == ':') {
58 			// nothing to do if the given name matches the current timezone
59 			if (strcasecmp(fTimeZoneID, tz + 1) == 0) {
60 				timeZoneIdMatches = true;
61 				goto done;
62 			}
63 
64 			strlcpy(fTimeZoneID, tz + 1, sizeof(fTimeZoneID));
65 		} else {
66 			// note timezone name
67 			strlcpy(fTimeZoneID, tz, sizeof(fTimeZoneID));
68 
69 			// nothing to do if the given name matches the current timezone
70 			if (strcasecmp(fTimeZoneID, fDataBridge->addrOfTZName[0]) == 0) {
71 				timeZoneIdMatches = true;
72 				goto done;
73 			}
74 
75 			// parse TZ variable (only <std> and <offset> supported)
76 			const char* tzNameEnd = tz;
77 			while(isalpha(*tzNameEnd))
78 				++tzNameEnd;
79 			if (*tzNameEnd == '-' || *tzNameEnd == '+') {
80 				int hours = 0;
81 				int minutes = 0;
82 				int seconds = 0;
83 				sscanf(tzNameEnd + 1, "%2d:%2d:%2d", &hours, &minutes,
84 					&seconds);
85 				hours = min_c(24, max_c(0, hours));
86 				minutes = min_c(59, max_c(0, minutes));
87 				seconds = min_c(59, max_c(0, seconds));
88 
89 				*fDataBridge->addrOfTimezone = (*tzNameEnd == '-' ? -1 : 1)
90 					* (hours * 3600 + minutes * 60 + seconds);
91 				offsetHasBeenSet = true;
92 			}
93 		}
94 	} else {
95 		// nothing to do if the given name matches the current timezone
96 		if (strcasecmp(fTimeZoneID, timeZoneID) == 0) {
97 			timeZoneIdMatches = true;
98 			goto done;
99 		}
100 
101 		strlcpy(fTimeZoneID, timeZoneID, sizeof(fTimeZoneID));
102 	}
103 
104 done:
105 	// fTimeZone can still be NULL if we don't initialize it
106 	// in the first TZSet, causing problems for future
107 	// Localtime invocations.
108 	if (fTimeZone != NULL && timeZoneIdMatches)
109 		return B_OK;
110 
111 	delete fTimeZone;
112 	fTimeZone = TimeZone::createTimeZone(fTimeZoneID);
113 	if (fTimeZone == NULL)
114 		return B_NO_MEMORY;
115 
116 	UnicodeString temp;
117 	if (fTimeZone->getID(temp) == UCAL_UNKNOWN_ZONE_ID)
118 		goto error;
119 
120 	if (offsetHasBeenSet) {
121 		fTimeZone->setRawOffset(*fDataBridge->addrOfTimezone * -1 * 1000);
122 	} else {
123 		int32_t rawOffset;
124 		int32_t dstOffset;
125 		UDate nowMillis = 1000 * (UDate)time(NULL);
126 		UErrorCode icuStatus = U_ZERO_ERROR;
127 		fTimeZone->getOffset(nowMillis, FALSE, rawOffset, dstOffset, icuStatus);
128 		if (!U_SUCCESS(icuStatus))
129 			goto error;
130 		*fDataBridge->addrOfTimezone = -1 * (rawOffset + dstOffset) / 1000;
131 			// we want seconds, not the ms that ICU gives us
132 	}
133 
134 	*fDataBridge->addrOfDaylight = fTimeZone->useDaylightTime();
135 
136 	for (int i = 0; i < 2; ++i) {
137 		if (tz != NULL && *tz != ':' && i == 0) {
138 			strlcpy(fDataBridge->addrOfTZName[0], fTimeZoneID,
139 				fDataBridge->kTZNameLength);
140 		} else {
141 			UnicodeString icuString;
142 			fTimeZone->getDisplayName(i == 1, TimeZone::SHORT,
143 				fTimeData.ICULocaleForStrings(), icuString);
144 			CheckedArrayByteSink byteSink(fDataBridge->addrOfTZName[i],
145 				fDataBridge->kTZNameLength);
146 			icuString.toUTF8(byteSink);
147 
148 			if (byteSink.Overflowed())
149 				goto error;
150 
151 			// make sure to canonicalize "GMT+00:00" to just "GMT"
152 			if (strcmp(fDataBridge->addrOfTZName[i], "GMT+00:00") == 0)
153 				fDataBridge->addrOfTZName[i][3] = '\0';
154 		}
155 	}
156 
157 	return B_OK;
158 
159 error:
160 	*fDataBridge->addrOfTimezone = 0;
161 	*fDataBridge->addrOfDaylight = false;
162 	strcpy(fDataBridge->addrOfTZName[0], "GMT");
163 	strcpy(fDataBridge->addrOfTZName[1], "GMT");
164 
165 	return B_ERROR;
166 }
167 
168 
169 status_t
170 ICUTimeConversion::Localtime(const time_t* inTime, struct tm* tmOut)
171 {
172 	if (fTimeZone == NULL)
173 		return B_NO_INIT;
174 
175 	tmOut->tm_zone = fTimeZoneID;
176 	return _FillTmValues(fTimeZone, inTime, tmOut);
177 }
178 
179 
180 status_t
181 ICUTimeConversion::Gmtime(const time_t* inTime, struct tm* tmOut)
182 {
183 	const TimeZone* icuTimeZone = TimeZone::getGMT();
184 		// no delete - doesn't belong to us
185 
186 	status_t status = _FillTmValues(icuTimeZone, inTime, tmOut);
187 	if (status == B_OK) {
188 		// tm_zone must be "GMT" for gmtime, not the current timezone
189 		// (even if that happens to be equivalent to GMT).
190 		tmOut->tm_zone = (char*)"GMT";
191 	}
192 	return status;
193 }
194 
195 
196 status_t
197 ICUTimeConversion::Mktime(struct tm* inOutTm, time_t& timeOut)
198 {
199 	return _Mktime(fTimeZone, inOutTm, timeOut);
200 }
201 
202 
203 status_t
204 ICUTimeConversion::Timegm(struct tm* inOutTm, time_t& timeOut)
205 {
206 	const TimeZone* icuTimeZone = TimeZone::getGMT();
207 	return _Mktime(icuTimeZone, inOutTm, timeOut);
208 }
209 
210 
211 status_t
212 ICUTimeConversion::_FillTmValues(const TimeZone* icuTimeZone,
213 	const time_t* inTime, struct tm* tmOut)
214 {
215 	UErrorCode icuStatus = U_ZERO_ERROR;
216 	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(), icuStatus);
217 	if (!U_SUCCESS(icuStatus))
218 		return B_ERROR;
219 
220 	calendar.setTime(1000 * (UDate)*inTime, icuStatus);
221 	if (!U_SUCCESS(icuStatus))
222 		return B_ERROR;
223 
224 	tmOut->tm_sec = calendar.get(UCAL_SECOND, icuStatus);
225 	if (!U_SUCCESS(icuStatus))
226 		return B_ERROR;
227 	tmOut->tm_min = calendar.get(UCAL_MINUTE, icuStatus);
228 	if (!U_SUCCESS(icuStatus))
229 		return B_ERROR;
230 	tmOut->tm_hour = calendar.get(UCAL_HOUR_OF_DAY, icuStatus);
231 	if (!U_SUCCESS(icuStatus))
232 		return B_ERROR;
233 	tmOut->tm_mday = calendar.get(UCAL_DAY_OF_MONTH, icuStatus);
234 	if (!U_SUCCESS(icuStatus))
235 		return B_ERROR;
236 	tmOut->tm_mon = calendar.get(UCAL_MONTH, icuStatus);
237 	if (!U_SUCCESS(icuStatus))
238 		return B_ERROR;
239 	tmOut->tm_year = calendar.get(UCAL_YEAR, icuStatus) - 1900;
240 	if (!U_SUCCESS(icuStatus))
241 		return B_ERROR;
242 	tmOut->tm_wday = calendar.get(UCAL_DAY_OF_WEEK, icuStatus) - 1;
243 	if (!U_SUCCESS(icuStatus))
244 		return B_ERROR;
245 	tmOut->tm_yday = calendar.get(UCAL_DAY_OF_YEAR, icuStatus) - 1;
246 	if (!U_SUCCESS(icuStatus))
247 		return B_ERROR;
248 	tmOut->tm_isdst = calendar.inDaylightTime(icuStatus);
249 	if (!U_SUCCESS(icuStatus))
250 		return B_ERROR;
251 	tmOut->tm_gmtoff = (calendar.get(UCAL_ZONE_OFFSET, icuStatus)
252 		+ calendar.get(UCAL_DST_OFFSET, icuStatus)) / 1000;
253 	if (!U_SUCCESS(icuStatus))
254 		return B_ERROR;
255 
256 	if (tmOut->tm_gmtoff == -*fDataBridge->addrOfTimezone) {
257 		tmOut->tm_zone = fDataBridge->addrOfTZName[tmOut->tm_isdst ? 1 : 0];
258 	} else if (tmOut->tm_gmtoff == 0) {
259 		tmOut->tm_zone = (char*)"GMT";
260 	} else {
261 		return B_ERROR;
262 	}
263 
264 	return B_OK;
265 }
266 
267 
268 status_t
269 ICUTimeConversion::_Mktime(const TimeZone* icuTimeZone,
270 	struct tm* inOutTm, time_t& timeOut)
271 {
272 	if (icuTimeZone == NULL)
273 		return B_NO_INIT;
274 
275 	UErrorCode icuStatus = U_ZERO_ERROR;
276 	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(),
277 		icuStatus);
278 	if (!U_SUCCESS(icuStatus))
279 		return B_ERROR;
280 
281 	calendar.setLenient(TRUE);
282 	calendar.set(inOutTm->tm_year + 1900, inOutTm->tm_mon, inOutTm->tm_mday,
283 		inOutTm->tm_hour, inOutTm->tm_min, inOutTm->tm_sec);
284 
285 	UDate timeInMillis = calendar.getTime(icuStatus);
286 	if (!U_SUCCESS(icuStatus))
287 		return B_ERROR;
288 	timeOut = (time_t)((int64_t)timeInMillis / 1000);
289 
290 	return _FillTmValues(icuTimeZone, &timeOut, inOutTm);
291 }
292 
293 
294 }	// namespace Libroot
295 }	// namespace BPrivate
296