xref: /haiku/src/system/libroot/add-ons/icu/ICUTimeConversion.cpp (revision ae0a10cad3999b13cbfa47a3d947a5219d2d90f4)
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 
51 	// The given TZ environment variable's content overrides the default
52 	// system timezone.
53 	if (tz != NULL) {
54 		// If the value given in the TZ env-var starts with a colon, that
55 		// value is implementation specific, we expect a full timezone ID.
56 		if (*tz == ':') {
57 			// nothing to do if the given name matches the current timezone
58 			if (strcasecmp(fTimeZoneID, tz + 1) == 0)
59 				return B_OK;
60 
61 			strlcpy(fTimeZoneID, tz + 1, sizeof(fTimeZoneID));
62 		} else {
63 			// note timezone name
64 			strlcpy(fTimeZoneID, tz, sizeof(fTimeZoneID));
65 
66 			// nothing to do if the given name matches the current timezone
67 			if (strcasecmp(fTimeZoneID, fDataBridge->addrOfTZName[0]) == 0)
68 				return B_OK;
69 
70 			// parse TZ variable (only <std> and <offset> supported)
71 			const char* tzNameEnd = tz;
72 			while(isalpha(*tzNameEnd))
73 				++tzNameEnd;
74 			if (*tzNameEnd == '-' || *tzNameEnd == '+') {
75 				int hours = 0;
76 				int minutes = 0;
77 				int seconds = 0;
78 				sscanf(tzNameEnd + 1, "%2d:%2d:%2d", &hours, &minutes,
79 					&seconds);
80 				hours = min_c(24, max_c(0, hours));
81 				minutes = min_c(59, max_c(0, minutes));
82 				seconds = min_c(59, max_c(0, seconds));
83 
84 				*fDataBridge->addrOfTimezone = (*tzNameEnd == '-' ? -1 : 1)
85 					* (hours * 3600 + minutes * 60 + seconds);
86 				offsetHasBeenSet = true;
87 			}
88 		}
89 	} else {
90 		// nothing to do if the given name matches the current timezone
91 		if (strcasecmp(fTimeZoneID, timeZoneID) == 0)
92 			return B_OK;
93 
94 		strlcpy(fTimeZoneID, timeZoneID, sizeof(fTimeZoneID));
95 	}
96 
97 	delete fTimeZone;
98 	fTimeZone = TimeZone::createTimeZone(fTimeZoneID);
99 	if (fTimeZone == NULL)
100 		return B_NO_MEMORY;
101 
102 	if (offsetHasBeenSet) {
103 		fTimeZone->setRawOffset(*fDataBridge->addrOfTimezone * -1 * 1000);
104 	} else {
105 		int32_t rawOffset;
106 		int32_t dstOffset;
107 		UDate nowMillis = 1000 * (UDate)time(NULL);
108 		UErrorCode icuStatus = U_ZERO_ERROR;
109 		fTimeZone->getOffset(nowMillis, FALSE, rawOffset, dstOffset, icuStatus);
110 		if (!U_SUCCESS(icuStatus)) {
111 			*fDataBridge->addrOfTimezone = 0;
112 			*fDataBridge->addrOfDaylight = false;
113 			strcpy(fDataBridge->addrOfTZName[0], "GMT");
114 			strcpy(fDataBridge->addrOfTZName[1], "GMT");
115 
116 			return B_ERROR;
117 		}
118 		*fDataBridge->addrOfTimezone = -1 * (rawOffset + dstOffset) / 1000;
119 			// we want seconds, not the ms that ICU gives us
120 	}
121 
122 	*fDataBridge->addrOfDaylight = fTimeZone->useDaylightTime();
123 
124 	for (int i = 0; i < 2; ++i) {
125 		if (tz != NULL && *tz != ':' && i == 0) {
126 			strcpy(fDataBridge->addrOfTZName[0], fTimeZoneID);
127 		} else {
128 			UnicodeString icuString;
129 			fTimeZone->getDisplayName(i == 1, TimeZone::SHORT,
130 				fTimeData.ICULocaleForStrings(), icuString);
131 			CheckedArrayByteSink byteSink(fDataBridge->addrOfTZName[i],
132 				sizeof(fTimeZoneID));
133 			icuString.toUTF8(byteSink);
134 
135 			// make sure to canonicalize "GMT+00:00" to just "GMT"
136 			if (strcmp(fDataBridge->addrOfTZName[i], "GMT+00:00") == 0)
137 				fDataBridge->addrOfTZName[i][3] = '\0';
138 		}
139 	}
140 
141 	return B_OK;
142 }
143 
144 
145 status_t
146 ICUTimeConversion::Localtime(const time_t* inTime, struct tm* tmOut)
147 {
148 	if (fTimeZone == NULL)
149 		return B_NO_INIT;
150 
151 	tmOut->tm_zone = fTimeZoneID;
152 	return _FillTmValues(fTimeZone, inTime, tmOut);
153 }
154 
155 
156 status_t
157 ICUTimeConversion::Gmtime(const time_t* inTime, struct tm* tmOut)
158 {
159 	const TimeZone* icuTimeZone = TimeZone::getGMT();
160 		// no delete - doesn't belong to us
161 
162 	return _FillTmValues(icuTimeZone, inTime, tmOut);
163 }
164 
165 
166 status_t
167 ICUTimeConversion::Mktime(struct tm* inOutTm, time_t& timeOut)
168 {
169 	return _Mktime(fTimeZone, inOutTm, timeOut);
170 }
171 
172 
173 status_t
174 ICUTimeConversion::Timegm(struct tm* inOutTm, time_t& timeOut)
175 {
176 	const TimeZone* icuTimeZone = TimeZone::getGMT();
177 	return _Mktime(icuTimeZone, inOutTm, timeOut);
178 }
179 
180 
181 status_t
182 ICUTimeConversion::_FillTmValues(const TimeZone* icuTimeZone,
183 	const time_t* inTime, struct tm* tmOut)
184 {
185 	UErrorCode icuStatus = U_ZERO_ERROR;
186 	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(), icuStatus);
187 	if (!U_SUCCESS(icuStatus))
188 		return B_ERROR;
189 
190 	calendar.setTime(1000 * (UDate)*inTime, icuStatus);
191 	if (!U_SUCCESS(icuStatus))
192 		return B_ERROR;
193 
194 	tmOut->tm_sec = calendar.get(UCAL_SECOND, icuStatus);
195 	if (!U_SUCCESS(icuStatus))
196 		return B_ERROR;
197 	tmOut->tm_min = calendar.get(UCAL_MINUTE, icuStatus);
198 	if (!U_SUCCESS(icuStatus))
199 		return B_ERROR;
200 	tmOut->tm_hour = calendar.get(UCAL_HOUR_OF_DAY, icuStatus);
201 	if (!U_SUCCESS(icuStatus))
202 		return B_ERROR;
203 	tmOut->tm_mday = calendar.get(UCAL_DAY_OF_MONTH, icuStatus);
204 	if (!U_SUCCESS(icuStatus))
205 		return B_ERROR;
206 	tmOut->tm_mon = calendar.get(UCAL_MONTH, icuStatus);
207 	if (!U_SUCCESS(icuStatus))
208 		return B_ERROR;
209 	tmOut->tm_year = calendar.get(UCAL_YEAR, icuStatus) - 1900;
210 	if (!U_SUCCESS(icuStatus))
211 		return B_ERROR;
212 	tmOut->tm_wday = calendar.get(UCAL_DAY_OF_WEEK, icuStatus) - 1;
213 	if (!U_SUCCESS(icuStatus))
214 		return B_ERROR;
215 	tmOut->tm_yday = calendar.get(UCAL_DAY_OF_YEAR, icuStatus) - 1;
216 	if (!U_SUCCESS(icuStatus))
217 		return B_ERROR;
218 	tmOut->tm_isdst = calendar.inDaylightTime(icuStatus);
219 	if (!U_SUCCESS(icuStatus))
220 		return B_ERROR;
221 	tmOut->tm_gmtoff = (calendar.get(UCAL_ZONE_OFFSET, icuStatus)
222 		+ calendar.get(UCAL_DST_OFFSET, icuStatus)) / 1000;
223 	if (!U_SUCCESS(icuStatus))
224 		return B_ERROR;
225 	tmOut->tm_zone = fDataBridge->addrOfTZName[tmOut->tm_isdst ? 1 : 0];
226 
227 	return B_OK;
228 }
229 
230 
231 status_t
232 ICUTimeConversion::_Mktime(const TimeZone* icuTimeZone,
233 	struct tm* inOutTm, time_t& timeOut)
234 {
235 	if (icuTimeZone == NULL)
236 		return B_NO_INIT;
237 
238 	UErrorCode icuStatus = U_ZERO_ERROR;
239 	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(),
240 		icuStatus);
241 	if (!U_SUCCESS(icuStatus))
242 		return B_ERROR;
243 
244 	calendar.setLenient(TRUE);
245 	calendar.set(inOutTm->tm_year + 1900, inOutTm->tm_mon, inOutTm->tm_mday,
246 		inOutTm->tm_hour, inOutTm->tm_min, inOutTm->tm_sec);
247 
248 	UDate timeInMillis = calendar.getTime(icuStatus);
249 	if (!U_SUCCESS(icuStatus))
250 		return B_ERROR;
251 	timeOut = (time_t)((int64_t)timeInMillis / 1000);
252 
253 	return _FillTmValues(icuTimeZone, &timeOut, inOutTm);
254 }
255 
256 
257 }	// namespace Libroot
258 }	// namespace BPrivate
259