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
16U_NAMESPACE_USE
17
18
19namespace BPrivate {
20namespace Libroot {
21
22
23ICUTimeConversion::ICUTimeConversion(const ICUTimeData& timeData)
24	:
25	fTimeData(timeData),
26	fDataBridge(NULL),
27	fTimeZone(NULL)
28{
29	fTimeZoneID[0] = '\0';
30}
31
32
33ICUTimeConversion::~ICUTimeConversion()
34{
35	delete fTimeZone;
36}
37
38
39void
40ICUTimeConversion::Initialize(TimeConversionDataBridge* dataBridge)
41{
42	fDataBridge = dataBridge;
43}
44
45
46status_t
47ICUTimeConversion::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
104done:
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
159error:
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
169status_t
170ICUTimeConversion::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
180status_t
181ICUTimeConversion::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	return _FillTmValues(icuTimeZone, inTime, tmOut);
187}
188
189
190status_t
191ICUTimeConversion::Mktime(struct tm* inOutTm, time_t& timeOut)
192{
193	return _Mktime(fTimeZone, inOutTm, timeOut);
194}
195
196
197status_t
198ICUTimeConversion::Timegm(struct tm* inOutTm, time_t& timeOut)
199{
200	const TimeZone* icuTimeZone = TimeZone::getGMT();
201	return _Mktime(icuTimeZone, inOutTm, timeOut);
202}
203
204
205status_t
206ICUTimeConversion::_FillTmValues(const TimeZone* icuTimeZone,
207	const time_t* inTime, struct tm* tmOut)
208{
209	UErrorCode icuStatus = U_ZERO_ERROR;
210	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(), icuStatus);
211	if (!U_SUCCESS(icuStatus))
212		return B_ERROR;
213
214	calendar.setTime(1000 * (UDate)*inTime, icuStatus);
215	if (!U_SUCCESS(icuStatus))
216		return B_ERROR;
217
218	tmOut->tm_sec = calendar.get(UCAL_SECOND, icuStatus);
219	if (!U_SUCCESS(icuStatus))
220		return B_ERROR;
221	tmOut->tm_min = calendar.get(UCAL_MINUTE, icuStatus);
222	if (!U_SUCCESS(icuStatus))
223		return B_ERROR;
224	tmOut->tm_hour = calendar.get(UCAL_HOUR_OF_DAY, icuStatus);
225	if (!U_SUCCESS(icuStatus))
226		return B_ERROR;
227	tmOut->tm_mday = calendar.get(UCAL_DAY_OF_MONTH, icuStatus);
228	if (!U_SUCCESS(icuStatus))
229		return B_ERROR;
230	tmOut->tm_mon = calendar.get(UCAL_MONTH, icuStatus);
231	if (!U_SUCCESS(icuStatus))
232		return B_ERROR;
233	tmOut->tm_year = calendar.get(UCAL_YEAR, icuStatus) - 1900;
234	if (!U_SUCCESS(icuStatus))
235		return B_ERROR;
236	tmOut->tm_wday = calendar.get(UCAL_DAY_OF_WEEK, icuStatus) - 1;
237	if (!U_SUCCESS(icuStatus))
238		return B_ERROR;
239	tmOut->tm_yday = calendar.get(UCAL_DAY_OF_YEAR, icuStatus) - 1;
240	if (!U_SUCCESS(icuStatus))
241		return B_ERROR;
242	tmOut->tm_isdst = calendar.inDaylightTime(icuStatus);
243	if (!U_SUCCESS(icuStatus))
244		return B_ERROR;
245	tmOut->tm_gmtoff = (calendar.get(UCAL_ZONE_OFFSET, icuStatus)
246		+ calendar.get(UCAL_DST_OFFSET, icuStatus)) / 1000;
247	if (!U_SUCCESS(icuStatus))
248		return B_ERROR;
249	tmOut->tm_zone = fDataBridge->addrOfTZName[tmOut->tm_isdst ? 1 : 0];
250
251	return B_OK;
252}
253
254
255status_t
256ICUTimeConversion::_Mktime(const TimeZone* icuTimeZone,
257	struct tm* inOutTm, time_t& timeOut)
258{
259	if (icuTimeZone == NULL)
260		return B_NO_INIT;
261
262	UErrorCode icuStatus = U_ZERO_ERROR;
263	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(),
264		icuStatus);
265	if (!U_SUCCESS(icuStatus))
266		return B_ERROR;
267
268	calendar.setLenient(TRUE);
269	calendar.set(inOutTm->tm_year + 1900, inOutTm->tm_mon, inOutTm->tm_mday,
270		inOutTm->tm_hour, inOutTm->tm_min, inOutTm->tm_sec);
271
272	UDate timeInMillis = calendar.getTime(icuStatus);
273	if (!U_SUCCESS(icuStatus))
274		return B_ERROR;
275	timeOut = (time_t)((int64_t)timeInMillis / 1000);
276
277	return _FillTmValues(icuTimeZone, &timeOut, inOutTm);
278}
279
280
281}	// namespace Libroot
282}	// namespace BPrivate
283