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