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