Warning, file /frameworks/kdelibs4support/src/kdecore/kdatetime.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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