File indexing completed on 2024-12-29 04:50:10
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "timezonedb_p.h" 0008 0009 #include <KCountry> 0010 #include <KTimeZone> 0011 0012 #include <QDebug> 0013 #include <QTimeZone> 0014 0015 #include <cmath> 0016 #include <cstring> 0017 0018 using namespace KItinerary; 0019 0020 static QTimeZone toQTimeZone(const char *tzId) 0021 { 0022 if (!tzId || std::strcmp(tzId, "") == 0) { 0023 return {}; 0024 } 0025 return QTimeZone(tzId); 0026 } 0027 0028 static QList<const char*> timezonesForCountry(const KCountry &country) 0029 { 0030 auto tzs = country.timeZoneIds(); 0031 if (tzs.size() <= 1) { 0032 return tzs; 0033 } 0034 // filter overseas territories that map back to a different country code 0035 tzs.erase(std::remove_if(tzs.begin(), tzs.end(), [country](const char *tz) { 0036 return !(KTimeZone::country(tz) == country); 0037 }), tzs.end()); 0038 return tzs; 0039 } 0040 0041 static bool compareOffsetData(const QTimeZone::OffsetData &lhs, const QTimeZone::OffsetData &rhs) 0042 { 0043 return lhs.offsetFromUtc == rhs.offsetFromUtc 0044 && lhs.standardTimeOffset == rhs.standardTimeOffset 0045 && lhs.daylightTimeOffset == rhs.daylightTimeOffset 0046 && lhs.atUtc == rhs.atUtc 0047 && lhs.abbreviation == rhs.abbreviation; 0048 } 0049 0050 static bool isEquivalentTimezone(const QTimeZone &lhs, const QTimeZone &rhs) 0051 { 0052 auto dt = QDateTime::currentDateTimeUtc(); 0053 if (lhs.offsetFromUtc(dt) != rhs.offsetFromUtc(dt) || lhs.hasTransitions() != rhs.hasTransitions()) { 0054 return false; 0055 } 0056 0057 for (int i = 0; i < 2 && dt.isValid(); ++i) { 0058 const auto lhsOff = lhs.nextTransition(dt); 0059 const auto rhsOff = rhs.nextTransition(dt); 0060 if (!compareOffsetData(lhsOff, rhsOff)) { 0061 return false; 0062 } 0063 dt = lhsOff.atUtc; 0064 } 0065 0066 return true; 0067 } 0068 0069 QTimeZone KnowledgeDb::timezoneForLocation(float lat, float lon, QStringView alpha2CountryCode, QStringView regionCode) 0070 { 0071 0072 const auto coordTz = KTimeZone::fromLocation(lat, lon); 0073 const auto coordZone = toQTimeZone(coordTz); 0074 0075 const auto country = KCountry::fromAlpha2(alpha2CountryCode); 0076 auto countryTzs = KCountrySubdivision::fromCode(QString(alpha2CountryCode + QLatin1Char('-') + regionCode)).timeZoneIds(); 0077 if (countryTzs.isEmpty()) { 0078 countryTzs = timezonesForCountry(country); 0079 } 0080 const auto countryFromTz = KTimeZone::country(coordTz); 0081 0082 // if we determine a different country than was provided, search for an equivalent timezone 0083 // in the requested country 0084 // example: Tijuana airport ending up in America/Los Angeles, and America/Tijuna being the only MX timezone equivalent to that 0085 if (coordTz && countryFromTz.isValid() && country.isValid() && !(countryFromTz == country)) { // ### clean up once KCountry has op!= 0086 bool nonUnique = false; 0087 QTimeZone foundTz; 0088 0089 for (const char *countryTz : countryTzs) { 0090 const auto t = toQTimeZone(countryTz); 0091 if (!isEquivalentTimezone(t, coordZone)) { 0092 continue; 0093 } 0094 if (foundTz.isValid()) { 0095 nonUnique = true; 0096 break; 0097 } 0098 foundTz = t; 0099 } 0100 0101 if (!nonUnique && foundTz.isValid()) { 0102 return foundTz; 0103 } 0104 } 0105 0106 // only one method found a result, let's use that one 0107 if ((coordZone.isValid() && countryTzs.contains(coordTz)) || countryTzs.isEmpty()) { 0108 return coordZone; 0109 } 0110 if (!coordZone.isValid() && countryTzs.size() == 1) { 0111 return toQTimeZone(countryTzs.at(0)); 0112 } 0113 0114 // if the coordinate-based timezone is also in @p country, that takes precedence 0115 // example: the various AR sub-zones, or the MY sub-zone 0116 if (country == countryFromTz) { 0117 return coordZone; 0118 } 0119 0120 // if both timezones are equivalent, the country-based one wins, otherwise we use the coordinate one 0121 if (countryTzs.size() == 1) { 0122 const auto countryQtz = toQTimeZone(countryTzs.at(0)); 0123 return isEquivalentTimezone(coordZone, countryQtz) ? countryQtz : coordZone; 0124 } 0125 return coordZone; 0126 }