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