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