File indexing completed on 2024-11-24 04:45:05

0001 /*
0002    SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "timezones.h"
0008 
0009 #include <QDebug>
0010 #include <QFile>
0011 
0012 #include <cassert>
0013 #include <limits>
0014 
0015 using namespace KItinerary::Generator;
0016 
0017 Timezones::Timezones()
0018 {
0019     // load zone.tab for country mapping
0020     QFile zoneTab(QStringLiteral("/usr/share/zoneinfo/zone.tab"));
0021     if (!zoneTab.open(QFile::ReadOnly)) {
0022         qCritical() << "Unable to open zonetab file: " << zoneTab.errorString();
0023         exit(1);
0024     }
0025 
0026     const auto lines = QString::fromUtf8(zoneTab.readAll()).split(QLatin1Char('\n'));
0027     for (const auto &line : lines) {
0028         if (line.startsWith(QLatin1Char('#'))) {
0029             continue;
0030         }
0031 
0032         const auto cols = line.split(QLatin1Char('\t'));
0033         if (cols.size() < 3) {
0034             continue;
0035         }
0036 
0037         const auto countries = cols.at(0).split(QLatin1Char(','));
0038         const auto tzName = cols.at(2).toUtf8();
0039 
0040         m_zones.push_back(tzName);
0041         for (const auto &country : countries) {
0042             m_countryZones[country].push_back(tzName);
0043         }
0044         if (countries.size() == 1) {
0045             if (m_countryForZone.find(tzName) != m_countryForZone.end()) {
0046                 m_countryForZone[tzName] = QString();
0047             } else {
0048                 m_countryForZone[tzName] = countries[0];
0049             }
0050         }
0051     }
0052 
0053     /* Remove non-official zones that openSUSE patches into their zonetab. */
0054     removeZone("Asia/Beijing");
0055 
0056     /* Manual overrides for countries that de-facto only have a single timezone,
0057      * even if the IANA database doesn't reflect that.
0058     */
0059     m_countryZones[QStringLiteral("AR")] = { "America/Argentina/Buenos_Aires" };
0060     m_countryZones[QStringLiteral("CN")] = { "Asia/Shanghai" };
0061     m_countryZones[QStringLiteral("CY")] = { "Asia/Nicosia" };
0062     m_countryZones[QStringLiteral("DE")] = { "Europe/Berlin" };
0063     m_countryZones[QStringLiteral("MY")] = { "Asia/Kuala_Lumpur" };
0064 
0065     /* Manual overrides for timezones that do not belong to a unique country, contrary what zonetab claims. */
0066     setCountryForZone("Asia/Bangkok", {}); // also used in northern Vietnam
0067     setCountryForZone("Europe/Simferopol", {}); // disputed area
0068 
0069     // create offset index
0070     std::sort(m_zones.begin(), m_zones.end());
0071     m_zoneOffsets.reserve(m_zones.size());
0072     uint16_t offset = 0;
0073     for (const auto &tz : m_zones) {
0074         m_zoneOffsets.push_back(offset);
0075         offset += tz.size() + 1; // +1 of the trailing null byte
0076     }
0077 
0078 }
0079 
0080 Timezones::~Timezones() = default;
0081 
0082 uint16_t Timezones::offset(const QByteArray& tz) const
0083 {
0084     const auto it = std::lower_bound(m_zones.begin(), m_zones.end(), tz);
0085     if (it == m_zones.end() || (*it) != tz) {
0086         return std::numeric_limits<uint16_t>::max();
0087     }
0088     return m_zoneOffsets[std::distance(m_zones.begin(), it)];
0089 }
0090 
0091 void Timezones::setCountryForZone(const QByteArray &tz, const QString &country)
0092 {
0093     auto it = m_countryForZone.find(tz);
0094     if (it == m_countryForZone.end()) {
0095         return;
0096     }
0097     (*it).second = country;
0098 }
0099 
0100 void Timezones::removeZone(const QByteArray &tz)
0101 {
0102     m_zones.erase(std::remove(m_zones.begin(), m_zones.end(), tz), m_zones.end());
0103     m_countryForZone.erase(tz);
0104     for (auto &it : m_countryZones) {
0105         it.second.erase(std::remove(it.second.begin(), it.second.end(), tz), it.second.end());
0106     }
0107 }