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