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