File indexing completed on 2024-04-14 14:20:07

0001 /*
0002    This file is part of the KDE libraries
0003    Copyright (c) 2005-2010 David Jarvie <djarvie@kde.org>
0004    Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.
0005 
0006    This library is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU Library General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 
0011    This library is distributed in the hope that it will be useful,
0012    but WITHOUT ANY WARRANTY; without even the implied warranty of
0013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014    Library General Public License for more details.
0015 
0016    You should have received a copy of the GNU Library General Public License
0017    along with this library; see the file COPYING.LIB.  If not, write to
0018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019    Boston, MA 02110-1301, USA.
0020 */
0021 
0022 // This file requires HAVE_STRUCT_TM_TM_ZONE to be defined if struct tm member tm_zone is available.
0023 // This file requires HAVE_TM_GMTOFF to be defined if struct tm member tm_gmtoff is available.
0024 
0025 #include "moc_ksystemtimezone.cpp"
0026 
0027 #include <config-date.h>
0028 
0029 #if HAVE_SYS_TIME_H
0030 #include <sys/time.h>
0031 #endif
0032 #if HAVE_TIME_H
0033 #include <time.h>
0034 #endif
0035 #include <climits>
0036 #include <cstdlib>
0037 
0038 #include <QDebug>
0039 #include <QCoreApplication>
0040 #include <QFile>
0041 #include <QFileInfo>
0042 #include <QDir>
0043 #include <QRegExp>
0044 #include <QStringList>
0045 #include <QTextStream>
0046 #include <QDBusConnection>
0047 #include <QDBusInterface>
0048 #include <QDBusConnectionInterface>
0049 #include <QDBusReply>
0050 
0051 #include <kconfig.h>
0052 #include <klocalizedstring.h>
0053 #include <kstringhandler.h>
0054 #include <kconfiggroup.h>
0055 #include "ktzfiletimezone.h"
0056 #ifdef Q_OS_WIN
0057 #include "ktimezone_win.h"
0058 #endif
0059 
0060 #define KTIMEZONED_DBUS_IFACE "org.kde.KTimeZoned"
0061 
0062 /* Return the offset to UTC in the current time zone at the specified UTC time.
0063  * The thread-safe function localtime_r() is used in preference if available.
0064  */
0065 int gmtoff(time_t t)
0066 {
0067 #ifdef _POSIX_THREAD_SAFE_FUNCTIONS
0068     tm tmtime;
0069     if (!localtime_r(&t, &tmtime)) {
0070         return 0;
0071     }
0072 #if HAVE_TM_GMTOFF
0073     return tmtime.tm_gmtoff;
0074 #else
0075     int lwday = tmtime.tm_wday;
0076     int lt = 3600 * tmtime.tm_hour + 60 * tmtime.tm_min + tmtime.tm_sec;
0077     if (!gmtime_r(&t, &tmtime)) {
0078         return 0;
0079     }
0080     int uwday = tmtime.tm_wday;
0081     int ut = 3600 * tmtime.tm_hour + 60 * tmtime.tm_min + tmtime.tm_sec;
0082 #endif
0083 #else
0084     tm *tmtime = localtime(&t);
0085     if (!tmtime) {
0086         return 0;
0087     }
0088 #if HAVE_TM_GMTOFF
0089     return tmtime->tm_gmtoff;
0090 #else
0091     int lwday = tmtime->tm_wday;
0092     int lt = 3600 * tmtime->tm_hour + 60 * tmtime->tm_min + tmtime->tm_sec;
0093     tmtime = gmtime(&t);
0094     int uwday = tmtime->tm_wday;
0095     int ut = 3600 * tmtime->tm_hour + 60 * tmtime->tm_min + tmtime->tm_sec;
0096 #endif
0097 #endif
0098 #if ! HAVE_TM_GMTOFF
0099     if (lwday != uwday) {
0100         // Adjust for different day
0101         if (lwday == uwday + 1  || (lwday == 0 && uwday == 6)) {
0102             lt += 24 * 3600;
0103         } else {
0104             lt -= 24 * 3600;
0105         }
0106     }
0107     return lt - ut;
0108 #endif
0109 }
0110 
0111 /******************************************************************************/
0112 
0113 class KSystemTimeZonesPrivate : public KTimeZones
0114 {
0115 public:
0116     static KSystemTimeZonesPrivate *instance();
0117     static KTzfileTimeZoneSource *tzfileSource();
0118     static void setLocalZone();
0119     static void cleanup();
0120     static void readConfig(bool init);
0121 #ifdef Q_OS_WIN
0122     static void updateTimezoneInformation()
0123     {
0124         instance()->updateTimezoneInformation(true);
0125     }
0126 #else
0127     static void updateZonetab()
0128     {
0129         instance()->readZoneTab(true);
0130     }
0131 #endif
0132 
0133     static KTimeZone m_localZone;
0134     static QString m_localZoneName;
0135     static QString m_zoneinfoDir;
0136     static QString m_zonetab;
0137     static KSystemTimeZoneSource *m_source;
0138     static bool m_ktimezonedError;
0139 
0140 private:
0141     KSystemTimeZonesPrivate() {}
0142 #ifdef Q_OS_WIN
0143     void updateTimezoneInformation(bool update);
0144 #else
0145     void readZoneTab(bool update);
0146     static float convertCoordinate(const QString &coordinate);
0147 #endif
0148 
0149     static KSystemTimeZones *m_parent;
0150     static KSystemTimeZonesPrivate *m_instance;
0151     static KTzfileTimeZoneSource *m_tzfileSource;
0152 };
0153 
0154 KTimeZone                KSystemTimeZonesPrivate::m_localZone;
0155 QString                  KSystemTimeZonesPrivate::m_localZoneName;
0156 QString                  KSystemTimeZonesPrivate::m_zoneinfoDir;
0157 QString                  KSystemTimeZonesPrivate::m_zonetab;
0158 KSystemTimeZoneSource   *KSystemTimeZonesPrivate::m_source = nullptr;
0159 bool                     KSystemTimeZonesPrivate::m_ktimezonedError = true;
0160 KTzfileTimeZoneSource   *KSystemTimeZonesPrivate::m_tzfileSource = nullptr;
0161 KSystemTimeZones        *KSystemTimeZonesPrivate::m_parent = nullptr;
0162 KSystemTimeZonesPrivate *KSystemTimeZonesPrivate::m_instance = nullptr;
0163 
0164 KTzfileTimeZoneSource *KSystemTimeZonesPrivate::tzfileSource()
0165 {
0166     if (!m_tzfileSource) {
0167         instance();
0168         m_tzfileSource = new KTzfileTimeZoneSource(m_zoneinfoDir);
0169     }
0170     return m_tzfileSource;
0171 }
0172 
0173 // for the benefit of KTimeZoneTest
0174 KDELIBS4SUPPORT_EXPORT void k_system_time_zone_private_reset_config()
0175 {
0176     // Remove any old zones from the collection
0177     const KTimeZones::ZoneMap oldZones = KSystemTimeZonesPrivate::instance()->zones();
0178     for (KTimeZones::ZoneMap::ConstIterator it = oldZones.constBegin();  it != oldZones.constEnd();  ++it) {
0179         KSystemTimeZonesPrivate::instance()->remove(it.value());
0180     }
0181 
0182     // Read new config file
0183     KSystemTimeZonesPrivate::readConfig(false);
0184 }
0185 
0186 #ifndef NDEBUG
0187 Q_GLOBAL_STATIC(KTimeZone, simulatedLocalZone)
0188 #endif
0189 
0190 KSystemTimeZones::KSystemTimeZones()
0191     : d(nullptr)
0192 {
0193     QDBusConnection dbus = QDBusConnection::sessionBus();
0194     const QString dbusIface = QString::fromLatin1(KTIMEZONED_DBUS_IFACE);
0195     dbus.connect(QString(), QString(), dbusIface, QLatin1String("timeZoneChanged"), this, SLOT(configChanged()));
0196     dbus.connect(QString(), QString(), dbusIface, QLatin1String("timeZoneDatabaseUpdated"), this, SLOT(zonetabChanged(QString)));
0197     // No need to connect to definitionChanged() - see comments in zoneDefinitionChanged()
0198     //dbus.connect(QString(), QString(), dbusIface, QLatin1String("definitionChanged"), this, SLOT(zoneDefinitionChanged(QString)));
0199 }
0200 
0201 KSystemTimeZones::~KSystemTimeZones()
0202 {
0203 }
0204 
0205 KTimeZone KSystemTimeZones::local()
0206 {
0207 #ifndef NDEBUG
0208     if (simulatedLocalZone()->isValid()) {
0209         return *simulatedLocalZone();
0210     }
0211 #endif
0212     KSystemTimeZonesPrivate::instance();
0213     return KSystemTimeZonesPrivate::m_localZone;
0214 }
0215 
0216 KTimeZone KSystemTimeZones::realLocalZone()
0217 {
0218     KSystemTimeZonesPrivate::instance();
0219     return KSystemTimeZonesPrivate::m_localZone;
0220 }
0221 
0222 void KSystemTimeZones::setLocalZone(const KTimeZone &tz)
0223 {
0224     Q_UNUSED(tz);
0225 #ifndef NDEBUG
0226     *simulatedLocalZone() = tz;
0227 #endif
0228 }
0229 
0230 bool KSystemTimeZones::isSimulated()
0231 {
0232 #ifndef NDEBUG
0233     return simulatedLocalZone()->isValid();
0234 #else
0235     return false;
0236 #endif
0237 }
0238 
0239 QString KSystemTimeZones::zoneinfoDir()
0240 {
0241     KSystemTimeZonesPrivate::instance();
0242     return KSystemTimeZonesPrivate::m_zoneinfoDir;
0243 }
0244 
0245 bool KSystemTimeZones::isTimeZoneDaemonAvailable()
0246 {
0247     KSystemTimeZonesPrivate::instance();
0248     return !KSystemTimeZonesPrivate::m_ktimezonedError;
0249 }
0250 
0251 KTimeZones *KSystemTimeZones::timeZones()
0252 {
0253     return KSystemTimeZonesPrivate::instance();
0254 }
0255 
0256 KTimeZone KSystemTimeZones::readZone(const QString &name)
0257 {
0258     return KTzfileTimeZone(KSystemTimeZonesPrivate::tzfileSource(), name);
0259 }
0260 
0261 const KTimeZones::ZoneMap KSystemTimeZones::zones()
0262 {
0263     return KSystemTimeZonesPrivate::instance()->zones();
0264 }
0265 
0266 KTimeZone KSystemTimeZones::zone(const QString &name)
0267 {
0268     return KSystemTimeZonesPrivate::instance()->zone(name);
0269 }
0270 
0271 void KSystemTimeZones::configChanged()
0272 {
0273     //qDebug() << "KSystemTimeZones::configChanged()";
0274     KSystemTimeZonesPrivate::m_ktimezonedError = false;
0275     KSystemTimeZonesPrivate::readConfig(false);
0276 }
0277 
0278 void KSystemTimeZones::zonetabChanged(const QString &zonetab)
0279 {
0280     Q_UNUSED(zonetab)
0281 #ifndef Q_OS_WIN
0282     //qDebug() << "KSystemTimeZones::zonetabChanged()";
0283     KSystemTimeZonesPrivate::m_ktimezonedError = false;
0284     // Re-read zone.tab and update our collection, removing any deleted
0285     // zones and adding any new zones.
0286     KSystemTimeZonesPrivate::updateZonetab();
0287 #endif
0288 }
0289 
0290 void KSystemTimeZones::zoneDefinitionChanged(const QString &zone)
0291 {
0292     // No need to do anything when the definition (as opposed to the
0293     // identity) of the local zone changes, since the updated details
0294     // will always be accessed by the system library calls to fetch
0295     // local zone information.
0296     Q_UNUSED(zone)
0297     KSystemTimeZonesPrivate::m_ktimezonedError = false;
0298 }
0299 
0300 // Perform initialization, create the unique KSystemTimeZones instance,
0301 // whose only function is to receive D-Bus signals from KTimeZoned,
0302 // and create the unique KSystemTimeZonesPrivate instance.
0303 KSystemTimeZonesPrivate *KSystemTimeZonesPrivate::instance()
0304 {
0305     if (!m_instance) {
0306         m_instance = new KSystemTimeZonesPrivate;
0307 
0308         // A KSystemTimeZones instance is required only to catch D-Bus signals.
0309         m_parent = new KSystemTimeZones;
0310         // Ensure that the KDED time zones module has initialized. The call loads the module on demand.
0311         QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
0312         if (!bus->isServiceRegistered(QLatin1String("org.kde.kded5"))) {
0313             // kded isn't even running: start it
0314             QDBusReply<void> reply = bus->startService(QLatin1String("org.kde.kded5"));
0315             if (!reply.isValid()) {
0316                 qWarning() << "Couldn't start kded5 from org.kde.kded5.service:" << reply.error();
0317             }
0318         }
0319         const QString dbusIface = QString::fromLatin1(KTIMEZONED_DBUS_IFACE);
0320         QDBusInterface *ktimezoned = new QDBusInterface(QLatin1String("org.kde.kded5"), QLatin1String("/modules/ktimezoned"), dbusIface);
0321         QDBusReply<void> reply = ktimezoned->call(QLatin1String("initialize"), false);
0322         m_ktimezonedError = !reply.isValid();
0323         if (m_ktimezonedError) {
0324             qCritical() << "KSystemTimeZones: ktimezoned initialize() D-Bus call failed: " << reply.error().message() << endl;
0325         }
0326 //qDebug()<<"instance(): ... initialised";
0327         delete ktimezoned;
0328 
0329         // Read the time zone config written by ktimezoned
0330         readConfig(true);
0331 
0332         // Go read the database.
0333 #ifdef Q_OS_WIN
0334         // On Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
0335         // is the place to look. The TZI binary value is the TIME_ZONE_INFORMATION structure.
0336         m_instance->updateTimezoneInformation(false);
0337 #else
0338         // For Unix, read zone.tab.
0339         if (!m_zonetab.isEmpty()) {
0340             m_instance->readZoneTab(false);
0341         }
0342 #endif
0343         setLocalZone();
0344 
0345         qAddPostRoutine(KSystemTimeZonesPrivate::cleanup);
0346     }
0347     return m_instance;
0348 }
0349 
0350 void KSystemTimeZonesPrivate::readConfig(bool init)
0351 {
0352     KConfig config(QLatin1String("ktimezonedrc"));
0353     if (!init) {
0354         config.reparseConfiguration();
0355     }
0356     KConfigGroup group(&config, "TimeZones");
0357     if (!group.exists()) {
0358         qCritical() << "No time zone information obtained from ktimezoned";
0359         m_ktimezonedError = true;
0360     }
0361     m_zoneinfoDir   = group.readEntry("ZoneinfoDir");
0362     m_zonetab       = group.readEntry("Zonetab");
0363     m_localZoneName = group.readEntry("LocalZone");
0364     if (m_zoneinfoDir.length() > 1 && m_zoneinfoDir.endsWith(QLatin1Char('/'))) {
0365         m_zoneinfoDir.truncate(m_zoneinfoDir.length() - 1);    // strip trailing '/'
0366     }
0367     if (!init) {
0368 #ifdef Q_OS_WIN
0369         updateTimezoneInformation();
0370 #else
0371         updateZonetab();
0372 #endif
0373         setLocalZone();
0374     }
0375     //qDebug() << "readConfig(): local zone=" << m_localZoneName;
0376 }
0377 
0378 void KSystemTimeZonesPrivate::setLocalZone()
0379 {
0380     // Check whether the local zone name is set at all
0381     if (m_localZoneName.isEmpty()) {
0382         m_localZone = KTimeZone::utc();
0383         return;
0384     }
0385 
0386     // Check if the zone name is a known zone
0387     if (m_instance) {
0388         m_localZone = m_instance->zone(m_localZoneName);
0389         if (m_localZone.isValid()) {
0390             return;
0391         }
0392     }
0393 
0394     QString filename;
0395     if (m_localZoneName.startsWith(QLatin1Char('/'))) {
0396         // The time zone is specified by a file outside the zoneinfo directory
0397         filename = m_localZoneName;
0398     } else {
0399         // The zone name is specified by a relative file name in zoneinfo directory.
0400         filename = m_zoneinfoDir + QLatin1Char('/') + m_localZoneName;
0401     }
0402 
0403     // Verify that the time zone file actually exists
0404     if (!QFile::exists(filename)) {
0405         m_localZone = KTimeZone::utc();
0406         return;
0407     }
0408 
0409     // Parse the specified time zone data file
0410     QString zonename = filename;
0411     if (zonename.startsWith(m_zoneinfoDir + QLatin1Char('/'))) {
0412         zonename = zonename.mid(m_zoneinfoDir.length() + 1);
0413     }
0414     m_localZone = KTzfileTimeZone(KSystemTimeZonesPrivate::tzfileSource(), zonename);
0415 
0416     // Add the new time zone to the list of known time zones
0417     if (m_instance) {
0418         const KTimeZone oldzone = m_instance->zone(zonename);
0419         if (!oldzone.isValid() || oldzone.type() != "KTzfileTimeZone") {
0420             m_instance->remove(oldzone);
0421             m_instance->add(m_localZone);
0422         }
0423     }
0424 }
0425 
0426 void KSystemTimeZonesPrivate::cleanup()
0427 {
0428     delete m_parent;
0429     delete m_instance;
0430     delete m_source;
0431     delete m_tzfileSource;
0432 }
0433 
0434 #ifdef Q_OS_WIN
0435 
0436 void KSystemTimeZonesPrivate::updateTimezoneInformation(bool update)
0437 {
0438     if (!m_source) {
0439         m_source = new KSystemTimeZoneSourceWindows;
0440     }
0441     QStringList newZones;
0442     Q_FOREACH (const QString &tz, KSystemTimeZoneWindows::listTimeZones()) {
0443         // const std::wstring wstr = tz.toStdWString();
0444         // const KTimeZone info = make_time_zone( wstr.c_str() );
0445         KSystemTimeZoneWindows stz(m_source, tz);
0446         if (update) {
0447             // Update the existing collection with the new zone definition
0448             newZones += stz.name();
0449             KTimeZone oldTz = zone(stz.name());
0450             if (oldTz.isValid()) {
0451                 oldTz.updateBase(stz);    // the zone previously existed, so update its definition
0452             } else {
0453                 add(stz);    // the zone didn't previously exist, so add it
0454             }
0455         } else {
0456             add(stz);
0457         }
0458     }
0459     if (update) {
0460         // Remove any zones from the collection which no longer exist
0461         const ZoneMap oldZones = zones();
0462         for (ZoneMap::const_iterator it = oldZones.begin();  it != oldZones.end();  ++it) {
0463             if (newZones.indexOf(it.key()) < 0) {
0464                 remove(it.value());
0465             }
0466         }
0467     }
0468 }
0469 
0470 #else
0471 /*
0472  * Find the location of the zoneinfo files and store in mZoneinfoDir.
0473  * Parse zone.tab and for each time zone, create a KSystemTimeZone instance.
0474  */
0475 void KSystemTimeZonesPrivate::readZoneTab(bool update)
0476 {
0477     //qDebug() << "readZoneTab(" << m_zonetab<< ")";
0478     QStringList newZones;
0479     QFile f;
0480     f.setFileName(m_zonetab);
0481     if (!f.open(QIODevice::ReadOnly)) {
0482         return;
0483     }
0484     QTextStream str(&f);
0485     const QRegExp lineSeparator(QLatin1String("[ \t]"));
0486     const QRegExp ordinateSeparator(QLatin1String("[+-]"));
0487     if (!m_source) {
0488         m_source = new KSystemTimeZoneSource;
0489     }
0490     while (!str.atEnd()) {
0491         const QString line = str.readLine();
0492         if (line.isEmpty() || line[0] == QLatin1Char('#')) {
0493             continue;
0494         }
0495         QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
0496         const int n = tokens.count();
0497         if (n < 3) {
0498             qCritical() << "readZoneTab(): invalid record: " << line << endl;
0499             continue;
0500         }
0501 
0502         // Got three tokens. Now check for two ordinates plus first one is "".
0503         const int i = tokens[1].indexOf(ordinateSeparator, 1);
0504         if (i < 0) {
0505             qCritical() << "readZoneTab() " << tokens[2] << ": invalid coordinates: " << tokens[1] << endl;
0506             continue;
0507         }
0508 
0509         const float latitude = convertCoordinate(tokens[1].left(i));
0510         const float longitude = convertCoordinate(tokens[1].mid(i));
0511 
0512         // Add entry to list.
0513         if (tokens[0] == QLatin1String("??")) {
0514             tokens[0] = QString::fromLatin1("");
0515         }
0516         // Solaris sets the empty Comments field to '-', making it not empty.
0517         // Clean it up.
0518         if (n > 3  && tokens[3] == QLatin1String("-")) {
0519             tokens[3] = QString::fromLatin1("");
0520         }
0521         // Note: KTzfileTimeZone is used in preference to KSystemTimeZone because of
0522         //       the large overhead incurred by tzset() - see KSystemTimeZones class
0523         //       description for details.
0524         const KTzfileTimeZone tz(tzfileSource(), tokens[2], tokens[0], latitude, longitude, (n > 3 ? tokens[3] : QString()));
0525         if (update) {
0526             // Update the existing collection with the new zone definition
0527             newZones += tz.name();
0528             KTimeZone oldTz = zone(tz.name());
0529             if (oldTz.isValid()) {
0530                 oldTz.updateBase(tz);    // the zone previously existed, so update its definition
0531             } else {
0532                 add(tz);    // the zone didn't previously exist, so add it
0533             }
0534         } else {
0535             add(tz);
0536         }
0537     }
0538     f.close();
0539 
0540     if (update) {
0541         // Remove any zones from the collection which no longer exist
0542         const ZoneMap oldZones = zones();
0543         for (ZoneMap::ConstIterator it = oldZones.constBegin();  it != oldZones.constEnd();  ++it) {
0544             if (newZones.indexOf(it.key()) < 0) {
0545                 remove(it.value());
0546             }
0547         }
0548     }
0549 }
0550 
0551 /**
0552  * Convert sHHMM or sHHMMSS to a floating point number of degrees.
0553  */
0554 float KSystemTimeZonesPrivate::convertCoordinate(const QString &coordinate)
0555 {
0556     int value = coordinate.toInt();
0557     int degrees = 0;
0558     int minutes = 0;
0559     int seconds = 0;
0560 
0561     if (coordinate.length() > 6) {
0562         degrees = value / 10000;
0563         value -= degrees * 10000;
0564         minutes = value / 100;
0565         value -= minutes * 100;
0566         seconds = value;
0567     } else {
0568         degrees = value / 100;
0569         value -= degrees * 100;
0570         minutes = value;
0571     }
0572     value = degrees * 3600 + minutes * 60 + seconds;
0573     return value / 3600.0;
0574 }
0575 #endif
0576 
0577 /******************************************************************************/
0578 
0579 KSystemTimeZoneBackend::KSystemTimeZoneBackend(KSystemTimeZoneSource *source, const QString &name,
0580         const QString &countryCode, float latitude, float longitude, const QString &comment)
0581     : KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment)
0582 {}
0583 
0584 KSystemTimeZoneBackend::~KSystemTimeZoneBackend()
0585 {}
0586 
0587 KTimeZoneBackend *KSystemTimeZoneBackend::clone() const
0588 {
0589     return new KSystemTimeZoneBackend(*this);
0590 }
0591 
0592 QByteArray KSystemTimeZoneBackend::type() const
0593 {
0594     return "KSystemTimeZone";
0595 }
0596 
0597 int KSystemTimeZoneBackend::offsetAtZoneTime(const KTimeZone *caller, const QDateTime &zoneDateTime, int *secondOffset) const
0598 {
0599     if (!caller->isValid()  ||  !zoneDateTime.isValid()  ||  zoneDateTime.timeSpec() != Qt::LocalTime) {
0600         return 0;
0601     }
0602     // Make this time zone the current local time zone
0603     const QByteArray originalZone = qgetenv("TZ");   // save the original local time zone
0604     QByteArray tz = caller->name().toUtf8();
0605     tz.prepend(":");
0606     const bool change = (tz != originalZone);
0607     if (change) {
0608         qputenv("TZ", tz);
0609         ::tzset();
0610     }
0611 
0612     // Convert zone time to UTC, and then get the offset to UTC
0613     tm tmtime;
0614     tmtime.tm_sec    = zoneDateTime.time().second();
0615     tmtime.tm_min    = zoneDateTime.time().minute();
0616     tmtime.tm_hour   = zoneDateTime.time().hour();
0617     tmtime.tm_mday   = zoneDateTime.date().day();
0618     tmtime.tm_mon    = zoneDateTime.date().month() - 1;
0619     tmtime.tm_year   = zoneDateTime.date().year() - 1900;
0620     tmtime.tm_isdst  = -1;
0621     const time_t t = mktime(&tmtime);
0622     int offset1 = (t == (time_t) - 1) ? KTimeZone::InvalidOffset : gmtoff(t);
0623     if (secondOffset) {
0624         int offset2 = offset1;
0625         if (t != (time_t) - 1) {
0626             // Check if there is a backward DST change near to this time, by
0627             // checking if the UTC offset is different 1 hour later or earlier.
0628             // ASSUMPTION: DST SHIFTS ARE NEVER GREATER THAN 1 HOUR.
0629             const int maxShift = 3600;
0630             offset2 = gmtoff(t + maxShift);
0631             if (offset2 < offset1) {
0632                 // There is a backward DST shift during the following hour
0633                 if (offset1 - offset2 < maxShift) {
0634                     offset2 = gmtoff(t + (offset1 - offset2));
0635                 }
0636             } else if ((offset2 = gmtoff(t - maxShift)) > offset1) {
0637                 // There is a backward DST shift during the previous hour
0638                 if (offset2 - offset1 < maxShift) {
0639                     offset2 = gmtoff(t - (offset2 - offset1));
0640                 }
0641                 // Put UTC offsets into the correct order
0642                 const int o = offset1;
0643                 offset1 = offset2;
0644                 offset2 = o;
0645             } else {
0646                 offset2 = offset1;
0647             }
0648         }
0649         *secondOffset = offset2;
0650     }
0651 
0652     if (change) {
0653         // Restore the original local time zone
0654         if (originalZone.isEmpty()) {
0655             ::unsetenv("TZ");
0656         } else {
0657             qputenv("TZ", originalZone);
0658         }
0659         ::tzset();
0660     }
0661     return offset1;
0662 }
0663 
0664 int KSystemTimeZoneBackend::offsetAtUtc(const KTimeZone *caller, const QDateTime &utcDateTime) const
0665 {
0666     return offset(caller, KTimeZone::toTime_t(utcDateTime));
0667 }
0668 
0669 int KSystemTimeZoneBackend::offset(const KTimeZone *caller, time_t t) const
0670 {
0671     if (!caller->isValid()  ||  t == KTimeZone::InvalidTime_t) {
0672         return 0;
0673     }
0674 
0675     // Make this time zone the current local time zone
0676     const QByteArray originalZone = qgetenv("TZ");   // save the original local time zone
0677     QByteArray tz = caller->name().toUtf8();
0678     tz.prepend(":");
0679     const bool change = (tz != originalZone);
0680     if (change) {
0681         qputenv("TZ", tz);
0682         ::tzset();
0683     }
0684 
0685     const int secs = gmtoff(t);
0686 
0687     if (change) {
0688         // Restore the original local time zone
0689         if (originalZone.isEmpty()) {
0690             ::unsetenv("TZ");
0691         } else {
0692             qputenv("TZ", originalZone);
0693         }
0694         ::tzset();
0695     }
0696     return secs;
0697 }
0698 
0699 bool KSystemTimeZoneBackend::isDstAtUtc(const KTimeZone *caller, const QDateTime &utcDateTime) const
0700 {
0701     return isDst(caller, KTimeZone::toTime_t(utcDateTime));
0702 }
0703 
0704 bool KSystemTimeZoneBackend::isDst(const KTimeZone *caller, time_t t) const
0705 {
0706     Q_UNUSED(caller)
0707     if (t != (time_t) - 1) {
0708 #ifdef _POSIX_THREAD_SAFE_FUNCTIONS
0709         tm tmtime;
0710         if (localtime_r(&t, &tmtime)) {
0711             return tmtime.tm_isdst > 0;
0712         }
0713 #else
0714         const tm *tmtime = localtime(&t);
0715         if (tmtime) {
0716             return tmtime->tm_isdst > 0;
0717         }
0718 #endif
0719     }
0720     return false;
0721 }
0722 
0723 /******************************************************************************/
0724 
0725 KSystemTimeZone::KSystemTimeZone(KSystemTimeZoneSource *source, const QString &name,
0726                                  const QString &countryCode, float latitude, float longitude, const QString &comment)
0727     : KTimeZone(new KSystemTimeZoneBackend(source, name, countryCode, latitude, longitude, comment))
0728 {
0729 }
0730 
0731 KSystemTimeZone::~KSystemTimeZone()
0732 {
0733 }
0734 
0735 /******************************************************************************/
0736 
0737 class KSystemTimeZoneDataPrivate
0738 {
0739 public:
0740     QByteArray TZ;
0741     QList<QByteArray> abbreviations;
0742 };
0743 
0744 // N.B. KSystemTimeZoneSourcePrivate is also used by KSystemTimeZoneData
0745 class KSystemTimeZoneSourcePrivate
0746 {
0747 public:
0748     static void setTZ(const QByteArray &zoneName);
0749     static void restoreTZ();
0750     static QByteArray savedTZ;       // temporary value of TZ environment variable saved by setTZ()
0751     static QByteArray originalTZ;    // saved value of TZ environment variable during multiple parse() calls
0752     static bool       TZIsSaved;     // TZ has been saved in savedTZ
0753     static bool       multiParse;    // true if performing multiple parse() calls
0754 };
0755 
0756 QByteArray KSystemTimeZoneSourcePrivate::savedTZ;
0757 QByteArray KSystemTimeZoneSourcePrivate::originalTZ;
0758 bool       KSystemTimeZoneSourcePrivate::TZIsSaved = false;
0759 bool       KSystemTimeZoneSourcePrivate::multiParse = false;
0760 
0761 KSystemTimeZoneSource::KSystemTimeZoneSource()
0762     : d(nullptr)
0763 //  : d(new KSystemTimeZoneSourcePrivate)
0764 {
0765 }
0766 
0767 KSystemTimeZoneSource::~KSystemTimeZoneSource()
0768 {
0769 //    delete d;
0770 }
0771 
0772 KTimeZoneData *KSystemTimeZoneSource::parse(const KTimeZone &zone) const
0773 {
0774     const QByteArray tz = zone.name().toUtf8();
0775     KSystemTimeZoneSourcePrivate::setTZ(tz);   // make this time zone the current local time zone
0776 
0777     tzset();    // initialize the tzname array
0778     KSystemTimeZoneData *data = new KSystemTimeZoneData;
0779     data->d->TZ = tz;
0780     data->d->abbreviations.append(tzname[0]);
0781     data->d->abbreviations.append(tzname[1]);
0782 
0783     // There is no easy means to access the sequence of daylight savings time
0784     // changes, or leap seconds adjustments, so leave that data empty.
0785 
0786     KSystemTimeZoneSourcePrivate::restoreTZ();   // restore the original local time zone if necessary
0787     return data;
0788 }
0789 
0790 void KSystemTimeZoneSource::startParseBlock()
0791 {
0792     KSystemTimeZoneSourcePrivate::originalTZ = qgetenv("TZ");   // save the original local time zone
0793     KSystemTimeZoneSourcePrivate::multiParse = true;
0794 }
0795 
0796 void KSystemTimeZoneSource::endParseBlock()
0797 {
0798     if (KSystemTimeZoneSourcePrivate::multiParse) {
0799         // Restore the original local time zone
0800         if (KSystemTimeZoneSourcePrivate::originalTZ.isEmpty()) {
0801             ::unsetenv("TZ");
0802         } else {
0803             qputenv("TZ", KSystemTimeZoneSourcePrivate::originalTZ);
0804         }
0805         ::tzset();
0806         KSystemTimeZoneSourcePrivate::multiParse = false;
0807     }
0808 }
0809 
0810 // Set the TZ environment variable to the specified time zone,
0811 // saving its current setting first if necessary.
0812 void KSystemTimeZoneSourcePrivate::setTZ(const QByteArray &zoneName)
0813 {
0814     QByteArray tz = zoneName;
0815     tz.prepend(":");
0816     bool setTZ = multiParse;
0817     if (!setTZ) {
0818         savedTZ = qgetenv("TZ");   // save the original local time zone
0819         TZIsSaved = true;
0820         setTZ = (tz != savedTZ);
0821     }
0822     if (setTZ) {
0823         qputenv("TZ", tz);
0824         ::tzset();
0825     }
0826 }
0827 
0828 // Restore the TZ environment variable if it was saved by setTz()
0829 void KSystemTimeZoneSourcePrivate::restoreTZ()
0830 {
0831     if (TZIsSaved) {
0832         if (savedTZ.isEmpty()) {
0833             ::unsetenv("TZ");
0834         } else {
0835             qputenv("TZ", savedTZ);
0836         }
0837         ::tzset();
0838         TZIsSaved = false;
0839     }
0840 }
0841 
0842 /******************************************************************************/
0843 
0844 KSystemTimeZoneData::KSystemTimeZoneData()
0845     : d(new KSystemTimeZoneDataPrivate)
0846 { }
0847 
0848 KSystemTimeZoneData::KSystemTimeZoneData(const KSystemTimeZoneData &rhs)
0849     : KTimeZoneData(),
0850       d(new KSystemTimeZoneDataPrivate)
0851 {
0852     operator=(rhs);
0853 }
0854 
0855 KSystemTimeZoneData::~KSystemTimeZoneData()
0856 {
0857     delete d;
0858 }
0859 
0860 KSystemTimeZoneData &KSystemTimeZoneData::operator=(const KSystemTimeZoneData &rhs)
0861 {
0862     d->TZ = rhs.d->TZ;
0863     d->abbreviations = rhs.d->abbreviations;
0864     return *this;
0865 }
0866 
0867 KTimeZoneData *KSystemTimeZoneData::clone() const
0868 {
0869     return new KSystemTimeZoneData(*this);
0870 }
0871 
0872 QList<QByteArray> KSystemTimeZoneData::abbreviations() const
0873 {
0874     return d->abbreviations;
0875 }
0876 
0877 QByteArray KSystemTimeZoneData::abbreviation(const QDateTime &utcDateTime) const
0878 {
0879     QByteArray abbr;
0880     if (utcDateTime.timeSpec() != Qt::UTC) {
0881         return abbr;
0882     }
0883     time_t t = utcDateTime.toTime_t();
0884     if (t != KTimeZone::InvalidTime_t) {
0885         KSystemTimeZoneSourcePrivate::setTZ(d->TZ);   // make this time zone the current local time zone
0886 
0887         /* Use tm.tm_zone if available because it returns the abbreviation
0888          * in use at the time specified. Otherwise, use tzname[] which
0889          * returns the appropriate current abbreviation instead.
0890          */
0891 #ifdef _POSIX_THREAD_SAFE_FUNCTIONS
0892         tm tmtime;
0893         if (localtime_r(&t, &tmtime))
0894 #if HAVE_STRUCT_TM_TM_ZONE
0895             abbr = tmtime.tm_zone;
0896 #else
0897             abbr = tzname[(tmtime.tm_isdst > 0) ? 1 : 0];
0898 #endif
0899 #else
0900         const tm *tmtime = localtime(&t);
0901         if (tmtime)
0902 #if HAVE_STRUCT_TM_TM_ZONE
0903             abbr = tmtime->tm_zone;
0904 #else
0905             abbr = tzname[(tmtime->tm_isdst > 0) ? 1 : 0];
0906 #endif
0907 #endif
0908         KSystemTimeZoneSourcePrivate::restoreTZ();   // restore the original local time zone if necessary
0909     }
0910     return abbr;
0911 }
0912 
0913 QList<int> KSystemTimeZoneData::utcOffsets() const
0914 {
0915     return QList<int>();
0916 }
0917