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 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 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 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 197 ICUTimeConversion::Mktime(struct tm* inOutTm, time_t& timeOut) 198 { 199 return _Mktime(fTimeZone, inOutTm, timeOut); 200 } 201 202 203 status_t 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 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 if (tmOut->tm_gmtoff == -*fDataBridge->addrOfTimezone) { 257 tmOut->tm_zone = fDataBridge->addrOfTZName[tmOut->tm_isdst ? 1 : 0]; 258 } else if (tmOut->tm_gmtoff == 0) { 259 tmOut->tm_zone = (char*)"GMT"; 260 } else { 261 return B_ERROR; 262 } 263 264 return B_OK; 265 } 266 267 268 status_t 269 ICUTimeConversion::_Mktime(const TimeZone* icuTimeZone, 270 struct tm* inOutTm, time_t& timeOut) 271 { 272 if (icuTimeZone == NULL) 273 return B_NO_INIT; 274 275 UErrorCode icuStatus = U_ZERO_ERROR; 276 GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(), 277 icuStatus); 278 if (!U_SUCCESS(icuStatus)) 279 return B_ERROR; 280 281 calendar.setLenient(TRUE); 282 calendar.set(inOutTm->tm_year + 1900, inOutTm->tm_mon, inOutTm->tm_mday, 283 inOutTm->tm_hour, inOutTm->tm_min, inOutTm->tm_sec); 284 285 UDate timeInMillis = calendar.getTime(icuStatus); 286 if (!U_SUCCESS(icuStatus)) 287 return B_ERROR; 288 timeOut = (time_t)((int64_t)timeInMillis / 1000); 289 290 return _FillTmValues(icuTimeZone, &timeOut, inOutTm); 291 } 292 293 294 } // namespace Libroot 295 } // namespace BPrivate 296