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