timezonedb.cpp 5.35 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
    Copyright (C) 2018 Volker Krause <vkrause@kde.org>

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This program is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU General Public License
15
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17
18
*/

#include "timezonedb.h"
19
#include "timezonedb_p.h"
20
#include "timezonedb_data.cpp"
21
// #include "timezone_zindex.cpp"
22
23
24

#include <QTimeZone>

25
using namespace KItinerary;
26

27
const char* KnowledgeDb::tzId(KnowledgeDb::Tz tz)
28
{
29
30
31
32
33
34
    return timezone_names + timezone_names_offsets[static_cast<std::underlying_type<KnowledgeDb::Tz>::type>(tz)];
}

QTimeZone KnowledgeDb::toQTimeZone(Tz tz)
{
    if (tz == Tz::Undefined) {
35
36
        return {};
    }
37
    return QTimeZone(tzId(tz));
38
}
39

40
KnowledgeDb::Tz KnowledgeDb::timezoneForCountry(CountryId country)
41
{
42
    const auto it = std::lower_bound(std::begin(country_timezone_map), std::end(country_timezone_map), country);
43
44
45
46
    if (it != std::end(country_timezone_map) && (*it).country == country) {
        return (*it).timezone;
    }

47
    return Tz::Undefined;
48
}
49

50
51
52
53
54
KnowledgeDb::CountryId KnowledgeDb::countryForTimezone(KnowledgeDb::Tz tz)
{
    return timezone_country_map[static_cast<std::underlying_type<KnowledgeDb::Tz>::type>(tz)];
}

55
56
57
#if 0
KnowledgeDb::Tz KnowledgeDb::timezoneForCoordinate(float lat, float lon)
{
58
59
60
61
62
    // see arctic latitude filter in the generator script, we only cover 65°S to 80°N
    if (lat < -65.0f || lat > 80.0f) {
        return Tz::Undefined;
    }

63
    const uint32_t x = ((lon + 180.0) / 360.0) * (1 << timezone_index_zDepth);
64
    const uint32_t y = ((lat + 65.0) / 145.0) * (1 << timezone_index_zDepth);
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    uint32_t z = 0;
    for (int i = timezone_index_zDepth - 1; i >= 0; --i) {
        z <<= 1;
        z += (y & (1 << i)) ? 1 : 0;
        z <<= 1;
        z += (x & (1 << i)) ? 1 : 0;
    }

    const auto it = std::upper_bound(std::begin(timezone_index), std::end(timezone_index), z);
    if (it == std::begin(timezone_index)) {
        return Tz::Undefined;
    }
    return (*std::prev(it)).tz;
}
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

static bool compareOffsetData(const QTimeZone::OffsetData &lhs, const QTimeZone::OffsetData &rhs)
{
    return lhs.offsetFromUtc == rhs.offsetFromUtc
        && lhs.standardTimeOffset == rhs.standardTimeOffset
        && lhs.daylightTimeOffset == rhs.daylightTimeOffset
        && lhs.atUtc == rhs.atUtc
        && lhs.abbreviation == rhs.abbreviation;
}

static bool isEquivalentTimezone(const QTimeZone &lhs, const QTimeZone &rhs)
{
    auto dt = QDateTime::currentDateTimeUtc();
    if (lhs.offsetFromUtc(dt) != rhs.offsetFromUtc(dt) || lhs.hasTransitions() != rhs.hasTransitions()) {
        return false;
    }

    for (int i = 0; i < 2 && dt.isValid(); ++i) {
        const auto lhsOff = lhs.nextTransition(dt);
        const auto rhsOff = rhs.nextTransition(dt);
        if (!compareOffsetData(lhsOff, rhsOff)) {
            return false;
        }
        dt = lhsOff.atUtc;
    }

    return true;
}

KnowledgeDb::Tz KnowledgeDb::timezoneForLocation(float lat, float lon, CountryId country)
{
    const auto coordTz = timezoneForCoordinate(lat, lon);
    const auto countryTz = timezoneForCountry(country);
112
113
    const auto countryFromCoord = countryForTimezone(coordTz);

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    // if we determine a different country than was provided, search for an equivalent timezone
    // in the requested country
    // example: Tijuana airport ending up in America/Los Angeles, and America/Tijuna being the only MX timezone equivalent to that
    if (coordTz != Tz::Undefined && countryFromCoord.isValid() && countryFromCoord != country) {
        bool nonUnique = false;
        Tz foundTz = Tz::Undefined;
        const auto coordZone = toQTimeZone(coordTz);

        constexpr const int timezone_count = sizeof(timezone_country_map) / sizeof(timezone_country_map[0]);
        for (int i = 1; i < timezone_count; ++i) {
            if (timezone_country_map[i] != country) {
                continue;
            }
            const auto t = static_cast<Tz>(i);
            if (!isEquivalentTimezone(toQTimeZone(t), coordZone)) {
                continue;
            }
            if (foundTz != Tz::Undefined) {
                nonUnique = true;
                break;
            }
            foundTz = t;
        }

        if (!nonUnique && foundTz != Tz::Undefined) {
            return foundTz;
        }
    }

    // only one method found a result, let's use that one
144
145
146
147
148
149
150
    if (coordTz == Tz::Undefined || coordTz == countryTz) {
        return countryTz;
    }
    if (countryTz == Tz::Undefined) {
        return coordTz;
    }

151
152
153
154
155
156
    // if the coordinate-based timezone is also in @p country, that takes precedence
    // example: the various AR sub-zones, or the MY sub-zone
    if (country == countryFromCoord) {
        return coordTz;
    }

157
158
159
    // if both timezones are equivalent, the country-based one wins, otherwise we use the coordinate one
    return isEquivalentTimezone(toQTimeZone(coordTz), toQTimeZone(countryTz)) ? countryTz : coordTz;
}
160
#endif