File indexing completed on 2024-06-02 05:19:09

0001 //#define SIMULATION
0002 /*
0003  *  kadatetime.cpp  -  represents a date and optional time with a time zone
0004  *  This file is part of kalarmcalendar library, which provides access to KAlarm
0005  *  calendar data.
0006  *  It is the Qt5/Qt6 version of KDE 4 kdelibs/kdecore/date/kdatetime.cpp.
0007  *  Program:  kalarm
0008  *  SPDX-FileCopyrightText: 2005-2023 David Jarvie <djarvie@kde.org>
0009  *
0010  *  SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "kadatetime.h"
0014 
0015 #include <QDataStream>
0016 #include <QDebug>
0017 #include <QLocale>
0018 #include <QRegularExpression>
0019 #include <QSharedData>
0020 #include <QStringList>
0021 #include <QTimeZone>
0022 
0023 #include <limits>
0024 
0025 namespace
0026 {
0027 
0028 const int InvalidOffset = 0x80000000;   // invalid UTC offset
0029 const int NO_NUMBER = std::numeric_limits<int>::min();   // indicates that no number is present in string conversion functions
0030 
0031 QList<QString> shortDayNames;
0032 QList<QString> longDayNames;
0033 QList<QString> shortMonthNames;
0034 QList<QString> longMonthNames;
0035 
0036 // Short day name, in English
0037 const QString& shortDay(int day);   // Mon = 1, ...
0038 // Long day name, in English
0039 const QString& longDay(int day);   // Monday = 1, ...
0040 // Short month name, in English
0041 const QString& shortMonth(int month);   // Jan = 1, ...
0042 // Long month name, in English
0043 const QString& longMonth(int month);   // January = 1, ...
0044 
0045 QDateTime fromStr(const QString& string, const QString& format, int& utcOffset,
0046                   QString& zoneName, QString& zoneAbbrev, bool& dateOnly);
0047 int matchDay(const QString& string, int& offset, bool localised);
0048 int matchMonth(const QString& string, int& offset, bool localised);
0049 bool getUTCOffset(const QString& string, int& offset, bool colon, int& result);
0050 int getAmPm(const QString& string, int& offset, bool localised);
0051 bool getNumber(const QString& string, int& offset, int mindigits, int maxdigits, int minval, int maxval, int& result);
0052 using DayMonthName = const QString& (*)(int);
0053 int findString(const QString& string, DayMonthName func, int count, int& offset);
0054 // Return number as zero-padded numeric string.
0055 QString numString(int n, int width);
0056 
0057 int offsetAtZoneTime(const QTimeZone& tz, const QDateTime&, int* secondOffset = nullptr);
0058 QDateTime toZoneTime(const QTimeZone& tz, const QDateTime& utcDateTime, bool* secondOccurrence = nullptr);
0059 bool checkTzTransitionOccurrence(const QDateTime& dt, const QDateTime& utcDateTime);
0060 int  checkTzTransitionBackwards(QTimeZone::OffsetData& transition, const QTimeZone& tz, const QDateTime& utcDateTime, const QDateTime& tzDateTime = {});
0061 
0062 /** Return the Qt timespec for a QDateTime. If UTC, returns Qt::UTC.
0063  *  Note that QDateTime::timeSpec() returns QTimeZone for a UTC time (since Qt 6.6 approx). */
0064 inline Qt::TimeSpec qTimeSpec(const QDateTime& qdt)
0065 {
0066     Qt::TimeSpec spec = qdt.timeSpec();
0067     return (spec == Qt::TimeZone  &&  qdt.timeZone() == QTimeZone::utc()) ? Qt::UTC : spec;
0068 }
0069 
0070 } // namespace
0071 
0072 namespace KAlarmCal
0073 {
0074 
0075 #ifdef COMPILING_TESTS
0076 KALARMCAL_EXPORT int KADateTime_utcCacheHit  = 0;
0077 KALARMCAL_EXPORT int KADateTime_zoneCacheHit = 0;
0078 #endif
0079 
0080 /*----------------------------------------------------------------------------*/
0081 
0082 class KADateTimeSpecPrivate
0083 {
0084 public:
0085     // *** NOTE: This structure is replicated in KADateTimePrivate. Any changes must be copied there.
0086     QTimeZone tz;            // if type == TimeZone, the instance's time zone.
0087     int       utcOffset = 0;     // if type == OffsetFromUTC, the offset from UTC
0088     KADateTime::SpecType type;  // time spec type
0089 };
0090 
0091 KADateTime::Spec::Spec()
0092     : d(new KADateTimeSpecPrivate)
0093 {
0094     d->type = KADateTime::Invalid;
0095 }
0096 
0097 KADateTime::Spec::Spec(const QTimeZone& tz)
0098     : d(new KADateTimeSpecPrivate())
0099 {
0100     setType(tz);
0101 }
0102 
0103 KADateTime::Spec::Spec(SpecType type, int utcOffset)
0104     : d(new KADateTimeSpecPrivate())
0105 {
0106     setType(type, utcOffset);
0107 }
0108 
0109 KADateTime::Spec::Spec(const Spec& spec)
0110     : d(new KADateTimeSpecPrivate())
0111 {
0112     operator=(spec);
0113 }
0114 
0115 KADateTime::Spec::~Spec()
0116 {
0117     delete d;
0118 }
0119 
0120 KADateTime::Spec& KADateTime::Spec::operator=(const Spec& spec)
0121 {
0122     if (&spec != this)
0123     {
0124         d->type = spec.d->type;
0125         if (d->type == KADateTime::TimeZone)
0126             d->tz = spec.d->tz;
0127         else if (d->type == KADateTime::OffsetFromUTC)
0128             d->utcOffset = spec.d->utcOffset;
0129     }
0130     return *this;
0131 }
0132 
0133 void KADateTime::Spec::setType(SpecType type, int utcOffset)
0134 {
0135     switch (type)
0136     {
0137         case KADateTime::OffsetFromUTC:
0138             d->utcOffset = utcOffset;
0139             [[fallthrough]]; // fall through to UTC
0140         case KADateTime::UTC:
0141             d->type = type;
0142             break;
0143         case KADateTime::LocalZone:
0144             d->tz = QTimeZone::systemTimeZone();
0145             d->type = type;
0146             break;
0147         case KADateTime::TimeZone:
0148         default:
0149             d->type = KADateTime::Invalid;
0150             break;
0151     }
0152 }
0153 
0154 void KADateTime::Spec::setType(const QTimeZone& tz)
0155 {
0156     if (tz == QTimeZone::utc())
0157         d->type = KADateTime::UTC;
0158     else if (tz.isValid())
0159     {
0160         d->type = KADateTime::TimeZone;
0161         d->tz   = tz;
0162     }
0163     else
0164         d->type = KADateTime::Invalid;
0165 }
0166 
0167 QTimeZone KADateTime::Spec::timeZone() const
0168 {
0169     switch (d->type)
0170     {
0171         case KADateTime::TimeZone:
0172             return d->tz;
0173         case KADateTime::UTC:
0174             return QTimeZone::utc();
0175         case KADateTime::LocalZone:
0176             return QTimeZone::systemTimeZone();
0177         default:
0178             return {};
0179     }
0180 }
0181 
0182 bool KADateTime::Spec::isUtc() const
0183 {
0184     if (d->type == KADateTime::UTC || (d->type == KADateTime::OffsetFromUTC && d->utcOffset == 0))
0185         return true;
0186     return false;
0187 }
0188 
0189 KADateTime::Spec KADateTime::Spec::UTC() { return {KADateTime::UTC}; }
0190 KADateTime::Spec KADateTime::Spec::LocalZone()
0191 {
0192     return {KADateTime::LocalZone};
0193 }
0194 KADateTime::Spec KADateTime::Spec::OffsetFromUTC(int utcOffset)
0195 {
0196     return {KADateTime::OffsetFromUTC, utcOffset};
0197 }
0198 KADateTime::SpecType KADateTime::Spec::type() const
0199 {
0200     return d->type;
0201 }
0202 bool KADateTime::Spec::isValid() const
0203 {
0204     return d->type != KADateTime::Invalid;
0205 }
0206 bool KADateTime::Spec::isLocalZone() const
0207 {
0208     return d->type == KADateTime::LocalZone;
0209 }
0210 bool KADateTime::Spec::isOffsetFromUtc() const
0211 {
0212     return d->type == KADateTime::OffsetFromUTC;
0213 }
0214 int  KADateTime::Spec::utcOffset() const
0215 {
0216     return d->type == KADateTime::OffsetFromUTC ? d->utcOffset : 0;
0217 }
0218 
0219 bool KADateTime::Spec::operator==(const Spec& other) const
0220 {
0221     if (d->type != other.d->type || (d->type == KADateTime::TimeZone && d->tz != other.d->tz)
0222     ||  (d->type == KADateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset))
0223         return false;
0224     return true;
0225 }
0226 
0227 bool KADateTime::Spec::equivalentTo(const Spec& other) const
0228 {
0229     if (d->type == other.d->type)
0230     {
0231         if ((d->type == KADateTime::TimeZone && d->tz != other.d->tz)
0232         ||  (d->type == KADateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset))
0233             return false;
0234         return true;
0235     }
0236     else
0237     {
0238         if ((d->type == KADateTime::UTC && other.d->type == KADateTime::OffsetFromUTC && other.d->utcOffset == 0)
0239         ||  (other.d->type == KADateTime::UTC && d->type == KADateTime::OffsetFromUTC && d->utcOffset == 0))
0240             return true;
0241         const QTimeZone local = QTimeZone::systemTimeZone();
0242         if ((d->type == KADateTime::LocalZone && other.d->type == KADateTime::TimeZone && other.d->tz == local)
0243         ||  (other.d->type == KADateTime::LocalZone && d->type == KADateTime::TimeZone && d->tz == local))
0244             return true;
0245         return false;
0246     }
0247 }
0248 
0249 QDataStream& operator<<(QDataStream& s, const KADateTime::Spec& spec)
0250 {
0251     // The specification type is encoded in order to insulate from changes
0252     // to the SpecType enum.
0253     switch (spec.type())
0254     {
0255         case KADateTime::UTC:
0256             s << static_cast<quint8>('u');
0257             break;
0258         case KADateTime::OffsetFromUTC:
0259             s << static_cast<quint8>('o') << spec.utcOffset();
0260             break;
0261         case KADateTime::TimeZone:
0262             s << static_cast<quint8>('z') << (spec.timeZone().isValid() ? spec.timeZone().id() : QByteArray());
0263             break;
0264         case KADateTime::LocalZone:
0265             s << static_cast<quint8>('c');
0266             break;
0267         case KADateTime::Invalid:
0268         default:
0269             s << static_cast<quint8>(' ');
0270             break;
0271     }
0272     return s;
0273 }
0274 
0275 QDataStream& operator>>(QDataStream& s, KADateTime::Spec& spec)
0276 {
0277     // The specification type is encoded in order to insulate from changes
0278     // to the SpecType enum.
0279     quint8 t;
0280     s >> t;
0281     switch (static_cast<char>(t))
0282     {
0283         case 'u':
0284             spec.setType(KADateTime::UTC);
0285             break;
0286         case 'o':
0287         {
0288             int utcOffset;
0289             s >> utcOffset;
0290             spec.setType(KADateTime::OffsetFromUTC, utcOffset);
0291             break;
0292         }
0293         case 'z':
0294         {
0295             QByteArray zone;
0296             s >> zone;
0297             spec.setType(QTimeZone(zone));
0298             break;
0299         }
0300         case 'c':
0301             spec.setType(KADateTime::LocalZone);
0302             break;
0303         default:
0304             spec.setType(KADateTime::Invalid);
0305             break;
0306     }
0307     return s;
0308 }
0309 
0310 /*----------------------------------------------------------------------------*/
0311 
0312 class KADateTimePrivate : public QSharedData
0313 {
0314 public:
0315     KADateTimePrivate()
0316         : QSharedData()
0317         , specType(KADateTime::Invalid)
0318         , utcCached(true)
0319         , convertedCached(false)
0320         , m2ndOccurrence(false)
0321         , mDateOnly(false)
0322     {}
0323 
0324     KADateTimePrivate(const QDate& d, const QTime& t, const KADateTime::Spec& s, bool donly = false)
0325         : QSharedData()
0326         , mDt(QDateTime(d, t, Qt::UTC))
0327         , specType(s.type())
0328         , utcCached(false)
0329         , convertedCached(false)
0330         , m2ndOccurrence(false)
0331         , mDateOnly(donly)
0332     {
0333         setDtSpec(s);
0334     }
0335 
0336     KADateTimePrivate(const QDateTime& d, const KADateTime::Spec& s, bool donly = false)
0337         : QSharedData()
0338         , mDt(d)
0339         , specType(s.type())
0340         , utcCached(false)
0341         , convertedCached(false)
0342         , m2ndOccurrence(false)
0343         , mDateOnly(donly)
0344     {
0345         setDtSpec(s);
0346         setDateTime(d);
0347     }
0348 
0349     explicit KADateTimePrivate(const QDateTime& d)
0350         : QSharedData()
0351         , mDt(d)
0352         , specType(KADateTime::Invalid)
0353         , utcCached(false)
0354         , convertedCached(false)
0355         , m2ndOccurrence(false)
0356         , mDateOnly(false)
0357     {
0358         switch (qTimeSpec(d))
0359         {
0360             case Qt::UTC:
0361                 specType = KADateTime::UTC;
0362                 return;
0363             case Qt::OffsetFromUTC:
0364                 specType = KADateTime::OffsetFromUTC;
0365                 return;
0366             case Qt::TimeZone:
0367                 specType = KADateTime::TimeZone;
0368                 break;
0369             case Qt::LocalTime:
0370                 specType = KADateTime::LocalZone;
0371                 mDt.setTimeZone(QTimeZone::systemTimeZone());
0372                 break;
0373         }
0374         // Evaluate m2ndOccurrence
0375         QTimeZone::OffsetData transition;
0376         int utcOffsetChange = checkTzTransitionBackwards(transition, mDt.timeZone(), utcDt(), mDt);
0377         if (utcOffsetChange < 0)
0378         {
0379             if (mDt.isDaylightTime() != d.isDaylightTime())
0380             {
0381                 if (d.isDaylightTime())
0382                 {
0383                     // d is DST but mDt isn't, i.e. mDt is an hour later than it should be
0384                     // (assuming transition offset is -1 hour). Add the transition offset
0385                     // to change mDt to DST at the correct time.
0386                     mDt = mDt.addSecs(utcOffsetChange);
0387                 }
0388                 else
0389                 {
0390                     // d is not DST but mDt is, i.e. mDt is an hour earlier than it should
0391                     // be (assuming transition offset is -1 hour). Subtract the transition
0392                     // offset to change mDt to non-DST at the correct time.
0393                     mDt = mDt.addSecs(-utcOffsetChange);
0394                 }
0395             }
0396             m2ndOccurrence = !d.isDaylightTime();
0397         }
0398     }
0399 
0400     KADateTimePrivate(const KADateTimePrivate& rhs) = default;
0401 
0402     ~KADateTimePrivate() = default;
0403     const QDateTime& rawDt() const
0404     {
0405         return mDt;
0406     }
0407     QDateTime dt() const
0408     {
0409         if (specType == KADateTime::LocalZone)
0410         {
0411             QDateTime dtl(mDt);
0412             dtl.setTimeSpec(Qt::LocalTime);
0413             return dtl;
0414         }
0415         return mDt;
0416     }
0417     QDateTime updatedDt(QTimeZone& local) const;
0418     const QDate date() const
0419     {
0420         return mDt.date();
0421     }
0422     const QTime time() const
0423     {
0424         return mDt.time();
0425     }
0426     KADateTime::Spec spec() const;
0427     QDateTime utcDt() const
0428     {
0429         if (specType == KADateTime::UTC)
0430             return mDt;
0431         if (!utcCached)
0432             setCachedUtc((specType == KADateTime::Invalid) ? QDateTime() : mDt.toUTC());
0433         return ut;
0434     }
0435     QDateTime cachedUtc() const
0436     {
0437         return (specType != KADateTime::Invalid) ? ut : QDateTime();
0438     }
0439     bool dateOnly() const
0440     {
0441         return mDateOnly;
0442     }
0443     bool secondOccurrence() const
0444     {
0445         return m2ndOccurrence;
0446     }
0447     // Set mDt and its time spec, without changing timeSpec.
0448     // Condition: 'dt' time spec must correspond to timeSpec.
0449     void setDtWithSpec(const QDateTime& dt)
0450     {
0451         mDt = dt;
0452         utcCached = convertedCached = false;
0453         setTzTransitionOccurrence();
0454     }
0455     // Set mDt and its time spec, without changing timeSpec.
0456     // Condition: 'dt' time spec must correspond to timeSpec.
0457     // 'utc' is the UTC equivalent of dt.
0458     void setDtWithSpec(const QDateTime& dt, const QDateTime& utc)
0459     {
0460         mDt = dt;
0461         setCachedUtc(utc);
0462         setTzTransitionOccurrence();
0463     }
0464 
0465     void setDtSpec(const KADateTime::Spec& s);
0466     void setDateTime(const QDateTime& d);
0467     void setDate(const QDate& d)
0468     {
0469         mDt.setDate(d);
0470         utcCached = convertedCached = false;
0471         setTzTransitionOccurrence(false);
0472     }
0473     void setTime(const QTime& t)
0474     {
0475         mDt.setTime(t);
0476         utcCached = convertedCached = mDateOnly = false;
0477         setTzTransitionOccurrence(false);
0478     }
0479     void setSpec(const KADateTime::Spec&);
0480     void setDateOnly(bool d);
0481     QTimeZone timeZone() const
0482     {
0483         return specType == KADateTime::TimeZone ? mDt.timeZone() : QTimeZone();
0484     }
0485     QTimeZone timeZoneOrLocal() const
0486     {
0487         return (specType == KADateTime::TimeZone) ? mDt.timeZone()
0488              : (specType == KADateTime::LocalZone) ? QTimeZone::systemTimeZone() : QTimeZone();
0489     }
0490     int  timeZoneOffset(QTimeZone& local) const;
0491     QDateTime toUtc(QTimeZone& local) const;
0492     QDateTime toZone(const QTimeZone& zone, QTimeZone& local) const;
0493     void newToZone(KADateTimePrivate* newd, const QTimeZone& zone, QTimeZone& local) const;
0494     void setTzTransitionOccurrence();
0495     bool setTzTransitionOccurrence(bool second);
0496     bool equalSpec(const KADateTimePrivate&) const;
0497     void clearCache()
0498     {
0499         utcCached = convertedCached = false;
0500     }
0501     void setCachedUtc(const QDateTime& dt) const
0502     {
0503         ut = dt;
0504         utcCached = true;
0505         convertedCached = false;
0506     }
0507 
0508     // Default time spec used by fromString()
0509     static KADateTime::Spec& fromStringDefault()
0510     {
0511         static KADateTime::Spec s_fromStringDefault(KADateTime::LocalZone);
0512         return s_fromStringDefault;
0513     }
0514 
0515     static QTime         sod;               // start of day (00:00:00)
0516 #ifdef SIMULATION
0517 #ifndef NDEBUG
0518     // For simulating the current time:
0519     static qint64        simulationOffset;    // offset to apply to current system time
0520     static QTimeZone     simulationLocalZone; // time zone to use
0521 #endif
0522 #endif
0523 
0524     /* Because some applications create thousands of instances of KADateTime, this
0525      * data structure is designed to minimize memory usage. Ensure that all small
0526      * members are kept together at the end!
0527      */
0528 private:
0529     // This contains the Qt time spec, including QTimeZone or UTC offset.
0530     // For specType = LocalZone, it is set to the system time zone used to calculate
0531     // the cached UTC time, instead of Qt::LocalTime which doesn't handle historical
0532     // daylight savings times.
0533     QDateTime             mDt;
0534 public:
0535     mutable QDateTime     ut;          // cached UTC equivalent of 'mDt'
0536 private:
0537     mutable QDateTime     converted;   // cached conversion to another time zone (if 'tz' is valid)
0538 public:
0539     KADateTime::SpecType  specType          : 4; // time spec type (N.B. need 3 bits + sign bit, since enums are signed on some platforms)
0540     mutable bool          utcCached         : 1; // true if 'ut' is valid
0541     mutable bool          convertedCached   : 1; // true if 'converted' is valid
0542     mutable bool          m2ndOccurrence    : 1; // this is the second occurrence of a time zone time
0543 private:
0544     bool                  mDateOnly         : 1; // true to ignore the time part
0545     mutable bool          converted2ndOccur : 1; // this is the second occurrence of 'converted' time
0546 };
0547 
0548 QTime KADateTimePrivate::sod(0, 0, 0);
0549 #ifdef SIMULATION
0550 #ifndef NDEBUG
0551 qint64    KADateTimePrivate::simulationOffset = 0;
0552 QTimeZone KADateTimePrivate::simulationLocalZone;
0553 #endif
0554 #endif
0555 
0556 KADateTime::Spec KADateTimePrivate::spec() const
0557 {
0558     switch (specType)
0559     {
0560         case KADateTime::TimeZone:
0561             return KADateTime::Spec(mDt.timeZone());
0562         case KADateTime::OffsetFromUTC:
0563             return {specType, mDt.offsetFromUtc()};
0564         default:
0565             return {specType};
0566     }
0567 }
0568 
0569 /******************************************************************************
0570 * Set mDt to the appropriate time spec for a given KADateTime::Spec.
0571 * Its date and time components are not changed.
0572 */
0573 void KADateTimePrivate::setDtSpec(const KADateTime::Spec& s)
0574 {
0575     switch (s.type())
0576     {
0577         case KADateTime::UTC:
0578             mDt.setTimeZone(QTimeZone::utc());
0579             break;
0580         case KADateTime::OffsetFromUTC:
0581             mDt.setOffsetFromUtc(s.utcOffset());
0582             break;
0583         case KADateTime::TimeZone:
0584             mDt.setTimeZone(s.timeZone());
0585             break;
0586         case KADateTime::LocalZone:
0587             mDt.setTimeZone(QTimeZone::systemTimeZone());
0588             break;
0589         case KADateTime::Invalid:
0590         default:
0591             return;
0592     }
0593     utcCached = convertedCached = m2ndOccurrence = false;
0594 
0595     // It's a time zone. If the date/time is one which repeats before and after
0596     // a DST -> standard time shift, ensure that it's set to the first occurrence.
0597     // (Note that QDateTime provides no option to choose which occurrence to set.)
0598     setTzTransitionOccurrence(false);
0599 }
0600 
0601 void KADateTimePrivate::setSpec(const KADateTime::Spec& other)
0602 {
0603     if (specType == other.type())
0604     {
0605         switch (specType)
0606         {
0607             case KADateTime::TimeZone:
0608             {
0609                 const QTimeZone tz = other.timeZone();
0610                 if (mDt.timeZone() != tz)
0611                 {
0612                     mDt.setTimeZone(tz);
0613                     utcCached = convertedCached = false;
0614                     setTzTransitionOccurrence(false);
0615                 }
0616                 return;
0617             }
0618             case KADateTime::OffsetFromUTC:
0619             {
0620                 int offset = other.utcOffset();
0621                 if (mDt.offsetFromUtc() == offset)
0622                     return;
0623                 mDt.setOffsetFromUtc(offset);
0624                 utcCached = convertedCached = false;
0625                 break;
0626             }
0627             default:
0628                 return;
0629         }
0630     }
0631     else
0632     {
0633         specType = other.type();
0634         setDtSpec(other);
0635         if (specType == KADateTime::Invalid)
0636         {
0637             ut = QDateTime();   // cache an invalid UTC value
0638             utcCached = true;
0639             convertedCached = m2ndOccurrence = false;
0640             return;
0641         }
0642     }
0643 }
0644 
0645 bool KADateTimePrivate::equalSpec(const KADateTimePrivate& other) const
0646 {
0647     if (specType != other.specType
0648     ||  (specType == KADateTime::TimeZone && mDt.timeZone() != other.mDt.timeZone())
0649     ||  (specType == KADateTime::OffsetFromUTC && mDt.offsetFromUtc() != other.mDt.offsetFromUtc()))
0650         return false;
0651     return true;
0652 }
0653 
0654 /******************************************************************************
0655 * Return mDt, updated to current system time zone if it's LocalZone.
0656 * Parameters:
0657 *   local - the local time zone if already known (if invalid, this function will
0658 *           fetch it if required)
0659 */
0660 QDateTime KADateTimePrivate::updatedDt(QTimeZone& local) const
0661 {
0662     if (specType == KADateTime::LocalZone)
0663     {
0664         local = QTimeZone::systemTimeZone();
0665         if (mDt.timeZone() != local)
0666         {
0667             const_cast<QDateTime*>(&mDt)->setTimeZone(local);
0668             utcCached = convertedCached = false;
0669         }
0670     }
0671     return mDt;
0672 }
0673 
0674 /******************************************************************************
0675 * Set the date/time without changing the time spec.
0676 * 'd' is converted to the current time spec.
0677 */
0678 void KADateTimePrivate::setDateTime(const QDateTime& d)
0679 {
0680     switch (qTimeSpec(d))
0681     {
0682         case Qt::UTC:
0683             switch (specType)
0684             {
0685                 case KADateTime::UTC:
0686                     setDtWithSpec(d);
0687                     break;
0688                 case KADateTime::OffsetFromUTC:
0689                     setDtWithSpec(d.toOffsetFromUtc(mDt.offsetFromUtc()), d);
0690                     break;
0691                 case KADateTime::LocalZone:
0692                 case KADateTime::TimeZone:
0693                 {
0694                     bool second = false;
0695                     setDtWithSpec(toZoneTime(mDt.timeZone(), d, &second), d);
0696                     m2ndOccurrence = second;
0697                     break;
0698                 }
0699                 default:    // invalid
0700                     break;
0701             }
0702             break;
0703         case Qt::OffsetFromUTC:
0704             setDateTime(d.toUTC());
0705             break;
0706         case Qt::TimeZone:
0707             switch (specType)
0708             {
0709                 case KADateTime::UTC:
0710                     mDt               = d.toUTC();
0711                     utcCached         = false;
0712                     converted         = d;
0713                     converted2ndOccur = checkTzTransitionOccurrence(d, mDt);
0714                     convertedCached   = true;
0715                     break;
0716                 case KADateTime::OffsetFromUTC:
0717                     mDt               = d.toOffsetFromUtc(mDt.offsetFromUtc());
0718                     utcCached         = false;
0719                     converted         = d;
0720                     converted2ndOccur = checkTzTransitionOccurrence(d, mDt.toUTC());
0721                     convertedCached   = true;
0722                     break;
0723                 case KADateTime::LocalZone:
0724                 case KADateTime::TimeZone:
0725                     if (d.timeZone() == mDt.timeZone())
0726                     {
0727                         mDt               = d;
0728                         utcCached         = false;
0729                         convertedCached   = false;
0730                     }
0731                     else
0732                     {
0733                         mDt               = d.toTimeZone(mDt.timeZone());
0734                         utcCached         = false;
0735                         converted         = d;
0736                         converted2ndOccur = checkTzTransitionOccurrence(d, mDt.toUTC());
0737                         convertedCached   = true;
0738                     }
0739                     break;
0740                 default:
0741                     break;
0742             }
0743             break;
0744         case Qt::LocalTime:
0745             // Qt::LocalTime doesn't handle historical daylight savings times,
0746             // so use the local time zone instead.
0747             setDateTime(QDateTime(d.date(), d.time(), QTimeZone::systemTimeZone()));
0748             break;
0749     }
0750 }
0751 
0752 void KADateTimePrivate::setDateOnly(bool dateOnly)
0753 {
0754     if (dateOnly != mDateOnly)
0755     {
0756         mDateOnly = dateOnly;
0757         if (dateOnly  &&  mDt.time() != sod)
0758             mDt.setTime(sod);
0759         utcCached = convertedCached = false;
0760         setTzTransitionOccurrence(false);
0761     }
0762 }
0763 
0764 /******************************************************************************
0765 * Check whether the local time occurs twice around a daylight savings time
0766 * shift, and if so, set it to either first or second occurrence according to
0767 * the daylight savings flag in mDt.
0768 */
0769 void KADateTimePrivate::setTzTransitionOccurrence()
0770 {
0771     m2ndOccurrence = checkTzTransitionOccurrence(mDt, utcDt());
0772 }
0773 
0774 /******************************************************************************
0775 * Check whether the local time occurs twice around a daylight savings time
0776 * shift, and if so, set it to either first or second occurrence.
0777 * The daylight savings flag in mDt is ignored - only the date, time and time
0778 * zone are used in the evaluation.
0779 */
0780 bool KADateTimePrivate::setTzTransitionOccurrence(bool second)
0781 {
0782     m2ndOccurrence = false;
0783     if (qTimeSpec(mDt) != Qt::TimeZone)
0784         return false;
0785 
0786     // Convert to UTC. If the local time occurs twice around a time shift, this
0787     // UTC time could be either the first or second occurrence.
0788     const QDateTime utcDateTime = utcDt();
0789     // Check if there is a daylight savings shift around utcDateTime.
0790     QTimeZone::OffsetData transition;
0791     int utcOffsetChange = checkTzTransitionBackwards(transition, mDt.timeZone(), utcDateTime, mDt);
0792     if (utcOffsetChange < 0)
0793     {
0794         if (utcDateTime >= transition.atUtc)
0795         {
0796             if (second)
0797                 return true;
0798             mDt = mDt.addSecs(utcOffsetChange);
0799         }
0800         else
0801         {
0802             if (!second)
0803                 return true;
0804             mDt = mDt.addSecs(-utcOffsetChange);
0805         }
0806         utcCached = false;
0807         convertedCached = false;
0808         m2ndOccurrence = second;
0809         return true;
0810     }
0811     return false;
0812 }
0813 
0814 /******************************************************************************
0815 * Returns the UTC offset for the date/time, provided that it is a time zone type.
0816 * Calculates and caches the UTC value.
0817 * Parameters:
0818 *   local - the local time zone if already known (if invalid, this function will
0819 *           fetch it if required)
0820 */
0821 int KADateTimePrivate::timeZoneOffset(QTimeZone& local) const
0822 {
0823     if (specType != KADateTime::TimeZone && specType != KADateTime::LocalZone)
0824         return InvalidOffset;
0825     QDateTime dt = updatedDt(local);   // update the cache if it's LocalZone
0826     if (utcCached)
0827     {
0828         dt.setTimeZone(QTimeZone::utc());
0829         return cachedUtc().secsTo(dt);
0830     }
0831     int secondOffset;
0832     int offset = offsetAtZoneTime(mDt.timeZone(), mDt, &secondOffset);
0833     // Keep m2ndOccurrence setting, but the time doesn't occur twice, cancel it.
0834     if (m2ndOccurrence)
0835     {
0836         m2ndOccurrence = (secondOffset != offset);   // cancel "second occurrence" flag if not applicable
0837         offset = secondOffset;
0838     }
0839     if (m2ndOccurrence)
0840         offset = secondOffset;
0841     if (offset == InvalidOffset)
0842     {
0843         ut = QDateTime();   // cache an invalid UTC value
0844         utcCached = true;
0845         convertedCached = false;
0846     }
0847     else
0848     {
0849         // Calculate the UTC time from the offset and cache it
0850         QDateTime utcdt = mDt;
0851         utcdt.setTimeZone(QTimeZone::utc());
0852         setCachedUtc(utcdt.addSecs(-offset));
0853     }
0854     return offset;
0855 }
0856 
0857 /******************************************************************************
0858 * Returns the date/time converted to UTC.
0859 * The calculated UTC value is cached, to save time in future conversions.
0860 * Parameters:
0861 *   local - the local time zone if already known (if invalid, this function will
0862 *           fetch it if required)
0863 */
0864 QDateTime KADateTimePrivate::toUtc(QTimeZone& local) const
0865 {
0866     updatedDt(local);   // update the cache if it's LocalZone
0867     if (utcCached)
0868     {
0869         // Return cached UTC value
0870         if (specType == KADateTime::LocalZone)
0871         {
0872             // LocalZone uses the dynamic current local system time zone.
0873             // Check for a time zone change before using the cached UTC value.
0874             if (!local.isValid())
0875                 local = QTimeZone::systemTimeZone();
0876             if (mDt.timeZone() == local)
0877             {
0878 //                qDebug() << "toUtc(): cached -> " << cachedUtc() << endl,
0879 #ifdef COMPILING_TESTS
0880                 ++KADateTime_utcCacheHit;
0881 #endif
0882                 return cachedUtc();
0883             }
0884             utcCached = false;
0885         }
0886         else
0887         {
0888 //            qDebug() << "toUtc(): cached -> " << cachedUtc() << endl,
0889 #ifdef COMPILING_TESTS
0890             ++KADateTime_utcCacheHit;
0891 #endif
0892             return cachedUtc();
0893         }
0894     }
0895 
0896     // No cached UTC value, so calculate it
0897     switch (specType)
0898     {
0899         case KADateTime::UTC:
0900             return mDt;
0901         case KADateTime::OffsetFromUTC:
0902         {
0903             if (!mDt.isValid())
0904                 break;
0905             const QDateTime dt = utcDt();
0906 //            qDebug() << "toUtc(): calculated -> " << dt << endl,
0907             return dt;
0908         }
0909         case KADateTime::LocalZone:   // mDt is set to the system time zone
0910         case KADateTime::TimeZone:
0911             if (!mDt.isValid())
0912                 break;
0913             timeZoneOffset(local);   // calculate offset and cache UTC value
0914 //            qDebug() << "toUtc(): calculated -> " << cachedUtc() << endl,
0915             return cachedUtc();
0916         default:
0917             break;
0918     }
0919 
0920     // Invalid - mark it cached to avoid having to process it again
0921     ut = QDateTime();    // (invalid)
0922     utcCached = true;
0923     convertedCached = false;
0924 //    qDebug() << "toUtc(): invalid";
0925     return mDt;
0926 }
0927 
0928 /******************************************************************************
0929 * Convert this value to another time zone.
0930 * The value is cached to save having to repeatedly calculate it.
0931 * The caller should check for an invalid date/time.
0932 * Parameters:
0933 *   zone  - the time zone to convert to
0934 *   local - the local time zone if already known (if invalid, this function will
0935 *           fetch it if required)
0936 */
0937 QDateTime KADateTimePrivate::toZone(const QTimeZone& zone, QTimeZone& local) const
0938 {
0939     updatedDt(local);   // update the cache if it's LocalZone
0940     if (convertedCached  &&  converted.timeZone() == zone)
0941     {
0942         // Converted value is already cached
0943 #ifdef COMPILING_TESTS
0944 //        qDebug() << "KADateTimePrivate::toZone(" << zone->id() << "): " << mDt << " cached";
0945         ++KADateTime_zoneCacheHit;
0946 #endif
0947         return converted;
0948     }
0949     else
0950     {
0951         // Need to convert the value
0952         bool second;
0953         const QDateTime result = toZoneTime(zone, toUtc(local), &second);
0954         converted         = result;
0955         converted2ndOccur = second;
0956         convertedCached   = true;
0957         return result;
0958     }
0959 }
0960 
0961 /******************************************************************************
0962 * Convert this value to another time zone, and write it into the specified instance.
0963 * The value is cached to save having to repeatedly calculate it.
0964 * The caller should check for an invalid date/time.
0965 * Parameters:
0966 *   newd  - the instance to set equal to the converted value
0967 *   zone  - the time zone to convert to
0968 *   local - the local time zone if already known (if invalid, this function will
0969 *           fetch it if required)
0970 */
0971 void KADateTimePrivate::newToZone(KADateTimePrivate* newd, const QTimeZone& zone, QTimeZone& local) const
0972 {
0973     newd->mDt            = toZone(zone, local);
0974     newd->specType       = KADateTime::TimeZone;
0975     newd->utcCached      = utcCached;
0976     newd->mDateOnly      = mDateOnly;
0977     newd->m2ndOccurrence = converted2ndOccur;
0978     switch (specType)
0979     {
0980         case KADateTime::UTC:
0981             newd->ut = mDt;   // cache the UTC value
0982             break;
0983         case KADateTime::LocalZone:
0984         case KADateTime::TimeZone:
0985             // This instance is also type time zone, so cache its value in the new instance
0986             newd->converted         = mDt;
0987             newd->converted2ndOccur = m2ndOccurrence;
0988             newd->convertedCached   = true;
0989             newd->ut                = ut;
0990             return;
0991         case KADateTime::OffsetFromUTC:
0992         default:
0993             newd->ut = ut;
0994             break;
0995     }
0996     newd->convertedCached = false;
0997 }
0998 
0999 /*----------------------------------------------------------------------------*/
1000 Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KADateTimePrivate>, emptyDateTimePrivate, (new KADateTimePrivate))
1001 
1002 KADateTime::KADateTime()
1003     : d(*emptyDateTimePrivate())
1004 {
1005 }
1006 
1007 KADateTime::KADateTime(const QDate& date, const Spec& spec)
1008     : d(new KADateTimePrivate(date, KADateTimePrivate::sod, spec, true))
1009 {
1010 }
1011 
1012 KADateTime::KADateTime(const QDate& date, const QTime& time, const Spec& spec)
1013     : d(new KADateTimePrivate(date, time, spec))
1014 {
1015 }
1016 
1017 KADateTime::KADateTime(const QDateTime& dt, const Spec& spec)
1018     : d(new KADateTimePrivate(dt, spec))
1019 {
1020 }
1021 
1022 KADateTime::KADateTime(const QDateTime& dt)
1023     : d(new KADateTimePrivate(dt))
1024 {
1025 }
1026 
1027 KADateTime::KADateTime(const KADateTime& other) = default;
1028 
1029 KADateTime::~KADateTime() = default;
1030 
1031 KADateTime& KADateTime::operator=(const KADateTime& other)
1032 {
1033     if (&other != this)
1034         d = other.d;
1035     return *this;
1036 }
1037 
1038 void KADateTime::detach()
1039 {
1040     d.detach();
1041 }
1042 bool KADateTime::isNull() const
1043 {
1044     return d->rawDt().isNull();
1045 }
1046 bool KADateTime::isValid() const
1047 {
1048     return d->specType != Invalid  &&  d->rawDt().isValid();
1049 }
1050 bool KADateTime::isDateOnly() const
1051 {
1052     return d->dateOnly();
1053 }
1054 bool KADateTime::isLocalZone() const
1055 {
1056     return d->specType == LocalZone;
1057 }
1058 bool KADateTime::isUtc() const
1059 {
1060     return d->specType == UTC || (d->specType == OffsetFromUTC && d->spec().utcOffset() == 0);
1061 }
1062 bool KADateTime::isOffsetFromUtc() const
1063 {
1064     return d->specType == OffsetFromUTC;
1065 }
1066 bool KADateTime::isSecondOccurrence() const
1067 {
1068     return (d->specType == TimeZone || d->specType == LocalZone) && d->secondOccurrence();
1069 }
1070 bool KADateTime::isDaylightTime() const
1071 {
1072     return (d->specType == TimeZone || d->specType == LocalZone) && d->rawDt().isDaylightTime();
1073 }
1074 QDate KADateTime::date() const
1075 {
1076     return d->date();
1077 }
1078 QTime KADateTime::time() const
1079 {
1080     return d->time();
1081 }
1082 QDateTime KADateTime::qDateTime() const
1083 {
1084     return d->dt();
1085 }
1086 
1087 KADateTime::Spec KADateTime::timeSpec() const
1088 {
1089     return d->spec();
1090 }
1091 KADateTime::SpecType KADateTime::timeType() const
1092 {
1093     return d->specType;
1094 }
1095 
1096 QTimeZone KADateTime::timeZone() const
1097 {
1098     switch (d->specType)
1099     {
1100         case UTC:
1101             return QTimeZone::utc();
1102         case TimeZone:
1103             return d->timeZone();
1104         case LocalZone:
1105             return QTimeZone::systemTimeZone();
1106         default:
1107             return {};
1108     }
1109 }
1110 
1111 int KADateTime::utcOffset() const
1112 {
1113     switch (d->specType)
1114     {
1115         case TimeZone:
1116         case LocalZone:
1117         {
1118             QTimeZone local;
1119             int offset = d->timeZoneOffset(local);   // calculate offset and cache UTC value
1120             return (offset == InvalidOffset) ? 0 : offset;
1121         }
1122         case OffsetFromUTC:
1123             return d->spec().utcOffset();
1124         case UTC:
1125         default:
1126             return 0;
1127     }
1128 }
1129 
1130 KADateTime KADateTime::toUtc() const
1131 {
1132     if (!isValid())
1133         return {};
1134     if (d->specType == UTC)
1135         return *this;
1136     if (d->dateOnly())
1137         return KADateTime(d->date(), Spec(UTC));
1138     QTimeZone local;
1139     const QDateTime udt = d->toUtc(local);
1140     if (!udt.isValid())
1141         return {};
1142     return KADateTime(udt, UTC);
1143 }
1144 
1145 KADateTime KADateTime::toOffsetFromUtc() const
1146 {
1147     if (!isValid())
1148         return {};
1149     int offset = 0;
1150     switch (d->specType)
1151     {
1152         case OffsetFromUTC:
1153             return *this;
1154         case UTC:
1155         {
1156             if (d->dateOnly())
1157                 return KADateTime(d->date(), Spec(OffsetFromUTC, 0));
1158             QDateTime qdt = d->rawDt();
1159             return KADateTime(qdt.date(), qdt.time(), Spec(OffsetFromUTC, 0));
1160         }
1161         case TimeZone:
1162         {
1163             QTimeZone local;
1164             offset = d->timeZoneOffset(local);   // calculate offset and cache UTC value
1165             break;
1166         }
1167         case LocalZone:
1168         {
1169             QTimeZone local;
1170             const QDateTime dt = d->updatedDt(local);
1171             offset = offsetAtZoneTime(dt.timeZone(), dt);
1172             break;
1173         }
1174         default:
1175             return {};
1176     }
1177     if (offset == InvalidOffset)
1178         return {};
1179     if (d->dateOnly())
1180         return KADateTime(d->date(), Spec(OffsetFromUTC, offset));
1181     return KADateTime(d->date(), d->time(), Spec(OffsetFromUTC, offset));
1182 }
1183 
1184 KADateTime KADateTime::toOffsetFromUtc(int utcOffset) const
1185 {
1186     if (!isValid())
1187         return {};
1188     if (d->specType == OffsetFromUTC && d->spec().utcOffset() == utcOffset)
1189         return *this;
1190     if (d->dateOnly())
1191         return KADateTime(d->date(), Spec(OffsetFromUTC, utcOffset));
1192     QTimeZone local;
1193     return KADateTime(d->toUtc(local), Spec(OffsetFromUTC, utcOffset));
1194 }
1195 
1196 KADateTime KADateTime::toLocalZone() const
1197 {
1198     if (!isValid())
1199         return {};
1200     if (d->dateOnly())
1201         return KADateTime(d->date(), LocalZone);
1202     QTimeZone local = QTimeZone::systemTimeZone();
1203     if (d->specType == TimeZone && d->timeZone() == local)
1204         return KADateTime(d->date(), d->time(), LocalZone);
1205     switch (d->specType)
1206     {
1207         case TimeZone:
1208         case OffsetFromUTC:
1209         case UTC:
1210         {
1211             KADateTime result;
1212             d->newToZone(result.d, local, local);  // cache the time zone conversion
1213             result.d->specType = LocalZone;
1214             return result;
1215         }
1216         case LocalZone:
1217             return *this;
1218         default:
1219             return {};
1220     }
1221 }
1222 
1223 KADateTime KADateTime::toZone(const QTimeZone& zone) const
1224 {
1225     if (!zone.isValid() || !isValid())
1226         return {};
1227     if (d->specType == TimeZone && d->timeZone() == zone)
1228         return *this;    // preserve UTC cache, if any
1229     if (d->dateOnly())
1230         return KADateTime(d->date(), Spec(zone));
1231     KADateTime result;
1232     QTimeZone local;
1233     d->newToZone(result.d, zone, local);  // cache the time zone conversion
1234     return result;
1235 }
1236 
1237 KADateTime KADateTime::toTimeSpec(const KADateTime& dt) const
1238 {
1239     return toTimeSpec(dt.timeSpec());
1240 }
1241 
1242 KADateTime KADateTime::toTimeSpec(const Spec& spec) const
1243 {
1244     if (spec == d->spec())
1245         return *this;
1246     if (!isValid())
1247         return {};
1248     if (d->dateOnly())
1249         return KADateTime(d->date(), spec);
1250     if (spec.type() == TimeZone)
1251     {
1252         KADateTime result;
1253         QTimeZone local;
1254         d->newToZone(result.d, spec.timeZone(), local);  // cache the time zone conversion
1255         return result;
1256     }
1257     QTimeZone local;
1258     return KADateTime(d->toUtc(local), spec);
1259 }
1260 
1261 qint64 KADateTime::toSecsSinceEpoch() const
1262 {
1263     QTimeZone local;
1264     const QDateTime qdt = d->toUtc(local);
1265     if (!qdt.isValid())
1266         return LLONG_MIN;
1267     return qdt.toSecsSinceEpoch();
1268 }
1269 
1270 void KADateTime::setSecsSinceEpoch(qint64 seconds)
1271 {
1272     QDateTime dt;
1273     dt.setTimeZone(QTimeZone::utc()); // prevent QDateTime::setMSecsSinceEpoch()
1274                                       // converting to local time
1275     dt.setMSecsSinceEpoch(seconds * 1000);
1276     d->specType = UTC;
1277     d->setDateOnly(false);
1278     d->setDtWithSpec(dt);
1279 }
1280 
1281 void KADateTime::setDateOnly(bool dateOnly)
1282 {
1283     d->setDateOnly(dateOnly);
1284 }
1285 
1286 void KADateTime::setDate(const QDate& date)
1287 {
1288     d->setDate(date);
1289 }
1290 
1291 void KADateTime::setTime(const QTime& time)
1292 {
1293     d->setTime(time);
1294 }
1295 
1296 void KADateTime::setTimeSpec(const Spec& other)
1297 {
1298     d->setSpec(other);
1299 }
1300 
1301 void KADateTime::setSecondOccurrence(bool second)
1302 {
1303     if ((d->specType == KADateTime::TimeZone  ||  d->specType == KADateTime::LocalZone)
1304     &&  second != d->m2ndOccurrence)
1305     {
1306         d->setTzTransitionOccurrence(second);
1307     }
1308 }
1309 
1310 KADateTime KADateTime::addMSecs(qint64 msecs) const
1311 {
1312     if (!msecs)
1313         return *this;    // retain cache - don't create another instance
1314     if (!isValid())
1315         return {};
1316     if (d->dateOnly())
1317     {
1318         KADateTime result(*this);
1319         result.d->setDate(d->date().addDays(msecs / 86400000));
1320         return result;
1321     }
1322     QTimeZone local;
1323     return KADateTime(d->toUtc(local).addMSecs(msecs), d->spec());
1324 }
1325 
1326 KADateTime KADateTime::addSecs(qint64 secs) const
1327 {
1328     return addMSecs(secs * 1000);
1329 }
1330 
1331 KADateTime KADateTime::addDays(qint64 days) const
1332 {
1333     if (!days)
1334         return *this;    // retain cache - don't create another instance
1335     KADateTime result(*this);
1336     result.d->setDate(d->date().addDays(days));
1337     return result;
1338 }
1339 
1340 KADateTime KADateTime::addMonths(int months) const
1341 {
1342     if (!months)
1343         return *this;    // retain cache - don't create another instance
1344     KADateTime result(*this);
1345     result.d->setDate(d->date().addMonths(months));
1346     return result;
1347 }
1348 
1349 KADateTime KADateTime::addYears(int years) const
1350 {
1351     if (!years)
1352         return *this;    // retain cache - don't create another instance
1353     KADateTime result(*this);
1354     result.d->setDate(d->date().addYears(years));
1355     return result;
1356 }
1357 
1358 qint64 KADateTime::msecsTo(const KADateTime& t2) const
1359 {
1360     if (!isValid() || !t2.isValid())
1361         return 0;
1362     if (d->dateOnly())
1363     {
1364         const QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date();
1365         return d->date().daysTo(dat) * 86400*1000;
1366     }
1367     if (t2.d->dateOnly())
1368         return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date()) * 86400*1000;
1369     QTimeZone local;
1370     return d->toUtc(local).msecsTo(t2.d->toUtc(local));
1371 }
1372 
1373 qint64 KADateTime::secsTo(const KADateTime& t2) const
1374 {
1375     if (!isValid() || !t2.isValid())
1376         return 0;
1377     if (d->dateOnly())
1378     {
1379         const QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date();
1380         return d->date().daysTo(dat) * 86400;
1381     }
1382     if (t2.d->dateOnly())
1383         return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date()) * 86400;
1384     QTimeZone local;
1385     return d->toUtc(local).secsTo(t2.d->toUtc(local));
1386 }
1387 
1388 qint64 KADateTime::daysTo(const KADateTime& t2) const
1389 {
1390     if (!isValid() || !t2.isValid())
1391         return 0;
1392     if (d->dateOnly())
1393     {
1394         const QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date();
1395         return d->date().daysTo(dat);
1396     }
1397     if (t2.d->dateOnly())
1398         return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date());
1399 
1400     QDate dat;
1401     QTimeZone local;
1402     switch (d->specType)
1403     {
1404         case UTC:
1405             dat = t2.d->toUtc(local).date();
1406             break;
1407         case OffsetFromUTC:
1408             dat = t2.d->toUtc(local).addSecs(d->spec().utcOffset()).date();
1409             break;
1410         case TimeZone:
1411             dat = t2.d->toZone(d->timeZone(), local).date();   // this caches the converted time in t2
1412             break;
1413         case LocalZone:
1414             local = QTimeZone::systemTimeZone();
1415             dat = t2.d->toZone(local, local).date();   // this caches the converted time in t2
1416             break;
1417         default:    // invalid
1418             return 0;
1419     }
1420     return d->date().daysTo(dat);
1421 }
1422 
1423 KADateTime KADateTime::currentLocalDateTime()
1424 {
1425 #ifdef SIMULATION
1426 #ifndef NDEBUG
1427     if (KADateTimePrivate::simulationLocalZone.isValid())
1428     {
1429         KADateTime dt = currentUtcDateTime().toZone(KADateTimePrivate::simulationLocalZone);
1430         dt.setSpec(LocalZone);
1431         return dt;
1432     }
1433     if (KADateTimePrivate::simulationOffset)
1434     {
1435         KADateTime dt = currentUtcDateTime().toZone(QTimeZone::systemTimeZone());
1436         dt.setSpec(LocalZone);
1437         return dt;
1438     }
1439 #endif
1440 #endif
1441     return KADateTime(QDateTime::currentDateTime(), LocalZone);
1442 }
1443 
1444 KADateTime KADateTime::currentUtcDateTime()
1445 {
1446     const KADateTime result(QDateTime::currentDateTimeUtc(), UTC);
1447 #ifndef NDEBUG
1448 #ifdef SIMULATION
1449     return result.addSecs(KADateTimePrivate::simulationOffset);
1450 #else
1451     return result;
1452 #endif
1453 #else
1454     return result;
1455 #endif
1456 }
1457 
1458 KADateTime KADateTime::currentDateTime(const Spec& spec)
1459 {
1460     switch (spec.type())
1461     {
1462         case UTC:
1463             return currentUtcDateTime();
1464         case TimeZone:
1465             if (spec.timeZone() != QTimeZone::systemTimeZone())
1466                 break;
1467             [[fallthrough]]; // fall through to LocalZone
1468         case LocalZone:
1469             return currentLocalDateTime();
1470         default:
1471             break;
1472     }
1473     return currentUtcDateTime().toTimeSpec(spec);
1474 }
1475 
1476 QDate KADateTime::currentLocalDate()
1477 {
1478     return currentLocalDateTime().date();
1479 }
1480 
1481 QTime KADateTime::currentLocalTime()
1482 {
1483     return currentLocalDateTime().time();
1484 }
1485 
1486 KADateTime::Comparison KADateTime::compare(const KADateTime& other) const
1487 {
1488     QDateTime start1;
1489     QDateTime start2;
1490     QTimeZone local;
1491     const bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != other.d->secondOccurrence());
1492     if (conv)
1493     {
1494         // Different time specs or one is a time which occurs twice,
1495         // so convert to UTC before comparing
1496         start1 = d->toUtc(local);
1497         start2 = other.d->toUtc(local);
1498     }
1499     else
1500     {
1501         // Same time specs, so no need to convert to UTC
1502         start1 = d->dt();
1503         start2 = other.d->dt();
1504     }
1505     if (d->dateOnly() || other.d->dateOnly())
1506     {
1507         // At least one of the instances is date-only, so we need to compare
1508         // time periods rather than just times.
1509         QDateTime end1;
1510         QDateTime end2;
1511         if (conv)
1512         {
1513             if (d->dateOnly())
1514             {
1515                 KADateTime kdt(*this);
1516                 kdt.setTime(QTime(23, 59, 59, 999));
1517                 end1 = kdt.d->toUtc(local);
1518             }
1519             else
1520                 end1 = start1;
1521             if (other.d->dateOnly())
1522             {
1523                 KADateTime kdt(other);
1524                 kdt.setTime(QTime(23, 59, 59, 999));
1525                 end2 = kdt.d->toUtc(local);
1526             }
1527             else
1528                 end2 = start2;
1529         }
1530         else
1531         {
1532             end1 = d->dt();
1533             if (d->dateOnly())
1534                 end1.setTime(QTime(23, 59, 59, 999));
1535             end2 = other.d->dt();
1536             if (other.d->dateOnly())
1537                 end2.setTime(QTime(23, 59, 59, 999));
1538         }
1539         if (start1 == start2)
1540         {
1541             return !d->dateOnly() ? AtStart
1542                  : (end1 == end2) ? Equal
1543                  : (end1 < end2)  ? static_cast<Comparison>(AtStart | Inside)
1544                  :                  static_cast<Comparison>(AtStart | Inside | AtEnd | After);
1545         }
1546         if (start1 < start2)
1547         {
1548             return (end1 < start2)  ? Before
1549                  : (end1 == end2)   ? static_cast<Comparison>(Before | AtStart | Inside | AtEnd)
1550                  : (end1 == start2) ? static_cast<Comparison>(Before | AtStart)
1551                  : (end1 < end2)    ? static_cast<Comparison>(Before | AtStart | Inside)
1552                  :                    Outside;
1553         }
1554         else
1555         {
1556             return (start1 > end2)  ? After
1557                  : (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast<Comparison>(AtEnd | After))
1558                  : (end1 == end2)   ? static_cast<Comparison>(Inside | AtEnd)
1559                  : (end1 < end2)    ? Inside
1560                  :                    static_cast<Comparison>(Inside | AtEnd | After);
1561         }
1562     }
1563     return (start1 == start2) ? Equal : (start1 < start2) ? Before : After;
1564 }
1565 
1566 bool KADateTime::operator==(const KADateTime& other) const
1567 {
1568     if (d == other.d)
1569         return true;    // the two instances share the same data
1570     if (d->dateOnly() != other.d->dateOnly())
1571         return false;
1572     if (d->equalSpec(*other.d))
1573     {
1574         // Both instances are in the same time zone, so compare directly
1575         if (d->dateOnly())
1576             return d->date() == other.d->date();
1577         else
1578             return d->secondOccurrence() == other.d->secondOccurrence()
1579                &&  d->dt() == other.d->dt();
1580     }
1581     // Don't waste time converting to UTC if the dates aren't close enough.
1582     if (qAbs(d->date().daysTo(other.d->date())) > 2)
1583         return false;
1584     QTimeZone local;
1585     if (d->dateOnly())
1586     {
1587         // Date-only values are equal if both the start and end of day times are equal.
1588         if (d->toUtc(local) != other.d->toUtc(local))
1589             return false;    // start-of-day times differ
1590         KADateTime end1(*this);
1591         end1.setTime(QTime(23, 59, 59, 999));
1592         KADateTime end2(other);
1593         end2.setTime(QTime(23, 59, 59, 999));
1594         return end1.d->toUtc(local) == end2.d->toUtc(local);
1595     }
1596     return d->toUtc(local) == other.d->toUtc(local);
1597 }
1598 
1599 bool KADateTime::operator<(const KADateTime& other) const
1600 {
1601     if (d == other.d)
1602         return false;    // the two instances share the same data
1603     if (d->equalSpec(*other.d))
1604     {
1605         // Both instances are in the same time zone, so compare directly
1606         if (d->dateOnly() || other.d->dateOnly())
1607             return d->date() < other.d->date();
1608         if (d->secondOccurrence() == other.d->secondOccurrence())
1609             return d->dt() < other.d->dt();
1610         // One is the second occurrence of a date/time, during a change from
1611         // daylight saving to standard time, so only do a direct comparison
1612         // if the dates are more than 1 day apart.
1613         const int dayDiff = d->date().daysTo(other.d->date());
1614         if (dayDiff > 1)
1615             return true;
1616         if (dayDiff < -1)
1617             return false;
1618     }
1619     else
1620     {
1621         // Don't waste time converting to UTC if the dates aren't close enough.
1622         const int dayDiff = d->date().daysTo(other.d->date());
1623         if (dayDiff > 2)
1624             return true;
1625         if (dayDiff < -2)
1626             return false;
1627     }
1628     QTimeZone local;
1629     if (d->dateOnly())
1630     {
1631         // This instance is date-only, so we need to compare the end of its
1632         // day with the other value. Note that if the other value is date-only,
1633         // we want to compare with the start of its day, which will happen
1634         // automatically.
1635         KADateTime kdt(*this);
1636         kdt.setTime(QTime(23, 59, 59, 999));
1637         return kdt.d->toUtc(local) < other.d->toUtc(local);
1638     }
1639     return d->toUtc(local) < other.d->toUtc(local);
1640 }
1641 
1642 QString KADateTime::toString(const QString& format) const
1643 {
1644     if (!isValid())
1645         return {};
1646 
1647     enum { TZNone, UTCOffsetShort, UTCOffset, UTCOffsetColon, TZAbbrev, TZName };
1648     const QLocale locale;
1649     QString result;
1650     bool escape = false;
1651     ushort flag = 0;
1652     for (int i = 0, end = format.length();  i < end;  ++i)
1653     {
1654         int zone = TZNone;
1655         int num = NO_NUMBER;
1656         int numLength = 0;    // no leading zeroes
1657         ushort ch = format[i].unicode();
1658         if (!escape)
1659         {
1660             if (ch == '%')
1661                 escape = true;
1662             else
1663                 result += format[i];
1664             continue;
1665         }
1666         if (!flag)
1667         {
1668             switch (ch)
1669             {
1670                 case '%':
1671                     result += QLatin1Char('%');
1672                     break;
1673                 case ':':
1674                     flag = ch;
1675                     break;
1676                 case 'Y':     // year
1677                     num = d->date().year();
1678                     numLength = 4;
1679                     break;
1680                 case 'y':     // year, 2 digits
1681                     num = d->date().year() % 100;
1682                     numLength = 2;
1683                     break;
1684                 case 'm':     // month, 01 - 12
1685                     numLength = 2;
1686                     num = d->date().month();
1687                     break;
1688                 case 'B':     // month name, translated
1689                     result += locale.monthName(d->date().month(), QLocale::LongFormat);
1690                     break;
1691                 case 'b':     // month name, translated, short
1692                     result += locale.monthName(d->date().month(), QLocale::ShortFormat);
1693                     break;
1694                 case 'd':     // day of month, 01 - 31
1695                     numLength = 2;
1696                     [[fallthrough]]; // fall through to 'e'
1697                 case 'e':     // day of month, 1 - 31
1698                     num = d->date().day();
1699                     break;
1700                 case 'A':     // week day name, translated
1701                     result += locale.dayName(d->date().dayOfWeek(), QLocale::LongFormat);
1702                     break;
1703                 case 'a':     // week day name, translated, short
1704                     result += locale.dayName(d->date().dayOfWeek(), QLocale::ShortFormat);
1705                     break;
1706                 case 'H':     // hour, 00 - 23
1707                     numLength = 2;
1708                     [[fallthrough]]; // fall through to 'k'
1709                 case 'k':     // hour, 0 - 23
1710                     num = d->time().hour();
1711                     break;
1712                 case 'I':     // hour, 01 - 12
1713                     numLength = 2;
1714                     [[fallthrough]]; // fall through to 'l'
1715                 case 'l':     // hour, 1 - 12
1716                     num = (d->time().hour() + 11) % 12 + 1;
1717                     break;
1718                 case 'M':     // minutes, 00 - 59
1719                     num = d->time().minute();
1720                     numLength = 2;
1721                     break;
1722                 case 'S':     // seconds, 00 - 59
1723                     num = d->time().second();
1724                     numLength = 2;
1725                     break;
1726                 case 'P':
1727                 {   // am/pm
1728                     bool am = (d->time().hour() < 12);
1729                     QString text = (am ? locale.amText() : locale.pmText()).toLower();
1730                     if (text == QLatin1String("a.m."))
1731                         text = QStringLiteral("am");
1732                     else if (text == QLatin1String("p.m."))
1733                         text = QStringLiteral("pm");
1734                     result += text;
1735                     break;
1736                 }
1737                 case 'p':
1738                 {   // AM/PM
1739                     bool am = (d->time().hour() < 12);
1740                     QString text = (am ? locale.amText() : locale.pmText()).toUpper();
1741                     if (text == QLatin1String("A.M."))
1742                         text = QStringLiteral("AM");
1743                     else if (text == QLatin1String("P.M."))
1744                         text = QStringLiteral("PM");
1745                     result += text;
1746                     break;
1747                 }
1748                 case 'z':     // UTC offset in hours and minutes
1749                     zone = UTCOffset;
1750                     break;
1751                 case 'Z':     // time zone abbreviation
1752                     zone = TZAbbrev;
1753                     break;
1754                 default:
1755                     result += QLatin1Char('%');
1756                     result += format[i];
1757                     break;
1758             }
1759         }
1760         else if (flag == ':')
1761         {
1762             // It's a "%:" sequence
1763             switch (ch)
1764             {
1765                 case 'A':     // week day name in English
1766                     result += longDay(d->date().dayOfWeek());
1767                     break;
1768                 case 'a':     // week day name in English, short
1769                     result += shortDay(d->date().dayOfWeek());
1770                     break;
1771                 case 'B':     // month name in English
1772                     result += longMonth(d->date().month());
1773                     break;
1774                 case 'b':     // month name in English, short
1775                     result += shortMonth(d->date().month());
1776                     break;
1777                 case 'm':     // month, 1 - 12
1778                     num = d->date().month();
1779                     break;
1780                 case 'P':     // am/pm
1781                     result += (d->time().hour() < 12) ? QLatin1String("am") : QLatin1String("pm");
1782                     break;
1783                 case 'p':     // AM/PM
1784                     result += (d->time().hour() < 12) ? QLatin1String("AM") : QLatin1String("PM");
1785                     break;
1786                 case 'S':
1787                 {   // seconds with ':' prefix, only if non-zero
1788                     int sec = d->time().second();
1789                     if (sec || d->time().msec())
1790                     {
1791                         result += QLatin1Char(':');
1792                         num = sec;
1793                         numLength = 2;
1794                     }
1795                     break;
1796                 }
1797                 case 's':     // milliseconds
1798                     result += numString(d->time().msec(), 3);
1799                     break;
1800                 case 'u':     // UTC offset in hours
1801                     zone = UTCOffsetShort;
1802                     break;
1803                 case 'z':     // UTC offset in hours and minutes, with colon
1804                     zone = UTCOffsetColon;
1805                     break;
1806                 case 'Z':     // time zone name
1807                     zone = TZName;
1808                     break;
1809                 default:
1810                     result += QLatin1String("%:");
1811                     result += format[i];
1812                     break;
1813             }
1814             flag = 0;
1815         }
1816         if (!flag)
1817             escape = false;
1818 
1819         // Append any required number or time zone information
1820         if (num != NO_NUMBER)
1821         {
1822             if (!numLength)
1823                 result += QString::number(num);
1824             else if (numLength == 2 || numLength == 4)
1825             {
1826                 if (num < 0)
1827                 {
1828                     num = -num;
1829                     result += QLatin1Char('-');
1830                 }
1831                 result += numString(num, (numLength == 2 ? 2 : 4));
1832             }
1833         }
1834         else if (zone != TZNone)
1835         {
1836             QTimeZone tz;
1837             switch (d->specType)
1838             {
1839                 case UTC:
1840                 case TimeZone:
1841                 case LocalZone:
1842                     switch (d->specType)
1843                     {
1844                         case UTC:
1845                             tz = QTimeZone::utc();
1846                             break;
1847                         case TimeZone:
1848                             tz = d->timeZone();
1849                             break;
1850                         case LocalZone:
1851                             tz = QTimeZone::systemTimeZone();
1852                             break;
1853                         default:
1854                             break;
1855                     }
1856                     [[fallthrough]]; // fall through to OffsetFromUTC
1857                 case OffsetFromUTC:
1858                 {
1859                     QTimeZone local;
1860                     int offset = (d->specType == TimeZone || d->specType == LocalZone) ? d->timeZoneOffset(local)
1861                                : (d->specType == OffsetFromUTC) ? d->spec().utcOffset() : 0;
1862                     if (offset == InvalidOffset)
1863                         return result + QLatin1String("+ERROR");
1864                     offset /= 60;
1865                     switch (zone)
1866                     {
1867                         case UTCOffsetShort:  // UTC offset in hours
1868                         case UTCOffset:       // UTC offset in hours and minutes
1869                         case UTCOffsetColon:
1870                         {  // UTC offset in hours and minutes, with colon
1871                             if (offset >= 0)
1872                                 result += QLatin1Char('+');
1873                             else
1874                             {
1875                                 result += QLatin1Char('-');
1876                                 offset = -offset;
1877                             }
1878                             result += numString(offset / 60, 2);
1879                             if (zone == UTCOffsetColon)
1880                                 result += QLatin1Char(':');
1881                             if (ch != 'u' || offset % 60)
1882                                 result += numString(offset % 60, 2);
1883                             break;
1884                         }
1885                         case TZAbbrev:     // time zone abbreviation
1886                             if (tz.isValid() && d->specType != OffsetFromUTC)
1887                                 result += tz.abbreviation(d->toUtc(local));
1888                             break;
1889                         case TZName:       // time zone name
1890                             if (tz.isValid() && d->specType != OffsetFromUTC)
1891                                 result += QString::fromLatin1(tz.id());
1892                             break;
1893                     }
1894                     break;
1895                 }
1896                 default:
1897                     break;
1898             }
1899         }
1900     }
1901     return result;
1902 }
1903 
1904 QString KADateTime::toString(TimeFormat format) const
1905 {
1906     QString result;
1907     if (!d->rawDt().isValid())
1908         return result;
1909 
1910     QString tzsign = QStringLiteral("+");
1911     int offset = 0;
1912     QString tzcolon;
1913     switch (format)
1914     {
1915         case RFCDateDay:
1916             result += shortDay(d->date().dayOfWeek());
1917             result += QLatin1String(", ");
1918             [[fallthrough]]; // fall through to RFCDate
1919         case RFCDate:
1920         {
1921             QString seconds;
1922             if (d->time().second())
1923                 seconds = QLatin1String(":") + numString(d->time().second(), 2);
1924             result += QStringLiteral("%1 %2 ").arg(numString(d->date().day(), 2),
1925                                                    shortMonth(d->date().month()));
1926             int year = d->date().year();
1927             if (year < 0)
1928             {
1929                 result += QLatin1Char('-');
1930                 year = -year;
1931             }
1932             result += QStringLiteral("%1 %2:%3%4 ").arg(numString(year,               4),
1933                                                         numString(d->time().hour(),   2),
1934                                                         numString(d->time().minute(), 2),
1935                                                         seconds);
1936             break;
1937         }
1938         case RFC3339Date:
1939         {
1940             result += QStringLiteral("%1-%2-%3T%4:%5:%6")
1941                                       .arg(numString(d->date().year(),   4),
1942                                            numString(d->date().month(),  2),
1943                                            numString(d->date().day(),    2),
1944                                            numString(d->time().hour(),   2),
1945                                            numString(d->time().minute(), 2),
1946                                            numString(d->time().second(), 2));
1947             int msec = d->time().msec();
1948             if (msec)
1949             {
1950                 int digits = 3;
1951                 if (!(msec % 10))
1952                 {
1953                     msec /= 10, --digits;
1954                     if (!(msec % 10))
1955                         msec /= 10, --digits;
1956                 }
1957                 result += QStringLiteral(".%1").arg(numString(msec, digits));
1958             }
1959             if (d->specType == UTC)
1960                 return result + QLatin1Char('Z');
1961             tzcolon = QStringLiteral(":");
1962             break;
1963         }
1964         case ISODate:
1965         case ISODateFull:
1966         {
1967             // QDateTime::toString(Qt::ISODate) doesn't output fractions of a second
1968             int year = d->date().year();
1969             if (year < 0)
1970             {
1971                 result += QLatin1Char('-');
1972                 year = -year;
1973             }
1974             result += QStringLiteral("%1-%2-%3").arg(numString(year, 4),
1975                                                      numString(d->date().month(), 2),
1976                                                      numString(d->date().day(),   2));
1977             if (!d->dateOnly()  ||  d->specType != LocalZone)
1978             {
1979                 result += QStringLiteral("T%1:%2:%3").arg(numString(d->time().hour(), 2),
1980                                                           numString(d->time().minute(), 2),
1981                                                           numString(d->time().second(), 2));
1982                 if (d->time().msec())
1983                 {
1984                     // Comma is preferred by ISO8601 as the decimal point symbol,
1985                     // so use it unless '.' is the symbol used in this locale.
1986                     result += (QLocale().decimalPoint() == QLatin1Char('.')) ? QLatin1Char('.') : QLatin1Char(',');
1987                     result += numString(d->time().msec(), 3);
1988                 }
1989             }
1990             if (d->specType == UTC)
1991                 return result + QLatin1Char('Z');
1992             if (format == ISODate && d->specType == LocalZone)
1993                 return result;
1994             tzcolon = QStringLiteral(":");
1995             break;
1996         }
1997         case QtTextDate:
1998             if (d->dateOnly())
1999                 result = toString(QStringLiteral("%a %b %e %Y"));
2000             else
2001                 result = toString(QStringLiteral("%a %b %e %H:%M:%S %Y"));
2002             if (result.isEmpty() || d->specType == LocalZone)
2003                 return result;
2004             result += QLatin1Char(' ');
2005             break;
2006 
2007         case LocalDate:
2008         {
2009             QLocale l;
2010             if (d->dateOnly())
2011                 result = l.toString(d->date(), QLocale::ShortFormat);
2012             else
2013                 result = l.toString(d->dt(), QLocale::ShortFormat);
2014             if (result.isEmpty() || d->specType == LocalZone)
2015                 return result;
2016             result += QLatin1Char(' ');
2017             break;
2018         }
2019         default:
2020             return result;
2021     }
2022 
2023     // Return the string with UTC offset ±hhmm appended
2024     if (d->specType == OffsetFromUTC)
2025         offset =  d->spec().utcOffset();
2026     else if (d->specType == TimeZone || d->specType == LocalZone)
2027     {
2028         QTimeZone local;
2029         offset = d->timeZoneOffset(local);   // calculate offset and cache UTC value
2030     }
2031     if (d->specType == Invalid || offset == InvalidOffset)
2032         return result + QLatin1String("+ERROR");
2033     if (offset < 0)
2034     {
2035         offset = -offset;
2036         tzsign = QStringLiteral("-");
2037     }
2038     offset /= 60;
2039     return result + tzsign + numString(offset / 60, 2)
2040                   + tzcolon + numString(offset % 60, 2);
2041 }
2042 
2043 KADateTime KADateTime::fromString(const QString& string, TimeFormat format, bool* negZero)
2044 {
2045     if (negZero)
2046         *negZero = false;
2047     const QString str = string.trimmed();
2048     if (str.isEmpty())
2049         return {};
2050 
2051     switch (format)
2052     {
2053         case RFCDateDay: // format is Wdy, DD Mon YYYY hh:mm:ss ±hhmm
2054         case RFCDate:    // format is [Wdy,] DD Mon YYYY hh:mm[:ss] ±hhmm
2055         {
2056             int nyear  = 6;   // indexes within string to values
2057             int nmonth = 4;
2058             int nday   = 2;
2059             int nwday  = 1;
2060             int nhour  = 7;
2061             int nmin   = 8;
2062             int nsec   = 9;
2063             // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
2064             static const QRegularExpression rx1(QStringLiteral(R"(^(?:([A-Z][a-z]+),\s*)?(\d{1,2})(\s+|-)([^-\s]+)(\s+|-)(\d{2,4})\s+(\d\d):(\d\d)(?::(\d\d))?\s+(\S+)$)"));
2065             const QRegularExpressionMatch match1 = rx1.match(str);
2066             QStringList parts_;
2067             if (match1.hasMatch())
2068             {
2069                 // Check that if date has '-' separators, both separators are '-'.
2070                 parts_ = match1.capturedTexts();
2071                 bool h1 = (parts_.at(3) == QLatin1String("-"));
2072                 bool h2 = (parts_.at(5) == QLatin1String("-"));
2073                 if (h1 != h2)
2074                     break;
2075             }
2076             else
2077             {
2078                 // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
2079                 static const QRegularExpression rx2(QStringLiteral(R"(^([A-Z][a-z]+)\s+(\S+)\s+(\d\d)\s+(\d\d):(\d\d):(\d\d)\s+(\d\d\d\d)$)"));
2080                 const QRegularExpressionMatch match2 = rx2.match(str);
2081                 QStringList parts_;
2082                 if (!match2.hasMatch())
2083                     break;
2084                 nyear  = 7;
2085                 nmonth = 2;
2086                 nday   = 3;
2087                 nwday  = 1;
2088                 nhour  = 4;
2089                 nmin   = 5;
2090                 nsec   = 6;
2091                 parts_ = match2.capturedTexts();
2092             }
2093             bool ok[4];
2094             const QStringList& parts(parts_);
2095             int day    = parts[nday].toInt(&ok[0]);
2096             int year   = parts[nyear].toInt(&ok[1]);
2097             int hour   = parts[nhour].toInt(&ok[2]);
2098             int minute = parts[nmin].toInt(&ok[3]);
2099             if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
2100                 break;
2101             int second = 0;
2102             if (!parts[nsec].isEmpty())
2103             {
2104                 second = parts[nsec].toInt(&ok[0]);
2105                 if (!ok[0])
2106                     break;
2107             }
2108             bool leapSecond = (second == 60);
2109             if (leapSecond)
2110                 second = 59;    // apparently a leap second - validate below, once time zone is known
2111             int month = 0;
2112             for (; month < 12 && parts[nmonth] != shortMonth(month + 1); ++month) {}
2113             int dayOfWeek = -1;
2114             if (!parts[nwday].isEmpty())
2115             {
2116                 // Look up the weekday name
2117                 while (++dayOfWeek < 7 && shortDay(dayOfWeek + 1) != parts[nwday]) {}
2118                 if (dayOfWeek >= 7)
2119                 {
2120                     for (dayOfWeek = 0; dayOfWeek < 7 && longDay(dayOfWeek + 1) != parts[nwday]; ++dayOfWeek) {}
2121                 }
2122             }
2123             if (month >= 12 || dayOfWeek >= 7 || (dayOfWeek < 0 && format == RFCDateDay))
2124                 break;
2125             int i = parts[nyear].size();
2126             if (i < 4)
2127             {
2128                 // It's an obsolete year specification with less than 4 digits
2129                 year += (i == 2  &&  year < 50) ? 2000 : 1900;
2130             }
2131 
2132             // Parse the UTC offset part
2133             int offset = 0;           // set default to '-0000'
2134             bool negOffset = false;
2135             if (parts.count() > 10)
2136             {
2137                 static const QRegularExpression rx(QStringLiteral(R"(^([+-])(\d\d)(\d\d)$)"));
2138                 const QRegularExpressionMatch match = rx.match(parts[10]);
2139                 if (match.hasMatch())
2140                 {
2141                     // It's a UTC offset ±hhmm
2142                     const QStringList partsu = match.capturedTexts();
2143                     offset = partsu[2].toInt(&ok[0]) * 3600;
2144                     int offsetMin = partsu[3].toInt(&ok[1]);
2145                     if (!ok[0] || !ok[1] || offsetMin > 59)
2146                         break;
2147                     offset += offsetMin * 60;
2148                     negOffset = (partsu[1] == QLatin1String("-"));
2149                     if (negOffset)
2150                         offset = -offset;
2151                 }
2152                 else
2153                 {
2154                     // Check for an obsolete time zone name
2155                     const QByteArray zone = parts[10].toLatin1();
2156                     if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J')
2157                         negOffset = true;    // military zone: RFC 2822 treats as '-0000'
2158                     else if (zone != "UT" && zone != "GMT")
2159                     { // treated as '+0000'
2160                         offset = (zone == "EDT")                  ? -4 * 3600
2161                                : (zone == "EST" || zone == "CDT") ? -5 * 3600
2162                                : (zone == "CST" || zone == "MDT") ? -6 * 3600
2163                                : (zone == "MST" || zone == "PDT") ? -7 * 3600
2164                                : (zone == "PST")                  ? -8 * 3600
2165                                : 0;
2166                         if (!offset)
2167                         {
2168                             // Check for any other alphabetic time zone
2169                             bool nonalpha = false;
2170                             for (int j = 0, end = zone.size(); j < end && !nonalpha; ++j)
2171                                 nonalpha = !isalpha(zone[j]);
2172                             if (nonalpha)
2173                                 break;
2174                             negOffset = true;    // unknown time zone: RFC 2822 treats as '-0000'
2175                         }
2176                     }
2177                 }
2178             }
2179             const QDate qdate(year, month + 1, day);
2180             if (!qdate.isValid())
2181                 break;
2182             KADateTime result(qdate, QTime(hour, minute, second), Spec(OffsetFromUTC, offset));
2183             if (!result.isValid() || (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek + 1))
2184                 break;    // invalid date/time, or weekday doesn't correspond with date
2185             if (!offset)
2186             {
2187                 if (negOffset && negZero)
2188                     *negZero = true;    // UTC offset given as "-0000"
2189                 result.setTimeSpec(UTC);
2190             }
2191             if (leapSecond)
2192             {
2193                 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
2194                 // Convert the time to UTC and check that it is 00:00:00.
2195                 if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400)   // (max abs(offset) is 100 hours)
2196                     break;    // the time isn't the last second of the day
2197             }
2198             return result;
2199         }
2200         case RFC3339Date:   // format is YYYY-MM-DDThh:mm:ss[.s]TZ
2201         {
2202             static const QRegularExpression rx(QStringLiteral(R"(^(\d{4})-(\d\d)-(\d\d)[Tt](\d\d):(\d\d):(\d\d)(?:\.(\d+))?([Zz]|([+-])(\d\d):(\d\d))$)"));
2203             const QRegularExpressionMatch match = rx.match(str);
2204             if (!match.hasMatch())
2205                 break;
2206             const QStringList parts = match.capturedTexts();
2207             bool ok;
2208             bool ok1;
2209             bool ok2;
2210             int msecs  = 0;
2211             bool leapSecond = false;
2212             int year = parts[1].toInt(&ok);
2213             int month = parts[2].toInt(&ok1);
2214             int day = parts[3].toInt(&ok2);
2215             if (!ok || !ok1 || !ok2)
2216                 break;
2217             const QDate d(year, month, day);
2218             if (!d.isValid())
2219                 break;
2220             int hour = parts[4].toInt(&ok);
2221             int minute = parts[5].toInt(&ok1);
2222             int second = parts[6].toInt(&ok2);
2223             if (!ok || !ok1 || !ok2)
2224                 break;
2225             leapSecond = (second == 60);
2226             if (leapSecond)
2227                 second = 59;    // apparently a leap second - validate below, once time zone is known
2228             if (!parts[7].isEmpty())
2229             {
2230                 QString ms = parts[7] + QLatin1String("00");
2231                 ms.truncate(3);
2232                 msecs = ms.toInt(&ok);
2233                 if (!ok)
2234                     break;
2235                 if (msecs && leapSecond)
2236                     break;    // leap second only valid if 23:59:60.000
2237             }
2238             const QTime t(hour, minute, second, msecs);
2239             if (!t.isValid())
2240                 break;
2241             int offset = 0;
2242             SpecType spec = (parts[8].toUpper() == QLatin1Char('Z')) ? UTC : OffsetFromUTC;
2243             if (spec == OffsetFromUTC)
2244             {
2245                 offset = parts[10].toInt(&ok) * 3600;
2246                 offset += parts[11].toInt(&ok1) * 60;
2247                 if (!ok || !ok1)
2248                     break;
2249                 if (parts[9] == QLatin1String("-"))
2250                 {
2251                     if (!offset && leapSecond)
2252                         break;    // leap second only valid if known time zone
2253                     offset = -offset;
2254                     if (!offset && negZero)
2255                         *negZero = true;
2256                 }
2257             }
2258             if (leapSecond)
2259             {
2260                 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
2261                 // Convert the time to UTC and check that it is 00:00:00.
2262                 if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400)   // (max abs(offset) is 100 hours)
2263                     break;    // the time isn't the last second of the day
2264             }
2265             return KADateTime(d, t, Spec(spec, offset));
2266         }
2267         case ISODate:
2268         {
2269             /*
2270              * Extended format: [±]YYYY-MM-DD[Thh[:mm[:ss.s]][TZ]]
2271              * Basic format:    [±]YYYYMMDD[Thh[mm[ss.s]][TZ]]
2272              * Extended format: [±]YYYY-DDD[Thh[:mm[:ss.s]][TZ]]
2273              * Basic format:    [±]YYYYDDD[Thh[mm[ss.s]][TZ]]
2274              * In the first three formats, the year may be expanded to more than 4 digits.
2275              *
2276              * QDateTime::fromString(Qt::ISODate) is a rather limited implementation
2277              * of parsing ISO 8601 format date/time strings, so it isn't used here.
2278              * This implementation isn't complete either, but it's better.
2279              *
2280              * ISO 8601 allows truncation, but for a combined date & time, the date part cannot
2281              * be truncated from the right, and the time part cannot be truncated from the left.
2282              * In other words, only the outer parts of the string can be omitted.
2283              * The standard does not actually define how to interpret omitted parts - it is up
2284              * to those interchanging the data to agree on a scheme.
2285              */
2286             bool dateOnly = false;
2287             // Check first for the extended format of ISO 8601
2288             static const QRegularExpression rx1(QStringLiteral(R"(^([+-])?(\d{4,})-(\d\d\d|\d\d-\d\d)[T ](\d\d)(?::(\d\d)(?::(\d\d)(?:(?:\.|,)(\d+))?)?)?(Z|([+-])(\d\d)(?::(\d\d))?)?$)"));
2289             QRegularExpressionMatch match = rx1.match(str);
2290             if (!match.hasMatch())
2291             {
2292                 // It's not the extended format - check for the basic format
2293                 static const QRegularExpression rx2(QStringLiteral(R"(^([+-])?(\d{4,})(\d{4})[T ](\d\d)(?:(\d\d)(?:(\d\d)(?:(?:\.|,)(\d+))?)?)?(Z|([+-])(\d\d)(\d\d)?)?$)"));
2294                 match = rx2.match(str);
2295                 if (!match.hasMatch())
2296                 {
2297                     static const QRegularExpression rx3(QStringLiteral(R"(^([+-])?(\d{4})(\d{3})[T ](\d\d)(?:(\d\d)(?:(\d\d)(?:(?:\.|,)(\d+))?)?)?(Z|([+-])(\d\d)(\d\d)?)?$)"));
2298                     match = rx3.match(str);
2299                     if (!match.hasMatch())
2300                     {
2301                         // Check for date-only formats
2302                         dateOnly = true;
2303                         static const QRegularExpression rx4(QStringLiteral(R"(^([+-])?(\d{4,})-(\d\d\d|\d\d-\d\d)$)"));
2304                         match = rx4.match(str);
2305                         if (!match.hasMatch())
2306                         {
2307                             // It's not the extended format - check for the basic format
2308                             static const QRegularExpression rx5(QStringLiteral("^([+-])?(\\d{4,})(\\d{4})$"));
2309                             match = rx5.match(str);
2310                             if (!match.hasMatch())
2311                             {
2312                                 static const QRegularExpression rx6(QStringLiteral("^([+-])?(\\d{4})(\\d{3})$"));
2313                                 match = rx6.match(str);
2314                                 if (!match.hasMatch())
2315                                     break;
2316                             }
2317                         }
2318                     }
2319                 }
2320             }
2321             QStringList parts1 = match.capturedTexts();
2322             parts1.resize(dateOnly ? 4 : 12);   // append any missing empty texts
2323             const QStringList parts = parts1;
2324             bool ok;
2325             bool ok1;
2326             QDate d;
2327             int hour   = 0;
2328             int minute = 0;
2329             int second = 0;
2330             int msecs  = 0;
2331             bool leapSecond = false;
2332             int year = parts[2].toInt(&ok);
2333             if (!ok)
2334                 break;
2335             if (parts[1] == QLatin1String("-"))
2336                 year = -year;
2337             if (!dateOnly)
2338             {
2339                 hour = parts[4].toInt(&ok);
2340                 if (!ok)
2341                     break;
2342                 if (!parts[5].isEmpty())
2343                 {
2344                     minute = parts[5].toInt(&ok);
2345                     if (!ok)
2346                         break;
2347                 }
2348                 if (!parts[6].isEmpty())
2349                 {
2350                     second = parts[6].toInt(&ok);
2351                     if (!ok)
2352                         break;
2353                 }
2354                 leapSecond = (second == 60);
2355                 if (leapSecond)
2356                 {
2357                     second = 59;    // apparently a leap second - validate below, once time zone is known
2358                 }
2359                 if (!parts[7].isEmpty())
2360                 {
2361                     QString ms = parts[7] + QLatin1String("00");
2362                     ms.truncate(3);
2363                     msecs = ms.toInt(&ok);
2364                     if (!ok)
2365                         break;
2366                 }
2367             }
2368             if (parts[3].length() == 3)
2369             {
2370                 // A day of the year is specified
2371                 int day = parts[3].toInt(&ok);
2372                 if (!ok || day < 1 || day > 366)
2373                     break;
2374                 d = QDate(year, 1, 1).addDays(day - 1);
2375                 if (!d.isValid() || (d.year() != year))
2376                     break;
2377                 //day   = d.day();
2378                 //month = d.month();
2379             }
2380             else
2381             {
2382                 // A month and day are specified
2383                 int month = QStringView(parts[3]).left(2).toInt(&ok);
2384                 int day   = QStringView(parts[3]).right(2).toInt(&ok1);
2385                 if (!ok || !ok1)
2386                     break;
2387                 d = QDate(year, month, day);
2388                 if (!d.isValid())
2389                     break;
2390             }
2391             if (dateOnly)
2392                 return KADateTime(d, Spec(LocalZone));
2393             if (hour == 24  && !minute && !second && !msecs)
2394             {
2395                 // A time of 24:00:00 is allowed by ISO 8601, and means midnight at the end of the day
2396                 d = d.addDays(1);
2397                 hour = 0;
2398             }
2399 
2400             QTime t(hour, minute, second, msecs);
2401             if (!t.isValid())
2402                 break;
2403             if (parts[8].isEmpty())
2404             {
2405                 // No UTC offset is specified. Don't try to validate leap seconds.
2406                 return KADateTime(d, t, KADateTimePrivate::fromStringDefault());
2407             }
2408             int offset = 0;
2409             SpecType spec = (parts[8] == QLatin1Char('Z')) ? UTC : OffsetFromUTC;
2410             if (spec == OffsetFromUTC)
2411             {
2412                 offset = parts[10].toInt(&ok) * 3600;
2413                 if (!ok)
2414                     break;
2415                 if (!parts[11].isEmpty())
2416                 {
2417                     offset += parts[11].toInt(&ok) * 60;
2418                     if (!ok)
2419                         break;
2420                 }
2421                 if (parts[9] == QLatin1String("-"))
2422                 {
2423                     offset = -offset;
2424                     if (!offset && negZero)
2425                         *negZero = true;
2426                 }
2427             }
2428             if (leapSecond)
2429             {
2430                 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
2431                 // Convert the time to UTC and check that it is 00:00:00.
2432                 if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400)   // (max abs(offset) is 100 hours)
2433                     break;    // the time isn't the last second of the day
2434             }
2435             return KADateTime(d, t, Spec(spec, offset));
2436         }
2437         case QtTextDate:    // format is Wdy Mth DD [hh:mm:ss] YYYY [±hhmm]
2438         {
2439             int offset = 0;
2440             static const QRegularExpression rx(QStringLiteral(R"(^(\S+\s+\S+\s+\d\d\s+(\d\d:\d\d:\d\d\s+)?\d\d\d\d)\s*(.*)$)"));
2441             const QRegularExpressionMatch match = rx.match(str);
2442             if (!match.hasMatch())
2443                 break;
2444             const QStringList parts = match.capturedTexts();
2445             QDate     qd;
2446             QDateTime qdt;
2447             bool dateOnly = parts[2].isEmpty();
2448             if (dateOnly)
2449             {
2450                 qd = QDate::fromString(parts[1], Qt::TextDate);
2451                 if (!qd.isValid())
2452                     break;
2453             }
2454             else
2455             {
2456                 qdt = QDateTime::fromString(parts[1], Qt::TextDate);
2457                 if (!qdt.isValid())
2458                     break;
2459             }
2460             if (parts[3].isEmpty())
2461             {
2462                 // No time zone offset specified, so return a local clock time
2463                 if (dateOnly)
2464                     return KADateTime(qd, KADateTimePrivate::fromStringDefault());
2465                 else
2466                 {
2467                     // Do it this way to prevent UTC conversions changing the time
2468                     return KADateTime(qdt.date(), qdt.time(), KADateTimePrivate::fromStringDefault());
2469                 }
2470             }
2471             static const QRegularExpression rx2(QStringLiteral(R"(([+-])([\d][\d])(?::?([\d][\d]))?$)"));
2472             const QRegularExpressionMatch match2 = rx2.match(parts[3]);
2473             if (!match2.hasMatch())
2474                 break;
2475 
2476             // Extract the UTC offset at the end of the string
2477             bool ok;
2478             const QStringList parts2 = match2.capturedTexts();
2479             offset = parts2[2].toInt(&ok) * 3600;
2480             if (!ok)
2481                 break;
2482             if (parts2.count() > 3)
2483             {
2484                 offset += parts2[3].toInt(&ok) * 60;
2485                 if (!ok)
2486                     break;
2487             }
2488             if (parts2[1] == QLatin1String("-"))
2489             {
2490                 offset = -offset;
2491                 if (!offset && negZero)
2492                     *negZero = true;
2493             }
2494             if (dateOnly)
2495                 return KADateTime(qd, Spec((offset ? OffsetFromUTC : UTC), offset));
2496             return KADateTime(qdt.date(), qdt.time(), Spec((offset ? OffsetFromUTC : UTC), offset));
2497         }
2498         case LocalDate:
2499         default:
2500             break;
2501     }
2502     return {};
2503 }
2504 
2505 KADateTime KADateTime::fromString(const QString& string, const QString& format,
2506                                   const QList<QTimeZone>* zones, bool offsetIfAmbiguous)
2507 {
2508     int     utcOffset = 0;    // UTC offset in seconds
2509     bool    dateOnly = false;
2510     QString zoneName;
2511     QString zoneAbbrev;
2512     QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly);
2513     if (!qdt.isValid())
2514         return {};
2515     if (zones)
2516     {
2517         // Try to find a time zone match from the supplied list of zones
2518         bool zname = false;
2519         const QList<QTimeZone>& zoneList = *zones;
2520         QTimeZone zoneFound;
2521         if (!zoneName.isEmpty())
2522         {
2523             // A time zone name has been found.
2524             // Use the time zone with that name.
2525             const QByteArray name = zoneName.toLatin1();
2526             for (const QTimeZone& tz : zoneList)
2527             {
2528                 if (tz.id() == name)
2529                 {
2530                     zoneFound = tz;
2531                     zname = true;
2532                     break;
2533                 }
2534             }
2535         }
2536         else if (!zoneAbbrev.isEmpty())
2537         {
2538             // A time zone abbreviation has been found.
2539             // Use the time zone which contains it, if any, provided that the
2540             // abbreviation applies at the specified date/time.
2541             bool useUtcOffset = false;
2542             KADateTime kdt;
2543             for (const QTimeZone& tz : zoneList)
2544             {
2545                 if (zoneAbbrev == tz.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QLocale::c())
2546                 ||  zoneAbbrev == tz.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QLocale::c()))
2547                 {
2548                     // Found a time zone which uses this abbreviation.
2549                     // Check if it is valid at the date/time specified.
2550                     kdt = KADateTime(qdt.date(), qdt.time(), tz);
2551                     bool matches = true;
2552                     if (tz.abbreviation(kdt.qDateTime()) != zoneAbbrev)
2553                     {
2554                         kdt.setSecondOccurrence(true);
2555                         if (tz.abbreviation(kdt.qDateTime()) != zoneAbbrev)
2556                             matches = false;
2557                     }
2558                     if (matches)
2559                     {
2560                         // Found a time zone which uses this abbreviation at the specified date/time
2561                         int offset = kdt.utcOffset();
2562                         if (zoneFound.isValid())
2563                         {
2564                             // Abbreviation is used by more than one time zone
2565                             if (!offsetIfAmbiguous || offset != utcOffset)
2566                                 return {};
2567                             useUtcOffset = true;
2568                         }
2569                         else
2570                         {
2571                             zoneFound = tz;
2572                             utcOffset = offset;
2573                         }
2574                     }
2575                 }
2576             }
2577             if (useUtcOffset)
2578             {
2579                 zoneFound = QTimeZone();
2580                 if (!utcOffset)
2581                     qdt.setTimeZone(QTimeZone::utc());
2582             }
2583             else if (zoneFound.isValid())
2584             {
2585                 if (dateOnly)
2586                     kdt.setDateOnly(true);
2587                 return kdt;
2588             }
2589             else
2590                 return {};   // an unknown zone name or abbreviation was found
2591         }
2592         else if (utcOffset  ||  qTimeSpec(qdt) == Qt::UTC)
2593         {
2594             // A UTC offset has been found.
2595             // Use the time zone which contains it, if any.
2596             // For a date-only value, use the start of the day.
2597             QDateTime dtUTC = qdt;
2598             dtUTC.setTimeZone(QTimeZone::utc());
2599             dtUTC = dtUTC.addSecs(-utcOffset);
2600             for (const QTimeZone& tz : zoneList)
2601             {
2602                 if (tz.offsetFromUtc(dtUTC) == utcOffset)
2603                 {
2604                     // Found a time zone which uses this offset at the specified time
2605                     if (zoneFound.isValid()  ||  !utcOffset)
2606                     {
2607                         // UTC offset is used by more than one time zone
2608                         if (!offsetIfAmbiguous)
2609                             return {};
2610                         if (dateOnly)
2611                             return KADateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset));
2612                         return KADateTime(qdt.date(), qdt.time(), Spec(OffsetFromUTC, utcOffset));
2613                     }
2614                     zoneFound = tz;
2615                 }
2616             }
2617         }
2618         if (!zoneFound.isValid() && zname)
2619             return {};   // an unknown zone name or abbreviation was found
2620         if (zoneFound.isValid())
2621         {
2622             if (dateOnly)
2623                 return KADateTime(qdt.date(), Spec(zoneFound));
2624             return KADateTime(qdt.date(), qdt.time(), Spec(zoneFound));
2625         }
2626     }
2627     else
2628     {
2629         // Try to find a time zone match with the system zones
2630         bool zname = false;
2631         QTimeZone zoneFound;
2632         if (!zoneName.isEmpty())
2633         {
2634             // A time zone name has been found.
2635             // Use the time zone with that name.
2636             zoneFound = QTimeZone(zoneName.toLatin1());
2637             zname = true;
2638         }
2639         else if (!zoneAbbrev.isEmpty())
2640         {
2641             // A time zone abbreviation has been found.
2642             // Use the time zone which contains it, if any, provided that the
2643             // abbreviation applies at the specified date/time.
2644             bool useUtcOffset = false;
2645             KADateTime kdt;
2646             const QList<QByteArray> zoneIds = QTimeZone::availableTimeZoneIds();
2647             for (const QByteArray& zoneId : zoneIds)
2648             {
2649                 const QTimeZone tz(zoneId);
2650                 if (zoneAbbrev == tz.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QLocale::c())
2651                 ||  zoneAbbrev == tz.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QLocale::c()))
2652                 {
2653                     // Found a time zone which uses this abbreviation.
2654                     // Check if it is valid at the date/time specified.
2655                     kdt = KADateTime(qdt.date(), qdt.time(), tz);
2656                     bool matches = true;
2657                     if (tz.abbreviation(kdt.qDateTime()) != zoneAbbrev)
2658                     {
2659                         kdt.setSecondOccurrence(true);
2660                         if (tz.abbreviation(kdt.qDateTime()) != zoneAbbrev)
2661                             matches = false;
2662                     }
2663                     if (matches)
2664                     {
2665                         // Found a time zone which uses this abbreviation at the specified date/time
2666                         int offset = kdt.utcOffset();
2667                         if (zoneFound.isValid())
2668                         {
2669                             // Abbreviation is used by more than one time zone
2670                             if (!offsetIfAmbiguous || offset != utcOffset)
2671                                 return {};
2672                             useUtcOffset = true;
2673                         }
2674                         else
2675                         {
2676                             zoneFound = tz;
2677                             utcOffset = offset;
2678                         }
2679                     }
2680                 }
2681             }
2682             if (useUtcOffset)
2683             {
2684                 zoneFound = QTimeZone();
2685                 if (!utcOffset)
2686                     qdt.setTimeZone(QTimeZone::utc());
2687             }
2688             else if (zoneFound.isValid())
2689             {
2690                 if (dateOnly)
2691                     kdt.setDateOnly(true);
2692                 return kdt;
2693             }
2694             else
2695                 return {};   // an unknown zone name or abbreviation was found
2696         }
2697         else if (utcOffset  ||  qTimeSpec(qdt) == Qt::UTC)
2698         {
2699             // A UTC offset has been found.
2700             // Use the time zone which contains it, if any.
2701             // For a date-only value, use the start of the day.
2702             QDateTime dtUTC = qdt;
2703             dtUTC.setTimeZone(QTimeZone::utc());
2704             dtUTC = dtUTC.addSecs(-utcOffset);
2705             const QList<QByteArray> zoneIds = QTimeZone::availableTimeZoneIds();
2706             for (const QByteArray& zoneId : zoneIds)
2707             {
2708                 const QTimeZone z(zoneId);
2709                 if (z.offsetFromUtc(dtUTC) == utcOffset)
2710                 {
2711                     // Found a time zone which uses this offset at the specified time
2712                     if (zoneFound.isValid()  ||  !utcOffset)
2713                     {
2714                         // UTC offset is used by more than one time zone
2715                         if (!offsetIfAmbiguous)
2716                             return {};
2717                         if (dateOnly)
2718                             return KADateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset));
2719                         return KADateTime(qdt.date(), qdt.time(), Spec(OffsetFromUTC, utcOffset));
2720                     }
2721                     zoneFound = z;
2722                 }
2723             }
2724         }
2725         if (!zoneFound.isValid() && zname)
2726             return {};   // an unknown zone name or abbreviation was found
2727         if (zoneFound.isValid())
2728         {
2729             if (dateOnly)
2730                 return KADateTime(qdt.date(), Spec(zoneFound));
2731             return KADateTime(qdt.date(), qdt.time(), Spec(zoneFound));
2732         }
2733     }
2734 
2735     // No time zone match was found
2736     KADateTime result;
2737     if (utcOffset)
2738         result = KADateTime(qdt.date(), qdt.time(), Spec(OffsetFromUTC, utcOffset));
2739     else if (qTimeSpec(qdt) == Qt::UTC)
2740         result = KADateTime(qdt.date(), qdt.time(), UTC);
2741     else
2742     {
2743         result = KADateTime(qdt.date(), qdt.time(), Spec(LocalZone));
2744         result.setTimeSpec(KADateTimePrivate::fromStringDefault());
2745     }
2746     if (dateOnly)
2747         result.setDateOnly(true);
2748     return result;
2749 }
2750 
2751 void KADateTime::setFromStringDefault(const Spec& spec)
2752 {
2753     KADateTimePrivate::fromStringDefault() = spec;
2754 }
2755 
2756 void KADateTime::setSimulatedSystemTime(const KADateTime& newTime)
2757 {
2758     Q_UNUSED(newTime)
2759 #ifdef SIMULATION
2760 #ifndef NDEBUG
2761     if (newTime.isValid())
2762     {
2763         KADateTimePrivate::simulationOffset = realCurrentLocalDateTime().secsTo_long(newTime);
2764         KADateTimePrivate::simulationLocalZone = newTime.timeZone();
2765     }
2766     else
2767     {
2768         KADateTimePrivate::simulationOffset = 0;
2769         KADateTimePrivate::simulationLocalZone = QTimeZone();
2770     }
2771 #endif
2772 #endif
2773 }
2774 
2775 KADateTime KADateTime::realCurrentLocalDateTime()
2776 {
2777     return KADateTime(QDateTime::currentDateTime(), Spec(QTimeZone::systemTimeZone()));
2778 }
2779 
2780 QDataStream& operator<<(QDataStream& s, const KADateTime& dt)
2781 {
2782     s << dt.date() << dt.time() << dt.timeSpec() << quint8(dt.isDateOnly() ? 0x01 : 0x00);
2783     return s;
2784 }
2785 
2786 QDataStream& operator>>(QDataStream& s, KADateTime& kdt)
2787 {
2788     QDate d;
2789     QTime t;
2790     KADateTime::Spec spec;
2791     quint8 flags;
2792     s >> d >> t >> spec >> flags;
2793     if (flags & 0x01)
2794         kdt = KADateTime(d, spec);
2795     else
2796         kdt = KADateTime(d, t, spec);
2797     return s;
2798 }
2799 
2800 } // namespace KAlarmCal
2801 
2802 using KAlarmCal::KADateTime;
2803 
2804 namespace
2805 {
2806 
2807 /******************************************************************************
2808 * Extracts a QDateTime from a string, given a format string.
2809 * The date/time is set to Qt::UTC if a zero UTC offset is found,
2810 * otherwise it is Qt::LocalTime. If Qt::LocalTime is returned and
2811 * utcOffset == 0, that indicates that no UTC offset was found.
2812 */
2813 QDateTime fromStr(const QString& string, const QString& format, int& utcOffset,
2814                   QString& zoneName, QString& zoneAbbrev, bool& dateOnly)
2815 {
2816     const QString str = string.simplified();
2817     int year      = NO_NUMBER;
2818     int month     = NO_NUMBER;
2819     int day       = NO_NUMBER;
2820     int dayOfWeek = NO_NUMBER;
2821     int hour      = NO_NUMBER;
2822     int minute    = NO_NUMBER;
2823     int second    = NO_NUMBER;
2824     int millisec  = NO_NUMBER;
2825     int ampm      = NO_NUMBER;
2826     int tzoffset  = NO_NUMBER;
2827     zoneName.clear();
2828     zoneAbbrev.clear();
2829 
2830     enum { TZNone, UTCOffset, UTCOffsetColon, TZAbbrev, TZName };
2831     int s = 0;
2832     int send = str.length();
2833     bool escape = false;
2834     ushort flag = 0;
2835     for (int f = 0, fend = format.length();  f < fend && s < send;  ++f)
2836     {
2837         int zone = TZNone;
2838         ushort ch = format[f].unicode();
2839         if (!escape)
2840         {
2841             if (ch == '%')
2842                 escape = true;
2843             else if (format[f].isSpace())
2844             {
2845                 if (str[s].isSpace())
2846                     ++s;
2847             }
2848             else if (format[f] == str[s])
2849                 ++s;
2850             else
2851                 return {};
2852             continue;
2853         }
2854         if (!flag)
2855         {
2856             switch (ch)
2857             {
2858                 case '%':
2859                     if (str[s++] != QLatin1Char('%'))
2860                         return {};
2861                     break;
2862                 case ':':
2863                     flag = ch;
2864                     break;
2865                 case 'Y':     // full year, 4 digits
2866                     if (!getNumber(str, s, 4, 4, NO_NUMBER, -1, year))
2867                         return {};
2868                     break;
2869                 case 'y':     // year, 2 digits
2870                     if (!getNumber(str, s, 2, 2, 0, 99, year))
2871                         return {};
2872                     year += (year <= 50) ? 2000 : 1999;
2873                     break;
2874                 case 'm':     // month, 2 digits, 01 - 12
2875                     if (!getNumber(str, s, 2, 2, 1, 12, month))
2876                         return {};
2877                     break;
2878                 case 'B':
2879                 case 'b':     // month name, translated or English
2880                 {
2881                     int m = matchMonth(str, s, true);
2882                     if (m <= 0 || (month != NO_NUMBER && month != m))
2883                         return {};
2884                     month = m;
2885                     break;
2886                 }
2887                 case 'd':     // day of month, 2 digits, 01 - 31
2888                     if (!getNumber(str, s, 2, 2, 1, 31, day))
2889                         return {};
2890                     break;
2891                 case 'e':     // day of month, 1 - 31
2892                     if (!getNumber(str, s, 1, 2, 1, 31, day))
2893                         return {};
2894                     break;
2895                 case 'A':
2896                 case 'a':     // week day name, translated or English
2897                 {
2898                     int dow = matchDay(str, s, true);
2899                     if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow))
2900                         return {};
2901                     dayOfWeek = dow;
2902                     break;
2903                 }
2904                 case 'H':     // hour, 2 digits, 00 - 23
2905                     if (!getNumber(str, s, 2, 2, 0, 23, hour))
2906                         return {};
2907                     break;
2908                 case 'k':     // hour, 0 - 23
2909                     if (!getNumber(str, s, 1, 2, 0, 23, hour))
2910                         return {};
2911                     break;
2912                 case 'I':     // hour, 2 digits, 01 - 12
2913                     if (!getNumber(str, s, 2, 2, 1, 12, hour))
2914                         return {};
2915                     break;
2916                 case 'l':     // hour, 1 - 12
2917                     if (!getNumber(str, s, 1, 2, 1, 12, hour))
2918                         return {};
2919                     break;
2920                 case 'M':     // minutes, 2 digits, 00 - 59
2921                     if (!getNumber(str, s, 2, 2, 0, 59, minute))
2922                         return {};
2923                     break;
2924                 case 'S':     // seconds, 2 digits, 00 - 59
2925                     if (!getNumber(str, s, 2, 2, 0, 59, second))
2926                         return {};
2927                     break;
2928                 case 's':     // seconds, 0 - 59
2929                     if (!getNumber(str, s, 1, 2, 0, 59, second))
2930                         return {};
2931                     break;
2932                 case 'P':
2933                 case 'p':     // am/pm
2934                 {
2935                     int ap = getAmPm(str, s, true);
2936                     if (!ap || (ampm != NO_NUMBER && ampm != ap))
2937                         return {};
2938                     ampm = ap;
2939                     break;
2940                 }
2941                 case 'z':     // UTC offset in hours and optionally minutes
2942                     zone = UTCOffset;
2943                     break;
2944                 case 'Z':     // time zone abbreviation
2945                     zone = TZAbbrev;
2946                     break;
2947                 case 't':     // whitespace
2948                     if (str[s++] != QLatin1Char(' '))
2949                         return {};
2950                     break;
2951                 default:
2952                     if (s + 2 > send || str[s++] != QLatin1Char('%') || str[s++] != format[f])
2953                         return {};
2954                     break;
2955             }
2956         }
2957         else if (flag == ':')
2958         {
2959             // It's a "%:" sequence
2960             switch (ch)
2961             {
2962                 case 'Y':     // full year, >= 4 digits
2963                     if (!getNumber(str, s, 4, 100, NO_NUMBER, -1, year))
2964                         return {};
2965                     break;
2966                 case 'A':
2967                 case 'a':     // week day name in English
2968                 {
2969                     int dow = matchDay(str, s, false);
2970                     if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow))
2971                         return {};
2972                     dayOfWeek = dow;
2973                     break;
2974                 }
2975                 case 'B':
2976                 case 'b':     // month name in English
2977                 {
2978                     int m = matchMonth(str, s, false);
2979                     if (m <= 0 || (month != NO_NUMBER && month != m))
2980                         return {};
2981                     month = m;
2982                     break;
2983                 }
2984                 case 'm':     // month, 1 - 12
2985                     if (!getNumber(str, s, 1, 2, 1, 12, month))
2986                         return {};
2987                     break;
2988                 case 'P':
2989                 case 'p':     // am/pm in English
2990                 {
2991                     int ap = getAmPm(str, s, false);
2992                     if (!ap || (ampm != NO_NUMBER && ampm != ap))
2993                         return {};
2994                     ampm = ap;
2995                     break;
2996                 }
2997                 case 'M':     // minutes, 0 - 59
2998                     if (!getNumber(str, s, 1, 2, 0, 59, minute))
2999                         return {};
3000                     break;
3001                 case 'S':     // seconds with ':' prefix, defaults to zero
3002                     if (str[s] != QLatin1Char(':'))
3003                     {
3004                         second = 0;
3005                         break;
3006                     }
3007                     ++s;
3008                     if (!getNumber(str, s, 1, 2, 0, 59, second))
3009                         return {};
3010                     break;
3011                 case 's':     // milliseconds, with decimal point prefix
3012                 {
3013                     if (str[s] != QLatin1Char('.'))
3014                     {
3015                         // If no locale, try comma, it is preferred by ISO8601 as the decimal point symbol
3016                         const QString dpt = QLocale().decimalPoint();
3017                         if (!QStringView(str).mid(s).startsWith(dpt))
3018                             return {};
3019                     }
3020                     ++s;
3021                     if (s >= send)
3022                         return {};
3023                     QString val = str.mid(s);
3024                     int i = 0;
3025                     for (int end = val.length(); i < end && val.at(i).isDigit(); ++i) {}
3026                     if (!i)
3027                         return {};
3028                     val.truncate(i);
3029                     val += QLatin1String("00");
3030                     val.truncate(3);
3031                     int ms = val.toInt();
3032                     if (millisec != NO_NUMBER && millisec != ms)
3033                         return {};
3034                     millisec = ms;
3035                     s += i;
3036                     break;
3037                 }
3038                 case 'u':     // UTC offset in hours and optionally minutes
3039                     zone = UTCOffset;
3040                     break;
3041                 case 'z':     // UTC offset in hours and minutes, with colon
3042                     zone = UTCOffsetColon;
3043                     break;
3044                 case 'Z':     // time zone name
3045                     zone = TZName;
3046                     break;
3047                 default:
3048                     if (s + 3 > send || str[s++] != QLatin1Char('%') || str[s++] != QLatin1Char(':') || str[s++] != format[f])
3049                         return {};
3050                     break;
3051             }
3052             flag = 0;
3053         }
3054         if (!flag)
3055             escape = false;
3056 
3057         if (zone != TZNone)
3058         {
3059             // Read time zone or UTC offset
3060             switch (zone)
3061             {
3062                 case UTCOffset:
3063                 case UTCOffsetColon:
3064                     if (!zoneAbbrev.isEmpty() || !zoneName.isEmpty())
3065                         return {};
3066                     if (!getUTCOffset(str, s, (zone == UTCOffsetColon), tzoffset))
3067                         return {};
3068                     break;
3069                 case TZAbbrev:     // time zone abbreviation
3070                 {
3071                     if (tzoffset != NO_NUMBER || !zoneName.isEmpty())
3072                         return {};
3073                     int start = s;
3074                     while (s < send && str[s].isLetterOrNumber())
3075                         ++s;
3076                     if (s == start)
3077                         return {};
3078                     const QString z = str.mid(start, s - start);
3079                     if (!zoneAbbrev.isEmpty() && z != zoneAbbrev)
3080                         return {};
3081                     zoneAbbrev = z;
3082                     break;
3083                 }
3084                 case TZName:       // time zone name
3085                 {
3086                     if (tzoffset != NO_NUMBER || !zoneAbbrev.isEmpty())
3087                         return {};
3088                     QString z;
3089                     if (f + 1 >= fend)
3090                     {
3091                         z = str.mid(s);
3092                         s = send;
3093                     }
3094                     else
3095                     {
3096                         // Get the terminating character for the zone name
3097                         QChar endchar = format[f + 1];
3098                         if (endchar == QLatin1Char('%')  &&  f + 2 < fend)
3099                         {
3100                             const QChar endchar2 = format[f + 2];
3101                             if (endchar2 == QLatin1Char('n') || endchar2 == QLatin1Char('t'))
3102                                 endchar = QLatin1Char(' ');
3103                         }
3104                         // Extract from the input string up to the terminating character
3105                         int start = s;
3106                         for (; s < send && str[s] != endchar; ++s) {}
3107                         if (s == start)
3108                             return {};
3109                         z = str.mid(start, s - start);
3110                     }
3111                     if (!zoneName.isEmpty() && z != zoneName)
3112                         return {};
3113                     zoneName = z;
3114                     break;
3115                 }
3116                 default:
3117                     break;
3118             }
3119         }
3120     }
3121 
3122     if (year == NO_NUMBER)
3123         year = KADateTime::currentLocalDate().year();
3124     if (month == NO_NUMBER)
3125         month = 1;
3126     QDate d = QDate(year, month, (day > 0 ? day : 1));
3127     if (!d.isValid())
3128         return {};
3129     if (dayOfWeek != NO_NUMBER)
3130     {
3131         if (day == NO_NUMBER)
3132         {
3133             day = 1 + dayOfWeek - QDate(year, month, 1).dayOfWeek();
3134             if (day <= 0)
3135                 day += 7;
3136         }
3137         else
3138         {
3139             if (QDate(year, month, day).dayOfWeek() != dayOfWeek)
3140                 return {};
3141         }
3142     }
3143     dateOnly = (hour == NO_NUMBER && minute == NO_NUMBER && second == NO_NUMBER && millisec == NO_NUMBER);
3144     if (hour == NO_NUMBER)
3145         hour = 0;
3146     if (minute == NO_NUMBER)
3147         minute = 0;
3148     if (second == NO_NUMBER)
3149         second = 0;
3150     if (millisec == NO_NUMBER)
3151         millisec = 0;
3152     if (ampm != NO_NUMBER)
3153     {
3154         if (!hour || hour > 12)
3155             return {};
3156         if (ampm == 1 && hour == 12)
3157             hour = 0;
3158         else if (ampm == 2 && hour < 12)
3159             hour += 12;
3160     }
3161 
3162     QDateTime dt(d, QTime(hour, minute, second, millisec), (tzoffset == 0 ? Qt::UTC : Qt::LocalTime));
3163 
3164     utcOffset = (tzoffset == NO_NUMBER) ? 0 : tzoffset * 60;
3165 
3166     return dt;
3167 }
3168 
3169 /******************************************************************************
3170 * Find which day name matches the specified part of a string.
3171 * 'offset' is incremented by the length of the match.
3172 * Reply = day number (1 - 7), or <= 0 if no match.
3173 */
3174 int matchDay(const QString& string, int& offset, bool localised)
3175 {
3176     int dayOfWeek;
3177     const QString part = string.mid(offset);
3178     if (part.isEmpty())
3179         return -1;
3180     if (localised)
3181     {
3182         // Check for localised day name first
3183         const QLocale locale;
3184         for (dayOfWeek = 1;  dayOfWeek <= 7;  ++dayOfWeek)
3185         {
3186             const QString name = locale.dayName(dayOfWeek, QLocale::LongFormat);
3187             if (part.startsWith(name, Qt::CaseInsensitive))
3188             {
3189                 offset += name.length();
3190                 return dayOfWeek;
3191             }
3192         }
3193         for (dayOfWeek = 1;  dayOfWeek <= 7;  ++dayOfWeek)
3194         {
3195             const QString name = locale.dayName(dayOfWeek, QLocale::ShortFormat);
3196             if (part.startsWith(name, Qt::CaseInsensitive))
3197             {
3198                 offset += name.length();
3199                 return dayOfWeek;
3200             }
3201         }
3202     }
3203 
3204     // Check for English day name
3205     dayOfWeek = findString(part, longDay, 7, offset);
3206     if (dayOfWeek <= 0)
3207         dayOfWeek = findString(part, shortDay, 7, offset);
3208     return dayOfWeek;
3209 }
3210 
3211 /******************************************************************************
3212 * Find which month name matches the specified part of a string.
3213 * 'offset' is incremented by the length of the match.
3214 * Reply = month number (1 - 12), or <= 0 if no match.
3215 */
3216 int matchMonth(const QString& string, int& offset, bool localised)
3217 {
3218     int month;
3219     const QString part = string.mid(offset);
3220     if (part.isEmpty())
3221         return -1;
3222     if (localised)
3223     {
3224         // Check for localised month name first
3225         const QLocale locale;
3226         for (month = 1;  month <= 12;  ++month)
3227         {
3228             const QString name = locale.monthName(month, QLocale::LongFormat);
3229             if (part.startsWith(name, Qt::CaseInsensitive))
3230             {
3231                 offset += name.length();
3232                 return month;
3233             }
3234         }
3235         for (month = 1;  month <= 12;  ++month)
3236         {
3237             const QString name = locale.monthName(month, QLocale::ShortFormat);
3238             if (part.startsWith(name, Qt::CaseInsensitive))
3239             {
3240                 offset += name.length();
3241                 return month;
3242             }
3243         }
3244     }
3245     // Check for English month name
3246     month = findString(part, longMonth, 12, offset);
3247     if (month <= 0)
3248         month = findString(part, shortMonth, 12, offset);
3249     return month;
3250 }
3251 
3252 /******************************************************************************
3253 * Read a UTC offset from the input string.
3254 */
3255 bool getUTCOffset(const QString& string, int& offset, bool colon, int& result)
3256 {
3257     int sign;
3258     int len = string.length();
3259     if (offset >= len)
3260         return false;
3261     switch (string[offset++].unicode())
3262     {
3263         case '+':
3264             sign = 1;
3265             break;
3266         case '-':
3267             sign = -1;
3268             break;
3269         default:
3270             return false;
3271     }
3272     int tzhour = NO_NUMBER;
3273     int tzmin  = NO_NUMBER;
3274     if (!getNumber(string, offset, 2, 2, 0, 99, tzhour))
3275         return false;
3276     if (colon)
3277     {
3278         if (offset >= len || string[offset++] != QLatin1Char(':'))
3279             return false;
3280     }
3281     if (offset >= len  ||  !string[offset].isDigit())
3282         tzmin = 0;
3283     else
3284     {
3285         if (!getNumber(string, offset, 2, 2, 0, 59, tzmin))
3286             return false;
3287     }
3288     tzmin += tzhour * 60;
3289     if (result != NO_NUMBER && result != tzmin)
3290         return false;
3291     result = sign * tzmin;
3292     return true;
3293 }
3294 
3295 /******************************************************************************
3296 * Read an am/pm indicator from the input string.
3297 * 'offset' is incremented by the length of the match.
3298 * Reply = 1 (am), 2 (pm), or 0 if no match.
3299 */
3300 int getAmPm(const QString& string, int& offset, bool localised)
3301 {
3302     QString part = string.mid(offset);
3303     int ap = 0;
3304     int n = 2;
3305     if (localised)
3306     {
3307         // Check localised form first
3308         const QLocale locale;
3309         QString aps = locale.amText();
3310         if (part.startsWith(aps, Qt::CaseInsensitive))
3311         {
3312             ap = 1;
3313             n = aps.length();
3314         }
3315         else
3316         {
3317             aps = locale.pmText();
3318             if (part.startsWith(aps, Qt::CaseInsensitive))
3319             {
3320                 ap = 2;
3321                 n = aps.length();
3322             }
3323         }
3324     }
3325     if (!ap)
3326     {
3327         if (part.startsWith(QLatin1String("am"), Qt::CaseInsensitive))
3328             ap = 1;
3329         else if (part.startsWith(QLatin1String("pm"), Qt::CaseInsensitive))
3330             ap = 2;
3331     }
3332     if (ap)
3333         offset += n;
3334     return ap;
3335 }
3336 
3337 /******************************************************************************
3338 * Convert part of 'string' to a number.
3339 * If converted number differs from any current value in 'result', the function fails.
3340 * Reply = true if successful.
3341 */
3342 bool getNumber(const QString& string, int& offset, int mindigits, int maxdigits, int minval, int maxval, int& result)
3343 {
3344     int end = string.size();
3345     bool neg = false;
3346     if (minval == NO_NUMBER  &&  offset < end  &&  string[offset] == QLatin1Char('-'))
3347     {
3348         neg = true;
3349         ++offset;
3350     }
3351     if (offset + maxdigits > end)
3352         maxdigits = end - offset;
3353     int ndigits;
3354     for (ndigits = 0; ndigits < maxdigits && string[offset + ndigits].isDigit(); ++ndigits) {}
3355     if (ndigits < mindigits)
3356         return false;
3357     bool ok;
3358     int n = QStringView(string).mid(offset, ndigits).toInt(&ok);
3359     if (neg)
3360         n = -n;
3361     if (!ok || (result != NO_NUMBER && n != result) || (minval != NO_NUMBER && n < minval) || (n > maxval && maxval >= 0))
3362         return false;
3363     result = n;
3364     offset += ndigits;
3365     return true;
3366 }
3367 
3368 int findString(const QString& string, DayMonthName func, int count, int& offset)
3369 {
3370     for (int i = 1; i <= count; ++i)
3371     {
3372         if (string.startsWith(func(i), Qt::CaseInsensitive))
3373         {
3374             offset += func(i).size();
3375             return i;
3376         }
3377     }
3378     return -1;
3379 }
3380 
3381 QString numString(int n, int width)
3382 {
3383   return QStringLiteral("%1").arg(n, width, 10, QLatin1Char('0'));
3384 }
3385 
3386 // Return the UTC offset in a given time zone, for a specified date/time.
3387 int offsetAtZoneTime(const QTimeZone& tz, const QDateTime& zoneDateTime, int* secondOffset)
3388 {
3389     if (!zoneDateTime.isValid())  // check for invalid time
3390     {
3391         if (secondOffset)
3392             *secondOffset = InvalidOffset;
3393         return InvalidOffset;
3394     }
3395     switch (qTimeSpec(zoneDateTime))
3396     {
3397         case Qt::LocalTime:
3398         case Qt::TimeZone:
3399             break;
3400         default:
3401             if (secondOffset)
3402                 *secondOffset = InvalidOffset;
3403             return InvalidOffset;
3404     }
3405     const int offset = tz.offsetFromUtc(zoneDateTime);
3406     if (secondOffset)
3407     {
3408         // Check if there is a daylight savings shift around zoneDateTime.
3409         const QDateTime utc = QDateTime(zoneDateTime.date(), zoneDateTime.time(), Qt::UTC).addSecs(-offset);
3410         QTimeZone::OffsetData transition;
3411         int step = checkTzTransitionBackwards(transition, tz, utc, zoneDateTime);
3412         if (step < 0)
3413         {
3414             // The local time occurs twice.
3415             *secondOffset = transition.offsetFromUtc;
3416             return transition.offsetFromUtc - step;
3417         }
3418         *secondOffset = offset;
3419     }
3420     return offset;
3421 }
3422 
3423 // Convert a UTC date/time to a time zone date/time.
3424 QDateTime toZoneTime(const QTimeZone& tz, const QDateTime& utcDateTime, bool* secondOccurrence)
3425 {
3426     if (secondOccurrence)
3427         *secondOccurrence = false;
3428     if (!utcDateTime.isValid() || qTimeSpec(utcDateTime) != Qt::UTC)
3429         return {};
3430     const QDateTime dt = utcDateTime.toTimeZone(tz);
3431     if (secondOccurrence)
3432     {
3433         QTimeZone::OffsetData transition;
3434         if (checkTzTransitionBackwards(transition, tz, utcDateTime, dt) < 0)
3435         {
3436             // The local time occurs twice.
3437             *secondOccurrence = (utcDateTime >= transition.atUtc);
3438         }
3439         else
3440             *secondOccurrence = false;
3441     }
3442     return dt;
3443 }
3444 
3445 /******************************************************************************
3446 * Check whether the local time occurs twice around a daylight savings time
3447 * shift, and if so, determine whether it is the first or second occurrence
3448 * according to the daylight savings flag in dt.
3449 */
3450 bool checkTzTransitionOccurrence(const QDateTime& dt, const QDateTime& utcDateTime)
3451 {
3452     if (qTimeSpec(dt) == Qt::TimeZone)
3453     {
3454         // Check if there is a daylight savings shift around utcDateTime.
3455         // If the local time occurs twice around a time shift, the UTC time
3456         // could be either the first or second occurrence.
3457         QTimeZone::OffsetData transition;
3458         if (checkTzTransitionBackwards(transition, dt.timeZone(), utcDateTime, dt) < 0)
3459         {
3460             // The local time occurs twice.
3461             return (utcDateTime >= transition.atUtc);
3462         }
3463     }
3464     return false;
3465 }
3466 
3467 /******************************************************************************
3468 * Check whether the local time corresponding to a UTC time occurs twice around
3469 * a daylight savings time shift.
3470 * Parameters:
3471 *   transition - receives the transition for the time shift, if reply < 0.
3472 *                transition.atUtc = UTC time of transition.
3473 * Reply = change in UTC offset from DST to standard time (< 0);
3474 *       = 0 if local time does not occur twice.
3475 */
3476 int checkTzTransitionBackwards(QTimeZone::OffsetData& transition, const QTimeZone& tz, const QDateTime& utcDateTime, const QDateTime& tzDateTime)
3477 {
3478     // Check if there is a daylight savings shift around utcDateTime.
3479     const QList<QTimeZone::OffsetData> transitions = tz.transitions(utcDateTime.addSecs(-10800), utcDateTime.addSecs(7200));
3480     if (!transitions.isEmpty())
3481     {
3482         // Assume that there will only be one transition in a 4 hour period.
3483         const QTimeZone::OffsetData before = tz.previousTransition(transitions[0].atUtc);
3484         if (before.atUtc.isValid()  &&  transitions[0].atUtc.isValid())
3485         {
3486             const int step = before.offsetFromUtc - transitions[0].offsetFromUtc;
3487             if (step > 0)
3488             {
3489                 // The transition steps the local time backwards, so check for
3490                 // a second occurrence of the local time.
3491                 // Find the local time when the transition occurs.
3492                 const QDateTime changeStart = transitions[0].atUtc.addSecs(transitions[0].offsetFromUtc);
3493                 const QDateTime changeEnd   = transitions[0].atUtc.addSecs(before.offsetFromUtc);
3494                 QDateTime dtTz = tzDateTime.isValid() ? tzDateTime : utcDateTime.toTimeZone(tz);
3495                 dtTz.setTimeZone(QTimeZone::utc());
3496                 if (dtTz >= changeStart  &&  dtTz < changeEnd)
3497                 {
3498                     // The local time occurs twice.
3499                     transition = transitions[0];
3500                     return -step;
3501                 }
3502             }
3503         }
3504     }
3505     return 0;
3506 }
3507 
3508 void initDayMonthNames()
3509 {
3510     if (shortDayNames.isEmpty())
3511     {
3512         QLocale locale(QStringLiteral("C"));   // US English locale
3513 
3514         for (int i = 1; i <= 7; ++i)
3515             shortDayNames.push_back(locale.dayName(i, QLocale::ShortFormat));
3516         for (int i = 1; i <= 7; ++i)
3517             longDayNames.push_back(locale.dayName(i, QLocale::LongFormat));
3518         for (int i = 1; i <= 12; ++i)
3519             shortMonthNames.push_back(locale.monthName(i, QLocale::ShortFormat));
3520         for (int i = 1; i <= 12; ++i)
3521             longMonthNames.push_back(locale.monthName(i, QLocale::LongFormat));
3522     }
3523 }
3524 
3525 // Short day name, in English
3526 const QString& shortDay(int day)   // Mon = 1, ...
3527 {
3528     static QString error;
3529     initDayMonthNames();
3530     return (day >= 1 && day <= 7) ? shortDayNames.at(day - 1) : error;
3531 }
3532 
3533 // Long day name, in English
3534 const QString& longDay(int day)   // Mon = 1, ...
3535 {
3536     static QString error;
3537     initDayMonthNames();
3538     return (day >= 1 && day <= 7) ? longDayNames.at(day - 1) : error;
3539 }
3540 
3541 // Short month name, in English
3542 const QString& shortMonth(int month)   // Jan = 1, ...
3543 {
3544     static QString error;
3545     initDayMonthNames();
3546     return (month >= 1 && month <= 12) ? shortMonthNames.at(month - 1) : error;
3547 }
3548 
3549 // Long month name, in English
3550 const QString& longMonth(int month)   // Jan = 1, ...
3551 {
3552     static QString error;
3553     initDayMonthNames();
3554     return (month >= 1 && month <= 12) ? longMonthNames.at(month - 1) : error;
3555 }
3556 
3557 } // namespace
3558 
3559 // vim: et sw=4: