File indexing completed on 2024-11-24 04:44:12
0001 /* 0002 * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-3.0-or-later 0005 */ 0006 0007 #include "timezoneconverter.h" 0008 #include <QRegularExpression> 0009 0010 #include "pimkolab_debug.h" 0011 #include <QTimeZone> 0012 QString TimezoneConverter::normalizeTimezone(const QString &tz) 0013 { 0014 if (QTimeZone::isTimeZoneIdAvailable(tz.toLatin1())) { 0015 return tz; 0016 } 0017 auto normalizedId = QTimeZone::windowsIdToDefaultIanaId(tz.toLatin1()); 0018 if (!normalizedId.isEmpty()) { 0019 return QString::fromUtf8(normalizedId); 0020 } 0021 // We're dealing with an invalid or unknown timezone, try to parse it 0022 QString guessedTimezone = fromCityName(tz); 0023 if (guessedTimezone.isEmpty()) { 0024 guessedTimezone = fromHardcodedList(tz); 0025 } 0026 if (guessedTimezone.isEmpty()) { 0027 guessedTimezone = fromGMTOffsetTimezone(tz); 0028 } 0029 qCDebug(PIMKOLAB_LOG) << "Guessed timezone and found: " << guessedTimezone; 0030 return guessedTimezone; 0031 } 0032 0033 QString TimezoneConverter::fromGMTOffsetTimezone(const QString &tz) 0034 { 0035 Q_UNUSED(tz) 0036 return {}; 0037 } 0038 0039 QString TimezoneConverter::fromCityName(const QString &tz) 0040 { 0041 const auto zones = QTimeZone::availableTimeZoneIds(); 0042 QHash<QString, QString> countryMap; 0043 for (const auto &zone : zones) { 0044 const QString cityName = QString::fromUtf8(zone.split('/').last()); 0045 qDebug() << " zone : " << zone; 0046 qDebug() << " cityName : " << cityName; 0047 qDebug() << " countryMap : " << countryMap; 0048 // Q_ASSERT(!countryMap.contains(cityName)); 0049 countryMap.insert(cityName, QString::fromUtf8(zone)); 0050 } 0051 0052 static const QRegularExpression locationFinder(QStringLiteral("\\b([a-zA-Z])+\\b")); 0053 QRegularExpressionMatchIterator iter = locationFinder.globalMatch(tz); 0054 while (iter.hasNext()) { 0055 QRegularExpressionMatch match = iter.next(); 0056 const QString location = match.captured(0); 0057 qCDebug(PIMKOLAB_LOG) << "location " << location; 0058 if (countryMap.contains(location)) { 0059 qCDebug(PIMKOLAB_LOG) << "found match " << countryMap.value(location); 0060 return countryMap.value(location); 0061 } 0062 } 0063 return {}; 0064 } 0065 0066 // Based on 0067 // * http://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx 0068 // * http://technet.microsoft.com/en-us/library/cc749073(v=ws.10).aspx 0069 // * http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml 0070 // * http://stackoverflow.com/questions/4967903/linux-windows-timezone-mapping 0071 static const struct WindowsTimezone { 0072 // const int gmtOffset; 0073 const char *timezoneSpecifier; // This one should be stable and always in english 0074 const char *name; // The display name (which is in some cases still useful to try guessing) 0075 const char *olson[28]; // Corresponding olson timezones we can map to 0076 } windowsTimezones[] = { 0077 {"Afghanistan Standard Time", "Kabul", {"Asia/Kabul", "Asia/Kabul"}}, 0078 {"Alaskan Standard Time", "Alaska", {"America/Anchorage", "America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"}}, 0079 {"Arab Standard Time", "Kuwait, Riyadh", {"Asia/Riyadh", "Asia/Bahrain", "Asia/Kuwait", "Asia/Qatar", "Asia/Riyadh", "Asia/Aden"}}, 0080 {"Arabian Standard Time", "Abu Dhabi, Muscat", {"Asia/Dubai", "Asia/Dubai", "Asia/Muscat", "Etc/GMT-4"}}, 0081 {"Arabic Standard Time", "Baghdad", {"Asia/Baghdad", "Asia/Baghdad"}}, 0082 {"Atlantic Standard Time", 0083 "Atlantic Time (Canada)", 0084 {"America/Halifax", "Atlantic/Bermuda", "America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton", "America/Thule"}}, 0085 {"AUS Central Standard Time", "Darwin", {"Australia/Darwin", "Australia/Darwin"}}, 0086 {"AUS Eastern Standard Time", "Canberra, Melbourne, Sydney", {"Australia/Sydney", "Australia/Sydney Australia/Melbourne"}}, 0087 {"Azerbaijan Standard Time", "Baku", {"Asia/Baku", "Asia/Baku"}}, 0088 {"Azores Standard Time", "Azores", {"Atlantic/Azores", "America/Scoresbysund", "Atlantic/Azores"}}, 0089 {"Canada Central Standard Time", "Saskatchewan", {"America/Regina", "America/Regina America/Swift_Current"}}, 0090 {"Cape Verde Standard Time", "Cape Verde Islands", {"Atlantic/Cape_Verde", "Atlantic/Cape_Verde", "Etc/GMT+1"}}, 0091 {"Caucasus Standard Time", "Yerevan", {"Asia/Yerevan", "Asia/Yerevan"}}, 0092 {"Cen. Australia Standard Time", "Adelaide", {"Australia/Adelaide", "Australia/Adelaide Australia/Broken_Hill"}}, 0093 {"Central America Standard Time", 0094 "Central America", 0095 {"America/Guatemala", 0096 "America/Belize", 0097 "America/Costa_Rica", 0098 "Pacific/Galapagos", 0099 "America/Guatemala", 0100 "America/Tegucigalpa", 0101 "America/Managua", 0102 "America/El_Salvador", 0103 "Etc/GMT+6"}}, 0104 {"Central Asia Standard Time", 0105 "Astana, Dhaka", 0106 {"Asia/Almaty", "Antarctica/Vostok", "Indian/Chagos", "Asia/Bishkek", "Asia/Almaty Asia/Qyzylorda", "Etc/GMT-6"}}, 0107 {"Central Brazilian Standard Time", "Manaus", {"America/Cuiaba", "America/Cuiaba America/Campo_Grande"}}, 0108 {"Central Europe Standard Time", 0109 "Belgrade, Bratislava, Budapest, Ljubljana, Prague", 0110 {"Europe/Budapest", "Europe/Tirane", "Europe/Prague", "Europe/Budapest", "Europe/Podgorica", "Europe/Belgrade", "Europe/Ljubljana", "Europe/Bratislava"}}, 0111 {"Central European Standard Time", 0112 "Sarajevo, Skopje, Warsaw, Zagreb", 0113 {"Europe/Warsaw", "Europe/Sarajevo", "Europe/Zagreb", "Europe/Skopje", "Europe/Warsaw"}}, 0114 {"Central Pacific Standard Time", 0115 "Magadan, Solomon Islands, New Caledonia", 0116 {"Pacific/Guadalcanal", "Antarctica/Macquarie", "Pacific/Ponape Pacific/Kosrae", "Pacific/Noumea", "Pacific/Guadalcanal", "Pacific/Efate", "Etc/GMT-11"}}, 0117 {"Central Standard Time", 0118 "Central Time (US and Canada)", 0119 {"America/Chicago", 0120 "America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute", 0121 "America/Matamoros", 0122 "America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center " 0123 "America/North_Dakota/New_Salem", 0124 "CST6CDT"}}, 0125 {"Central Standard Time (Mexico)", 0126 "Guadalajara, Mexico City, Monterrey", 0127 {"America/Mexico_City", "America/Mexico_City America/Bahia_Banderas America/Cancun America/Merida America/Monterrey"}}, 0128 {"China Standard Time", 0129 "Beijing, Chongqing, Hong Kong SAR, Urumqi", 0130 {"Asia/Shanghai", "Asia/Shanghai Asia/Chongqing Asia/Harbin Asia/Kashgar Asia/Urumqi", "Asia/Hong_Kong", "Asia/Macau"}}, 0131 {"Dateline Standard Time", "International Date Line West", {"Etc/GMT+12", "Etc/GMT+12"}}, 0132 {"E. Africa Standard Time", 0133 "Nairobi", 0134 {"Africa/Nairobi", 0135 "Antarctica/Syowa", 0136 "Africa/Djibouti", 0137 "Africa/Asmera", 0138 "Africa/Addis_Ababa", 0139 "Africa/Nairobi", 0140 "Indian/Comoro", 0141 "Indian/Antananarivo", 0142 "Africa/Khartoum", 0143 "Africa/Mogadishu", 0144 "Africa/Juba", 0145 "Africa/Dar_es_Salaam", 0146 "Africa/Kampala", 0147 "Indian/Mayotte", 0148 "Etc/GMT-3"}}, 0149 {"E. Australia Standard Time", "Brisbane", {"Australia/Brisbane", "Australia/Brisbane Australia/Lindeman"}}, 0150 {"E. Europe Standard Time", "Minsk", {"Asia/Nicosia", "Asia/Nicosia"}}, 0151 {"E. South America Standard Time", "Brasilia", {"America/Sao_Paulo", "America/Sao_Paulo"}}, 0152 {"Eastern Standard Time", 0153 "Eastern Time (US and Canada)", 0154 {"America/New_York", 0155 "America/Nassau", 0156 "America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay", 0157 "America/Grand_Turk", 0158 "America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello " 0159 "America/Louisville", 0160 "EST5EDT"}}, 0161 {"Egypt Standard Time", "Cairo", {"Africa/Cairo", "Africa/Cairo", "Asia/Gaza Asia/Hebron"}}, 0162 {"Ekaterinburg Standard Time", "Ekaterinburg", {"Asia/Yekaterinburg", "Asia/Yekaterinburg"}}, 0163 {"Fiji Standard Time", "Fiji Islands, Kamchatka, Marshall Islands", {"Pacific/Fiji", "Pacific/Fiji"}}, 0164 {"FLE Standard Time", 0165 "Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius", 0166 {"Europe/Kiev", 0167 "Europe/Mariehamn", 0168 "Europe/Sofia", 0169 "Europe/Tallinn", 0170 "Europe/Helsinki", 0171 "Europe/Vilnius", 0172 "Europe/Riga", 0173 "Europe/Kiev Europe/Simferopol Europe/Uzhgorod Europe/Zaporozhye"}}, 0174 {"Georgian Standard Time", "Tblisi", {"Asia/Tbilisi", "Asia/Tbilisi"}}, 0175 {"GMT Standard Time", 0176 "Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London", 0177 {"Europe/London", 0178 "Atlantic/Canary", 0179 "Atlantic/Faeroe", 0180 "Europe/London", 0181 "Europe/Guernsey", 0182 "Europe/Dublin", 0183 "Europe/Isle_of_Man", 0184 "Europe/Jersey", 0185 "Europe/Lisbon Atlantic/Madeira"}}, 0186 {"Greenland Standard Time", "Greenland", {"America/Godthab", "America/Godthab"}}, 0187 {"Greenwich Standard Time", 0188 "Casablanca, Monrovia", 0189 {"Atlantic/Reykjavik", 0190 "Africa/Ouagadougou", 0191 "Africa/Abidjan", 0192 "Africa/El_Aaiun", 0193 "Africa/Accra", 0194 "Africa/Banjul", 0195 "Africa/Conakry", 0196 "Africa/Bissau", 0197 "Atlantic/Reykjavik", 0198 "Africa/Monrovia", 0199 "Africa/Bamako", 0200 "Africa/Nouakchott", 0201 "Atlantic/St_Helena", 0202 "Africa/Freetown", 0203 "Africa/Dakar", 0204 "Africa/Sao_Tome", 0205 "Africa/Lome"}}, 0206 {"GTB Standard Time", "Athens, Bucharest, Istanbul", {"Europe/Bucharest", "Europe/Athens", "Europe/Chisinau", "Europe/Bucharest"}}, 0207 {"Hawaiian Standard Time", "Hawaii", {"Pacific/Honolulu", "Pacific/Rarotonga", "Pacific/Tahiti", "Pacific/Johnston", "Pacific/Honolulu", "Etc/GMT+10"}}, 0208 {"India Standard Time", "Chennai, Kolkata, Mumbai, New Delhi", {"Asia/Calcutta", "Asia/Calcutta"}}, 0209 {"Iran Standard Time", "Tehran", {"Asia/Tehran", "Asia/Tehran"}}, 0210 {"Israel Standard Time", "Jerusalem", {"Asia/Jerusalem", "Asia/Jerusalem"}}, 0211 {"Korea Standard Time", "Seoul", {"Asia/Seoul", "Asia/Pyongyang", "Asia/Seoul"}}, 0212 // {"Mid-Atlantic Standard Time", "Mid-Atlantic", {"}}, 0213 {"Mountain Standard Time", 0214 "Mountain Time (US and Canada)", 0215 {"America/Denver", 0216 "America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife", 0217 "America/Ojinaga", 0218 "America/Denver America/Boise America/Shiprock", 0219 "MST7MDT"}}, 0220 {"Mountain Standard Time (Mexico)", "Chihuahua, La Paz, Mazatlan", {"America/Chihuahua", "America/Chihuahua America/Mazatlan"}}, 0221 {"Myanmar Standard Time", "Yangon (Rangoon)", {"Asia/Rangoon", "Indian/Cocos", "Asia/Rangoon"}}, 0222 {"N. Central Asia Standard Time", "Almaty, Novosibirsk", {"Asia/Novosibirsk", "Asia/Novosibirsk Asia/Novokuznetsk Asia/Omsk"}}, 0223 {"Namibia Standard Time", "Windhoek", {"Africa/Windhoek", "Africa/Windhoek"}}, 0224 {"Nepal Standard Time", "Kathmandu", {"Asia/Katmandu", "Asia/Katmandu"}}, 0225 {"New Zealand Standard Time", "Auckland, Wellington", {"Pacific/Auckland", "Antarctica/South_Pole Antarctica/McMurdo", "Pacific/Auckland"}}, 0226 {"Newfoundland Standard Time", "Newfoundland and Labrador", {"America/St_Johns", "America/St_Johns"}}, 0227 {"North Asia East Standard Time", "Irkutsk, Ulaanbaatar", {"Asia/Irkutsk", "Asia/Irkutsk"}}, 0228 {"North Asia Standard Time", "Krasnoyarsk", {"Asia/Krasnoyarsk", "Asia/Krasnoyarsk"}}, 0229 {"Pacific SA Standard Time", "Santiago", {"America/Santiago", "Antarctica/Palmer", "America/Santiago"}}, 0230 {"Pacific Standard Time", 0231 "Pacific Time (US and Canada); Tijuana", 0232 {"America/Los_Angeles", "America/Vancouver America/Dawson America/Whitehorse", "America/Tijuana", "America/Los_Angeles", "PST8PDT"}}, 0233 {"Romance Standard Time", 0234 "Brussels, Copenhagen, Madrid, Paris", 0235 {"Europe/Paris", "Europe/Brussels", "Europe/Copenhagen", "Europe/Madrid Africa/Ceuta", "Europe/Paris"}}, 0236 {"Russian Standard Time", "Moscow, St. Petersburg, Volgograd", {"Europe/Moscow", "Europe/Moscow Europe/Samara Europe/Volgograd"}}, 0237 {"SA Eastern Standard Time", 0238 "Buenos Aires, Georgetown", 0239 {"America/Cayenne", 0240 "Antarctica/Rothera", 0241 "America/Fortaleza America/Araguaina America/Belem America/Maceio America/Recife America/Santarem", 0242 "Atlantic/Stanley", 0243 "America/Cayenne", 0244 "America/Paramaribo", 0245 "Etc/GMT+3"}}, 0246 {"SA Pacific Standard Time", 0247 "Bogota, Lima, Quito", 0248 {"America/Bogota", 0249 "America/Coral_Harbour", 0250 "America/Bogota", 0251 "America/Guayaquil", 0252 "America/Port-au-Prince", 0253 "America/Jamaica", 0254 "America/Cayman", 0255 "America/Panama", 0256 "America/Lima", 0257 "Etc/GMT+5"}}, 0258 {"SA Western Standard Time", 0259 "Caracas, La Paz", 0260 {"America/La_Paz", 0261 "America/Antigua", 0262 "America/Anguilla", 0263 "America/Aruba", 0264 "America/Barbados", 0265 "America/St_Barthelemy", 0266 "America/La_Paz", 0267 "America/Kralendijk", 0268 "America/Manaus America/Boa_Vista America/Eirunepe America/Porto_Velho America/Rio_Branco", 0269 "America/Blanc-Sablon", 0270 "America/Curacao", 0271 "America/Dominica", 0272 "America/Santo_Domingo", 0273 "America/Grenada", 0274 "America/Guadeloupe", 0275 "America/Guyana", 0276 "America/St_Kitts", 0277 "America/St_Lucia", 0278 "America/Marigot", 0279 "America/Martinique", 0280 "America/Montserrat", 0281 "America/Puerto_Rico", 0282 "America/Lower_Princes", 0283 "America/Port_of_Spain", 0284 "America/St_Vincent", 0285 "America/Tortola", 0286 "America/St_Thomas", 0287 "Etc/GMT+4"}}, 0288 {"Samoa Standard Time", "Midway Island, Samoa", {"Pacific/Apia", "Pacific/Apia"}}, 0289 {"SE Asia Standard Time", 0290 "Bangkok, Hanoi, Jakarta", 0291 {"Asia/Bangkok", 0292 "Antarctica/Davis", 0293 "Indian/Christmas", 0294 "Asia/Jakarta Asia/Pontianak", 0295 "Asia/Phnom_Penh", 0296 "Asia/Vientiane", 0297 "Asia/Hovd", 0298 "Asia/Bangkok", 0299 "Asia/Saigon", 0300 "Etc/GMT-7"}}, 0301 {"Singapore Standard Time", 0302 "Kuala Lumpur, Singapore", 0303 {"Asia/Singapore", "Asia/Brunei", "Asia/Makassar", "Asia/Kuala_Lumpur Asia/Kuching", "Asia/Manila", "Asia/Singapore", "Etc/GMT-8"}}, 0304 {"South Africa Standard Time", 0305 "Harare, Pretoria", 0306 {"Africa/Johannesburg", 0307 "Africa/Bujumbura", 0308 "Africa/Gaborone", 0309 "Africa/Lubumbashi", 0310 "Africa/Maseru", 0311 "Africa/Blantyre", 0312 "Africa/Maputo", 0313 "Africa/Kigali", 0314 "Africa/Mbabane", 0315 "Africa/Johannesburg", 0316 "Africa/Lusaka", 0317 "Africa/Harare", 0318 "Etc/GMT-2"}}, 0319 {"Sri Lanka Standard Time", "Sri Jayawardenepura", {"Asia/Colombo", "Asia/Colombo"}}, 0320 {"Taipei Standard Time", "Taipei", {"Asia/Taipei", "Asia/Taipei"}}, 0321 {"Tasmania Standard Time", "Hobart", {"Australia/Hobart", "Australia/Hobart Australia/Currie"}}, 0322 {"Tokyo Standard Time", "Osaka, Sapporo, Tokyo", {"Asia/Tokyo", "Asia/Jayapura", "Asia/Tokyo", "Pacific/Palau", "Asia/Dili", "Etc/GMT-9"}}, 0323 {"Tonga Standard Time", "Nuku'alofa", {"Pacific/Tongatapu", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Tongatapu", "Etc/GMT-13"}}, 0324 {"US Eastern Standard Time", "Indiana (East)", {"America/Indianapolis", "America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"}}, 0325 {"US Mountain Standard Time", "Arizona", {"America/Phoenix", "America/Dawson_Creek America/Creston", "America/Hermosillo", "America/Phoenix", "Etc/GMT+7"}}, 0326 {"Vladivostok Standard Time", "Vladivostok", {"Asia/Vladivostok", "Asia/Vladivostok Asia/Sakhalin"}}, 0327 {"W. Australia Standard Time", "Perth", {"Australia/Perth", "Antarctica/Casey", "Australia/Perth"}}, 0328 {"W. Central Africa Standard Time", 0329 "West Central Africa", 0330 {"Africa/Lagos", 0331 "Africa/Luanda", 0332 "Africa/Porto-Novo", 0333 "Africa/Kinshasa", 0334 "Africa/Bangui", 0335 "Africa/Brazzaville", 0336 "Africa/Douala", 0337 "Africa/Algiers", 0338 "Africa/Libreville", 0339 "Africa/Malabo", 0340 "Africa/Niamey", 0341 "Africa/Lagos", 0342 "Africa/Ndjamena", 0343 "Africa/Tunis", 0344 "Etc/GMT-1"}}, 0345 {"W. Europe Standard Time", 0346 "Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", 0347 {"Europe/Berlin", 0348 "Europe/Andorra", 0349 "Europe/Vienna", 0350 "Europe/Zurich", 0351 "Europe/Berlin", 0352 "Europe/Gibraltar", 0353 "Europe/Rome", 0354 "Europe/Vaduz", 0355 "Europe/Luxembourg", 0356 "Africa/Tripoli", 0357 "Europe/Monaco", 0358 "Europe/Malta", 0359 "Europe/Amsterdam", 0360 "Europe/Oslo", 0361 "Europe/Stockholm", 0362 "Arctic/Longyearbyen", 0363 "Europe/San_Marino", 0364 "Europe/Vatican"}}, 0365 {"West Asia Standard Time", 0366 "Islamabad, Karachi, Tashkent", 0367 {"Asia/Tashkent", 0368 "Antarctica/Mawson", 0369 "Asia/Oral Asia/Aqtau Asia/Aqtobe", 0370 "Indian/Maldives", 0371 "Indian/Kerguelen", 0372 "Asia/Dushanbe", 0373 "Asia/Ashgabat", 0374 "Asia/Tashkent Asia/Samarkand", 0375 "Etc/GMT-5"}}, 0376 {"West Pacific Standard Time", 0377 "Guam, Port Moresby", 0378 {"Pacific/Port_Moresby", "Antarctica/DumontDUrville", "Pacific/Truk", "Pacific/Guam", "Pacific/Saipan", "Pacific/Port_Moresby", "Etc/GMT-10"}}, 0379 {"Yakutsk Standard Time", "Yakuts", {"Asia/Yakutsk", "Asia/Yakutsk"}}}; 0380 static const int numWindowsTimezones = sizeof windowsTimezones / sizeof *windowsTimezones; 0381 0382 QString TimezoneConverter::fromHardcodedList(const QString &tz) 0383 { 0384 for (int i = 0; i < numWindowsTimezones; i++) { 0385 const WindowsTimezone &windowsTimezone = windowsTimezones[i]; 0386 const QByteArray specifier(windowsTimezone.timezoneSpecifier); 0387 const QByteArray windowsName(windowsTimezone.name); 0388 if ((!specifier.isEmpty() && tz.contains(QString::fromUtf8(specifier))) || (!windowsName.isEmpty() && tz.contains(QString::fromUtf8(windowsName)))) { 0389 // TODO find the olson timezone matching the local timezone if we have multiple to map to 0390 return QString::fromLatin1(windowsTimezone.olson[0]); 0391 } 0392 } 0393 return {}; 0394 }