KItinerary

timezonedb.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "timezonedb_p.h"
8
9#include <KCountry>
10#include <KTimeZone>
11
12#include <QDebug>
13#include <QTimeZone>
14
15#include <cmath>
16#include <cstring>
17
18using namespace KItinerary;
19
20[[nodiscard]] static QTimeZone toQTimeZone(const char *tzId)
21{
22 if (!tzId || std::strcmp(tzId, "") == 0) {
23 return {};
24 }
25 return QTimeZone(tzId);
26}
27
28[[nodiscard]] static QList<const char*> timezonesForCountry(const KCountry &country)
29{
30 auto tzs = country.timeZoneIds();
31 if (tzs.size() <= 1) {
32 return tzs;
33 }
34 // filter overseas territories that map back to a different country code
35 tzs.erase(std::remove_if(tzs.begin(), tzs.end(), [country](const char *tz) {
36 return !(KTimeZone::country(tz) == country);
37 }), tzs.end());
38 return tzs;
39}
40
41[[nodiscard]] static bool compareOffsetData(const QTimeZone::OffsetData &lhs, const QTimeZone::OffsetData &rhs)
42{
43 return lhs.offsetFromUtc == rhs.offsetFromUtc
44 && lhs.standardTimeOffset == rhs.standardTimeOffset
45 && lhs.daylightTimeOffset == rhs.daylightTimeOffset
46 && lhs.atUtc == rhs.atUtc
47 && lhs.abbreviation == rhs.abbreviation;
48}
49
50[[nodiscard]] static bool isEquivalentTimezone(const QTimeZone &lhs, const QTimeZone &rhs)
51{
53 if (lhs.offsetFromUtc(dt) != rhs.offsetFromUtc(dt) || lhs.hasTransitions() != rhs.hasTransitions()) {
54 return false;
55 }
56
57 for (int i = 0; i < 2 && dt.isValid(); ++i) {
58 const auto lhsOff = lhs.nextTransition(dt);
59 const auto rhsOff = rhs.nextTransition(dt);
60 if (!compareOffsetData(lhsOff, rhsOff)) {
61 return false;
62 }
63 dt = lhsOff.atUtc;
64 }
65
66 return true;
67}
68
69QTimeZone KnowledgeDb::timezoneForLocation(float lat, float lon, QStringView alpha2CountryCode, QStringView regionCode)
70{
71
72 const auto coordTz = KTimeZone::fromLocation(lat, lon);
73 auto coordZone = toQTimeZone(coordTz);
74
75 const auto country = KCountry::fromAlpha2(alpha2CountryCode);
76 auto countryTzs = KCountrySubdivision::fromCode(QString(alpha2CountryCode + QLatin1Char('-') + regionCode)).timeZoneIds();
77 if (countryTzs.isEmpty()) {
78 countryTzs = timezonesForCountry(country);
79 }
80 const auto countryFromTz = KTimeZone::country(coordTz);
81
82 // if we determine a different country than was provided, search for an equivalent timezone
83 // in the requested country
84 // example: Tijuana airport ending up in America/Los Angeles, and America/Tijuna being the only MX timezone equivalent to that
85 if (coordTz && countryFromTz.isValid() && country.isValid() && !(countryFromTz == country)) { // ### clean up once KCountry has op!=
86 bool nonUnique = false;
87 QTimeZone foundTz;
88
89 for (const char *countryTz : countryTzs) {
90 const auto t = toQTimeZone(countryTz);
91 if (!isEquivalentTimezone(t, coordZone)) {
92 continue;
93 }
94 if (foundTz.isValid()) {
95 nonUnique = true;
96 break;
97 }
98 foundTz = t;
99 }
100
101 if (!nonUnique && foundTz.isValid()) {
102 return foundTz;
103 }
104 }
105
106 // only one method found a result, let's use that one
107 if ((coordZone.isValid() && countryTzs.contains(coordTz)) || countryTzs.isEmpty()) {
108 return coordZone;
109 }
110 if (!coordZone.isValid() && countryTzs.size() == 1) {
111 return toQTimeZone(countryTzs.at(0));
112 }
113
114 // if the coordinate-based timezone is also in @p country, that takes precedence
115 // example: the various AR sub-zones, or the MY sub-zone
116 if (country == countryFromTz) {
117 return coordZone;
118 }
119
120 // if both timezones are equivalent, the country-based one wins, otherwise we use the coordinate one
121 if (countryTzs.size() == 1) {
122 const auto countryQtz = toQTimeZone(countryTzs.at(0));
123 return isEquivalentTimezone(coordZone, countryQtz) ? countryQtz : coordZone;
124 }
125 return coordZone;
126}
127
128bool KnowledgeDb::isPlausibleTimeZone(const QTimeZone &tz, float lat, float lon, QStringView alpha2CountryCode, QStringView regionCode)
129{
130 // coordinate-based timezone matches
131 const auto coordTzId = KTimeZone::fromLocation(lat, lon);
132 const QTimeZone coordTz(coordTzId);
133 if (coordTzId && std::strcmp(coordTzId, "") != 0 && coordTz.isValid() && isEquivalentTimezone(tz, coordTz)) {
134 return true;
135 }
136
137 // any of the country/region code timezones matches
138 auto countryTzs = KCountrySubdivision::fromCode(QString(alpha2CountryCode + QLatin1Char('-') + regionCode)).timeZoneIds();
139 if (countryTzs.isEmpty()) {
140 countryTzs = timezonesForCountry(KCountry::fromAlpha2(alpha2CountryCode));
141 }
142
143 for (const auto &tzId : countryTzs) {
144 QTimeZone countryTz(tzId);
145 if (isEquivalentTimezone(countryTz, tz)) {
146 return true;
147 }
148 }
149
150 // if we were able to determine any timezone and none of them matched, we consider @p tz implausible
151 return (!coordTzId || std::strcmp(coordTzId, "") == 0 || !coordTz.isValid()) && countryTzs.isEmpty();
152}
static KCountrySubdivision fromCode(const char *code)
QList< const char * > timeZoneIds() const
bool isValid() const
static KCountry fromAlpha2(const char *alpha2Code)
QList< const char * > timeZoneIds() const
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
KI18NLOCALEDATA_EXPORT const char * fromLocation(float latitude, float longitude)
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
QDateTime currentDateTimeUtc()
bool hasTransitions() const const
bool isValid() const const
OffsetData nextTransition(const QDateTime &afterDateTime) const const
int offsetFromUtc(const QDateTime &atDateTime) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:56:37 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.