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 }