/* This file is part of the KDE libraries Copyright (c) 2005-2011 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdatetime.h" #include #include #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include "kcalendarsystemqdate_p.h" #include #include static const char shortDay[][4] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static const char longDay[][10] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; static const char shortMonth[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char longMonth[][10] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; // The reason for the KDateTime being invalid, returned from KDateTime::fromString() enum Status { stValid = 0, // either valid, or really invalid stTooEarly // invalid (valid date before QDate range) }; static QDateTime fromStr(const QString& string, const QString& format, int& utcOffset, QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status&); static int matchDay(const QString &string, int &offset, KCalendarSystem*); static int matchMonth(const QString &string, int &offset, KCalendarSystem*); static bool getUTCOffset(const QString &string, int &offset, bool colon, int &result); static int getAmPm(const QString &string, int &offset, KLocale*); static bool getNumber(const QString &string, int &offset, int mindigits, int maxdigits, int minval, int maxval, int &result); static int findString_internal(const QString &string, const char *ptr, int count, int &offset, int disp); template static inline int findString(const QString &string, const char array[][disp], int count, int &offset) { return findString_internal(string, array[0], count, offset, disp); } static QDate checkDate(int year, int month, int day, Status&); static const int MIN_YEAR = -4712; // minimum year which QDate allows static const int NO_NUMBER = 0x8000000; // indicates that no number is present in string conversion functions #ifdef COMPILING_TESTS KDECORE_EXPORT int KDateTime_utcCacheHit = 0; KDECORE_EXPORT int KDateTime_zoneCacheHit = 0; #endif /*----------------------------------------------------------------------------*/ class KDateTimeSpecPrivate { public: KDateTimeSpecPrivate() : utcOffset(0) {} // *** NOTE: This structure is replicated in KDateTimePrivate. Any changes must be copied there. KTimeZone tz; // if type == TimeZone, the instance's time zone. int utcOffset; // if type == OffsetFromUTC, the offset from UTC KDateTime::SpecType type; // time spec type }; KDateTime::Spec::Spec() : d(new KDateTimeSpecPrivate) { d->type = KDateTime::Invalid; } KDateTime::Spec::Spec(const KTimeZone &tz) : d(new KDateTimeSpecPrivate()) { setType(tz); } KDateTime::Spec::Spec(SpecType type, int utcOffset) : d(new KDateTimeSpecPrivate()) { setType(type, utcOffset); } KDateTime::Spec::Spec(const Spec& spec) : d(new KDateTimeSpecPrivate()) { operator=(spec); } KDateTime::Spec::~Spec() { delete d; } KDateTime::Spec &KDateTime::Spec::operator=(const Spec& spec) { if (&spec != this) { d->type = spec.d->type; if (d->type == KDateTime::TimeZone) d->tz = spec.d->tz; else if (d->type == KDateTime::OffsetFromUTC) d->utcOffset = spec.d->utcOffset; } return *this; } void KDateTime::Spec::setType(SpecType type, int utcOffset) { switch (type) { case KDateTime::OffsetFromUTC: d->utcOffset = utcOffset; // fall through to UTC case KDateTime::UTC: case KDateTime::ClockTime: d->type = type; break; case KDateTime::LocalZone: d->tz = KSystemTimeZones::local(); d->type = KDateTime::TimeZone; break; case KDateTime::TimeZone: default: d->type = KDateTime::Invalid; break; } } void KDateTime::Spec::setType(const KTimeZone &tz) { if (tz == KTimeZone::utc()) d->type = KDateTime::UTC; else if (tz.isValid()) { d->type = KDateTime::TimeZone; d->tz = tz; } else d->type = KDateTime::Invalid; } KTimeZone KDateTime::Spec::timeZone() const { if (d->type == KDateTime::TimeZone) return d->tz; if (d->type == KDateTime::UTC) return KTimeZone::utc(); return KTimeZone(); } bool KDateTime::Spec::isUtc() const { if (d->type == KDateTime::UTC || (d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) return true; return false; } KDateTime::Spec KDateTime::Spec::UTC() { return Spec(KDateTime::UTC); } KDateTime::Spec KDateTime::Spec::ClockTime() { return Spec(KDateTime::ClockTime); } KDateTime::Spec KDateTime::Spec::LocalZone() { return Spec(KDateTime::LocalZone); } KDateTime::Spec KDateTime::Spec::OffsetFromUTC(int utcOffset) { return Spec(KDateTime::OffsetFromUTC, utcOffset); } KDateTime::SpecType KDateTime::Spec::type() const { return d->type; } bool KDateTime::Spec::isValid() const { return d->type != KDateTime::Invalid; } bool KDateTime::Spec::isLocalZone() const { return d->type == KDateTime::TimeZone && d->tz == KSystemTimeZones::local(); } bool KDateTime::Spec::isClockTime() const { return d->type == KDateTime::ClockTime; } bool KDateTime::Spec::isOffsetFromUtc() const { return d->type == KDateTime::OffsetFromUTC; } int KDateTime::Spec::utcOffset() const { return d->type == KDateTime::OffsetFromUTC ? d->utcOffset : 0; } bool KDateTime::Spec::operator==(const Spec &other) const { if (d->type != other.d->type || (d->type == KDateTime::TimeZone && d->tz != other.d->tz) || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) return false; return true; } bool KDateTime::Spec::equivalentTo(const Spec &other) const { if (d->type == other.d->type) { if ((d->type == KDateTime::TimeZone && d->tz != other.d->tz) || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) return false; return true; } else { if ((d->type == KDateTime::UTC && other.d->type == KDateTime::OffsetFromUTC && other.d->utcOffset == 0) || (other.d->type == KDateTime::UTC && d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) return true; return false; } } QT_BEGIN_NAMESPACE QDataStream & operator<<(QDataStream &s, const KDateTime::Spec &spec) { // The specification type is encoded in order to insulate from changes // to the SpecType enum. switch (spec.type()) { case KDateTime::UTC: s << static_cast('u'); break; case KDateTime::OffsetFromUTC: s << static_cast('o') << spec.utcOffset(); break; case KDateTime::TimeZone: s << static_cast('z') << (spec.timeZone().isValid() ? spec.timeZone().name() : QString()); break; case KDateTime::ClockTime: s << static_cast('c'); break; case KDateTime::Invalid: default: s << static_cast(' '); break; } return s; } QDataStream & operator>>(QDataStream &s, KDateTime::Spec &spec) { // The specification type is encoded in order to insulate from changes // to the SpecType enum. quint8 t; s >> t; switch (static_cast(t)) { case 'u': spec.setType(KDateTime::UTC); break; case 'o': { int utcOffset; s >> utcOffset; spec.setType(KDateTime::OffsetFromUTC, utcOffset); break; } case 'z': { QString zone; s >> zone; KTimeZone tz = KSystemTimeZones::zone(zone); spec.setType(tz); break; } case 'c': spec.setType(KDateTime::ClockTime); break; default: spec.setType(KDateTime::Invalid); break; } return s; } QT_END_NAMESPACE /*----------------------------------------------------------------------------*/ K_GLOBAL_STATIC_WITH_ARGS(KDateTime::Spec, s_fromStringDefault, (KDateTime::ClockTime)) class KDateTimePrivate : public QSharedData { public: KDateTimePrivate() : QSharedData(), specType(KDateTime::Invalid), status(stValid), utcCached(true), convertedCached(false), m2ndOccurrence(false), mDateOnly(false) { } KDateTimePrivate(const QDateTime &d, const KDateTime::Spec &s, bool donly = false) : QSharedData(), mDt(d), specType(s.type()), status(stValid), utcCached(false), convertedCached(false), m2ndOccurrence(false), mDateOnly(donly) { switch (specType) { case KDateTime::TimeZone: specZone = s.timeZone(); break; case KDateTime::OffsetFromUTC: specUtcOffset= s.utcOffset(); break; case KDateTime::Invalid: utcCached = true; // fall through to UTC case KDateTime::UTC: default: break; } } KDateTimePrivate(const KDateTimePrivate &rhs) : QSharedData(rhs), mDt(rhs.mDt), specZone(rhs.specZone), specUtcOffset(rhs.specUtcOffset), ut(rhs.ut), converted(rhs.converted), specType(rhs.specType), status(rhs.status), utcCached(rhs.utcCached), convertedCached(rhs.convertedCached), m2ndOccurrence(rhs.m2ndOccurrence), mDateOnly(rhs.mDateOnly), converted2ndOccur(rhs.converted2ndOccur) {} ~KDateTimePrivate() {} const QDateTime& dt() const { return mDt; } const QDate date() const { return mDt.date(); } KDateTime::Spec spec() const; QDateTime utc() const { return QDateTime(ut.date, ut.time, Qt::UTC); } bool dateOnly() const { return mDateOnly; } bool secondOccurrence() const { return m2ndOccurrence; } void setDt(const QDateTime &dt) { mDt = dt; utcCached = convertedCached = m2ndOccurrence = false; } void setDtFromUtc(const QDateTime &utcdt); void setDate(const QDate &d) { mDt.setDate(d); utcCached = convertedCached = m2ndOccurrence = false; } void setTime(const QTime &t) { mDt.setTime(t); utcCached = convertedCached = mDateOnly = m2ndOccurrence = false; } void setDtTimeSpec(Qt::TimeSpec s) { mDt.setTimeSpec(s); utcCached = convertedCached = m2ndOccurrence = false; } void setSpec(const KDateTime::Spec&); void setDateOnly(bool d); int timeZoneOffset() const; QDateTime toUtc(const KTimeZone &local = KTimeZone()) const; QDateTime toZone(const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const; void newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const; bool equalSpec(const KDateTimePrivate&) const; void clearCache() { utcCached = convertedCached = false; } void setDt(const QDateTime &dt, const QDateTime &utcDt) { mDt = dt; ut.date = utcDt.date(); ut.time = utcDt.time(); utcCached = true; convertedCached = false; m2ndOccurrence = false; } void setUtc(const QDateTime &dt) const { ut.date = dt.date(); ut.time = dt.time(); utcCached = true; convertedCached = false; } /* Initialise the date/time for specType = UTC, from a time zone time, * and cache the time zone time. */ void setUtcFromTz(const QDateTime &dt, const KTimeZone &tz) { if (specType == KDateTime::UTC) { mDt = tz.toUtc(dt); utcCached = false; converted.date = dt.date(); converted.time = dt.time(); converted.tz = tz; convertedCached = true; converted2ndOccur = false; // KTimeZone::toUtc() returns the first occurrence } } // Default time spec used by fromString() static KDateTime::Spec& fromStringDefault() { return *s_fromStringDefault; } static QTime sod; // start of day (00:00:00) #ifndef NDEBUG static qint64 currentDateTimeOffset; // offset to apply to current system time #endif /* Because some applications create thousands of instances of KDateTime, this * data structure is designed to minimize memory usage. Ensure that all small * members are kept together at the end! */ private: QDateTime mDt; public: KTimeZone specZone; // if specType == TimeZone, the instance's time zone // if specType == ClockTime, the local time zone used to calculate the cached UTC time (mutable) int specUtcOffset; // if specType == OffsetFromUTC, the offset from UTC mutable struct ut { // cached UTC equivalent of 'mDt'. Saves space compared to storing QDateTime. QDate date; QTime time; } ut; private: mutable struct converted { // cached conversion to another time zone (if 'tz' is valid) QDate date; QTime time; KTimeZone tz; } converted; public: KDateTime::SpecType specType : 4; // time spec type (N.B. need 3 bits + sign bit, since enums are signed on some platforms) Status status : 2; // reason for invalid status mutable bool utcCached : 1; // true if 'ut' is valid mutable bool convertedCached : 1; // true if 'converted' is valid mutable bool m2ndOccurrence : 1; // this is the second occurrence of a time zone time private: bool mDateOnly : 1; // true to ignore the time part mutable bool converted2ndOccur : 1; // this is the second occurrence of 'converted' time }; QTime KDateTimePrivate::sod(0,0,0); #ifndef NDEBUG qint64 KDateTimePrivate::currentDateTimeOffset = 0; #endif KDateTime::Spec KDateTimePrivate::spec() const { if (specType == KDateTime::TimeZone) return KDateTime::Spec(specZone); else return KDateTime::Spec(specType, specUtcOffset); } void KDateTimePrivate::setSpec(const KDateTime::Spec &other) { if (specType == other.type()) { switch (specType) { case KDateTime::TimeZone: { KTimeZone tz = other.timeZone(); if (specZone == tz) return; specZone = tz; break; } case KDateTime::OffsetFromUTC: { int offset = other.utcOffset(); if (specUtcOffset == offset) return; specUtcOffset = offset; break; } default: return; } utcCached = false; } else { specType = other.type(); switch (specType) { case KDateTime::TimeZone: specZone = other.timeZone(); break; case KDateTime::OffsetFromUTC: specUtcOffset = other.utcOffset(); break; case KDateTime::Invalid: ut.date = QDate(); // cache an invalid UTC value utcCached = true; // fall through to UTC case KDateTime::UTC: default: break; } } convertedCached = false; setDtTimeSpec((specType == KDateTime::UTC) ? Qt::UTC : Qt::LocalTime); // this clears cached UTC value } bool KDateTimePrivate::equalSpec(const KDateTimePrivate &other) const { if (specType != other.specType || (specType == KDateTime::TimeZone && specZone != other.specZone) || (specType == KDateTime::OffsetFromUTC && specUtcOffset != other.specUtcOffset)) return false; return true; } void KDateTimePrivate::setDateOnly(bool dateOnly) { if (dateOnly != mDateOnly) { mDateOnly = dateOnly; if (dateOnly && mDt.time() != sod) { mDt.setTime(sod); utcCached = false; convertedCached = false; } m2ndOccurrence = false; } } /* Sets the date/time to a given UTC date/time. The time spec is not changed. */ void KDateTimePrivate::setDtFromUtc(const QDateTime &utcdt) { switch (specType) { case KDateTime::UTC: setDt(utcdt); break; case KDateTime::OffsetFromUTC: { QDateTime local = utcdt.addSecs(specUtcOffset); local.setTimeSpec(Qt::LocalTime); setDt(local, utcdt); break; } case KDateTime::TimeZone: { bool second; setDt(specZone.toZoneTime(utcdt, &second), utcdt); m2ndOccurrence = second; break; } case KDateTime::ClockTime: specZone = KSystemTimeZones::local(); setDt(specZone.toZoneTime(utcdt), utcdt); break; default: // invalid break; } } /* * Returns the UTC offset for the date/time, provided that it is a time zone type. */ int KDateTimePrivate::timeZoneOffset() const { if (specType != KDateTime::TimeZone) return KTimeZone::InvalidOffset; if (utcCached) { QDateTime dt = mDt; dt.setTimeSpec(Qt::UTC); return utc().secsTo(dt); } int secondOffset; if (!specZone.isValid()) { return KTimeZone::InvalidOffset; } int offset = specZone.offsetAtZoneTime(mDt, &secondOffset); if (m2ndOccurrence) { m2ndOccurrence = (secondOffset != offset); // cancel "second occurrence" flag if not applicable offset = secondOffset; } if (offset == KTimeZone::InvalidOffset) { ut.date = QDate(); utcCached = true; convertedCached = false; } else { // Calculate the UTC time from the offset and cache it QDateTime utcdt = mDt; utcdt.setTimeSpec(Qt::UTC); setUtc(utcdt.addSecs(-offset)); } return offset; } /* * Returns the date/time converted to UTC. * Depending on which KTimeZone class is involved, conversion to UTC may require * significant calculation, so the calculated UTC value is cached. */ QDateTime KDateTimePrivate::toUtc(const KTimeZone &local) const { KTimeZone loc(local); if (utcCached) { // Return cached UTC value if (specType == KDateTime::ClockTime) { // ClockTime uses the dynamic current local system time zone. // Check for a time zone change before using the cached UTC value. if (!local.isValid()) loc = KSystemTimeZones::local(); if (specZone == loc) { // kDebug() << "toUtc(): cached -> " << utc() << endl, #ifdef COMPILING_TESTS ++KDateTime_utcCacheHit; #endif return utc(); } } else { // kDebug() << "toUtc(): cached -> " << utc() << endl, #ifdef COMPILING_TESTS ++KDateTime_utcCacheHit; #endif return utc(); } } // No cached UTC value, so calculate it switch (specType) { case KDateTime::UTC: return mDt; case KDateTime::OffsetFromUTC: { if (!mDt.isValid()) break; QDateTime dt = QDateTime(mDt.date(), mDt.time(), Qt::UTC).addSecs(-specUtcOffset); setUtc(dt); // kDebug() << "toUtc(): calculated -> " << dt << endl, return dt; } case KDateTime::ClockTime: { if (!mDt.isValid()) break; if (!loc.isValid()) loc = KSystemTimeZones::local(); const_cast(this)->specZone = loc; QDateTime dt(specZone.toUtc(mDt)); setUtc(dt); // kDebug() << "toUtc(): calculated -> " << dt << endl, return dt; } case KDateTime::TimeZone: if (!mDt.isValid()) break; timeZoneOffset(); // calculate offset and cache UTC value // kDebug() << "toUtc(): calculated -> " << utc() << endl, return utc(); default: break; } // Invalid - mark it cached to avoid having to process it again ut.date = QDate(); // (invalid) utcCached = true; convertedCached = false; // kDebug() << "toUtc(): invalid"; return mDt; } /* Convert this value to another time zone. * The value is cached to save having to repeatedly calculate it. * The caller should check for an invalid date/time. */ QDateTime KDateTimePrivate::toZone(const KTimeZone &zone, const KTimeZone &local) const { if (convertedCached && converted.tz == zone) { // Converted value is already cached #ifdef COMPILING_TESTS // kDebug() << "KDateTimePrivate::toZone(" << zone->name() << "): " << mDt << " cached"; ++KDateTime_zoneCacheHit; #endif return QDateTime(converted.date, converted.time, Qt::LocalTime); } else { // Need to convert the value bool second; QDateTime result = zone.toZoneTime(toUtc(local), &second); converted.date = result.date(); converted.time = result.time(); converted.tz = zone; convertedCached = true; converted2ndOccur = second; return result; } } /* Convert this value to another time zone, and write it into the specified instance. * The value is cached to save having to repeatedly calculate it. * The caller should check for an invalid date/time. */ void KDateTimePrivate::newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local) const { newd->mDt = toZone(zone, local); newd->specZone = zone; newd->specType = KDateTime::TimeZone; newd->utcCached = utcCached; newd->mDateOnly = mDateOnly; newd->m2ndOccurrence = converted2ndOccur; switch (specType) { case KDateTime::UTC: newd->ut.date = mDt.date(); // cache the UTC value newd->ut.time = mDt.time(); break; case KDateTime::TimeZone: // This instance is also type time zone, so cache its value in the new instance newd->converted.date = mDt.date(); newd->converted.time = mDt.time(); newd->converted.tz = specZone; newd->convertedCached = true; newd->converted2ndOccur = m2ndOccurrence; newd->ut = ut; return; default: newd->ut = ut; break; } newd->convertedCached = false; } /*----------------------------------------------------------------------------*/ K_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer, emptyDateTimePrivate, (new KDateTimePrivate)) KDateTime::KDateTime() : d(*emptyDateTimePrivate) { } KDateTime::KDateTime(const QDate &date, const Spec &spec) : d(new KDateTimePrivate(QDateTime(date, KDateTimePrivate::sod, Qt::LocalTime), spec, true)) { if (spec.type() == UTC) d->setDtTimeSpec(Qt::UTC); } KDateTime::KDateTime(const QDate &date, const QTime &time, const Spec &spec) : d(new KDateTimePrivate(QDateTime(date, time, Qt::LocalTime), spec)) { if (spec.type() == UTC) d->setDtTimeSpec(Qt::UTC); } KDateTime::KDateTime(const QDateTime &dt, const Spec &spec) : d(new KDateTimePrivate(dt, spec)) { // If the supplied date/time is UTC and we need local time, or vice versa, convert it. if (spec.type() == UTC) { if (dt.timeSpec() == Qt::LocalTime) d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time } else if (dt.timeSpec() == Qt::UTC) d->setDtFromUtc(dt); } KDateTime::KDateTime(const QDateTime &dt) : d(new KDateTimePrivate(dt, (dt.timeSpec() == Qt::LocalTime ? Spec(LocalZone) : Spec(UTC)))) { } KDateTime::KDateTime(const KDateTime &other) : d(other.d) { } KDateTime::~KDateTime() { } KDateTime &KDateTime::operator=(const KDateTime &other) { if (&other != this) d = other.d; return *this; } void KDateTime::detach() { d.detach(); } bool KDateTime::isNull() const { return d->dt().isNull(); } bool KDateTime::isValid() const { return d->specType != Invalid && d->dt().isValid(); } bool KDateTime::outOfRange() const { return d->status == stTooEarly; } bool KDateTime::isDateOnly() const { return d->dateOnly(); } bool KDateTime::isLocalZone() const { return d->specType == TimeZone && d->specZone == KSystemTimeZones::local(); } bool KDateTime::isClockTime() const { return d->specType == ClockTime; } bool KDateTime::isUtc() const { return d->specType == UTC || (d->specType == OffsetFromUTC && d->specUtcOffset == 0); } bool KDateTime::isOffsetFromUtc() const { return d->specType == OffsetFromUTC; } bool KDateTime::isSecondOccurrence() const { return d->specType == TimeZone && d->secondOccurrence(); } QDate KDateTime::date() const { return d->date(); } QTime KDateTime::time() const { return d->dt().time(); } QDateTime KDateTime::dateTime() const { return d->dt(); } KDateTime::Spec KDateTime::timeSpec() const { return d->spec(); } KDateTime::SpecType KDateTime::timeType() const { return d->specType; } KTimeZone KDateTime::timeZone() const { switch (d->specType) { case TimeZone: return d->specZone; case UTC: return KTimeZone::utc(); default: return KTimeZone(); } } int KDateTime::utcOffset() const { switch (d->specType) { case TimeZone: return d->timeZoneOffset(); // calculate offset and cache UTC value case OffsetFromUTC: return d->specUtcOffset; default: return 0; } } KDateTime KDateTime::toUtc() const { if (!isValid()) return KDateTime(); if (d->specType == UTC) return *this; if (d->dateOnly()) return KDateTime(d->date(), UTC); QDateTime udt = d->toUtc(); if (!udt.isValid()) return KDateTime(); return KDateTime(udt, UTC); } KDateTime KDateTime::toOffsetFromUtc() const { if (!isValid()) return KDateTime(); int offset = 0; switch (d->specType) { case OffsetFromUTC: return *this; case UTC: { if (d->dateOnly()) return KDateTime(d->date(), Spec(OffsetFromUTC, 0)); QDateTime qdt = d->dt(); qdt.setTimeSpec(Qt::LocalTime); return KDateTime(qdt, Spec(OffsetFromUTC, 0)); } case TimeZone: offset = d->timeZoneOffset(); // calculate offset and cache UTC value break; case ClockTime: offset = KSystemTimeZones::local().offsetAtZoneTime(d->dt()); break; default: return KDateTime(); } if (d->dateOnly()) return KDateTime(d->date(), Spec(OffsetFromUTC, offset)); return KDateTime(d->dt(), Spec(OffsetFromUTC, offset)); } KDateTime KDateTime::toOffsetFromUtc(int utcOffset) const { if (!isValid()) return KDateTime(); if (d->specType == OffsetFromUTC && d->specUtcOffset == utcOffset) return *this; if (d->dateOnly()) return KDateTime(d->date(), Spec(OffsetFromUTC, utcOffset)); return KDateTime(d->toUtc(), Spec(OffsetFromUTC, utcOffset)); } KDateTime KDateTime::toLocalZone() const { if (!isValid()) return KDateTime(); KTimeZone local = KSystemTimeZones::local(); if (d->specType == TimeZone && d->specZone == local) return *this; // it's already local zone. Preserve UTC cache, if any if (d->dateOnly()) return KDateTime(d->date(), Spec(local)); switch (d->specType) { case TimeZone: case OffsetFromUTC: case UTC: { KDateTime result; d->newToZone(result.d, local, local); // cache the time zone conversion return result; } case ClockTime: return KDateTime(d->dt(), Spec(local)); default: return KDateTime(); } } KDateTime KDateTime::toClockTime() const { if (!isValid()) return KDateTime(); if (d->specType == ClockTime) return *this; if (d->dateOnly()) return KDateTime(d->date(), Spec(ClockTime)); KDateTime result = toLocalZone(); result.d->specType = ClockTime; // cached value (if any) is unaffected return result; } KDateTime KDateTime::toZone(const KTimeZone &zone) const { if (!zone.isValid() || !isValid()) return KDateTime(); if (d->specType == TimeZone && d->specZone == zone) return *this; // preserve UTC cache, if any if (d->dateOnly()) return KDateTime(d->date(), Spec(zone)); KDateTime result; d->newToZone(result.d, zone); // cache the time zone conversion return result; } KDateTime KDateTime::toTimeSpec(const KDateTime &dt) const { return toTimeSpec(dt.timeSpec()); } KDateTime KDateTime::toTimeSpec(const Spec &spec) const { if (spec == d->spec()) return *this; if (!isValid()) return KDateTime(); if (d->dateOnly()) return KDateTime(d->date(), spec); if (spec.type() == TimeZone) { KDateTime result; d->newToZone(result.d, spec.timeZone()); // cache the time zone conversion return result; } return KDateTime(d->toUtc(), spec); } uint KDateTime::toTime_t() const { QDateTime qdt = d->toUtc(); if (!qdt.isValid()) return uint(-1); return qdt.toTime_t(); } void KDateTime::setTime_t(qint64 seconds) { d->setSpec(UTC); int days = static_cast(seconds / 86400); int secs = static_cast(seconds % 86400); QDateTime dt; dt.setTimeSpec(Qt::UTC); // prevent QDateTime::setTime_t() converting to local time dt.setTime_t(0); d->setDt(dt.addDays(days).addSecs(secs)); } void KDateTime::setDateOnly(bool dateOnly) { d->setDateOnly(dateOnly); } void KDateTime::setDate(const QDate &date) { d->setDate(date); } void KDateTime::setTime(const QTime &time) { d->setTime(time); } void KDateTime::setDateTime(const QDateTime &dt) { d->clearCache(); d->setDateOnly(false); if (dt.timeSpec() == Qt::LocalTime) { if (d->specType == UTC) d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time else d->setDt(dt); } else d->setDtFromUtc(dt); // a UTC time has been supplied } void KDateTime::setTimeSpec(const Spec &other) { d->setSpec(other); } void KDateTime::setSecondOccurrence(bool second) { if (d->specType == KDateTime::TimeZone && second != d->m2ndOccurrence) { d->m2ndOccurrence = second; d->clearCache(); if (second) { // Check whether a second occurrence is actually possible, and // if not, reset m2ndOccurrence. d->timeZoneOffset(); // check, and cache UTC value } } } KDateTime KDateTime::addMSecs(qint64 msecs) const { if (!msecs) return *this; // retain cache - don't create another instance if (!isValid()) return KDateTime(); if (d->dateOnly()) { KDateTime result(*this); result.d->setDate(d->date().addDays(static_cast(msecs / 86400000))); return result; } qint64 secs = msecs / 1000; int oldms = d->dt().time().msec(); int ms = oldms + static_cast(msecs % 1000); if (msecs >= 0) { if (ms >= 1000) { ++secs; ms -= 1000; } } else { if (ms < 0) { --secs; ms += 1000; } } KDateTime result = addSecs(secs); QTime t = result.time(); result.d->setTime(QTime(t.hour(), t.minute(), t.second(), ms)); return result; } KDateTime KDateTime::addSecs(qint64 secs) const { if (!secs) return *this; // retain cache - don't create another instance if (!isValid()) return KDateTime(); int days = static_cast(secs / 86400); int seconds = static_cast(secs % 86400); if (d->dateOnly()) { KDateTime result(*this); result.d->setDate(d->date().addDays(days)); return result; } if (d->specType == ClockTime) { QDateTime qdt = d->dt(); qdt.setTimeSpec(Qt::UTC); // set time as UTC to avoid daylight savings adjustments in addSecs() qdt = qdt.addDays(days).addSecs(seconds); qdt.setTimeSpec(Qt::LocalTime); return KDateTime(qdt, Spec(ClockTime)); } return KDateTime(d->toUtc().addDays(days).addSecs(seconds), d->spec()); } KDateTime KDateTime::addDays(int days) const { if (!days) return *this; // retain cache - don't create another instance KDateTime result(*this); result.d->setDate(d->date().addDays(days)); return result; } KDateTime KDateTime::addMonths(int months) const { if (!months) return *this; // retain cache - don't create another instance KDateTime result(*this); result.d->setDate(d->date().addMonths(months)); return result; } KDateTime KDateTime::addYears(int years) const { if (!years) return *this; // retain cache - don't create another instance KDateTime result(*this); result.d->setDate(d->date().addYears(years)); return result; } int KDateTime::secsTo(const KDateTime &t2) const { return static_cast(secsTo_long(t2)); } qint64 KDateTime::secsTo_long(const KDateTime &t2) const { if (!isValid() || !t2.isValid()) return 0; if (d->dateOnly()) { QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date(); return static_cast(d->date().daysTo(dat)) * 86400; } if (t2.d->dateOnly()) return static_cast(toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date())) * 86400; QDateTime dt1, dt2; if (d->specType == ClockTime && t2.d->specType == ClockTime) { // Set both times as UTC to avoid daylight savings adjustments in secsTo() dt1 = d->dt(); dt1.setTimeSpec(Qt::UTC); dt2 = t2.d->dt(); dt2.setTimeSpec(Qt::UTC); return dt1.secsTo(dt2); } else { dt1 = d->toUtc(); dt2 = t2.d->toUtc(); } return static_cast(dt1.date().daysTo(dt2.date())) * 86400 + dt1.time().secsTo(dt2.time()); } int KDateTime::daysTo(const KDateTime &t2) const { if (!isValid() || !t2.isValid()) return 0; if (d->dateOnly()) { QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date(); return d->date().daysTo(dat); } if (t2.d->dateOnly()) return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date()); QDate dat; switch (d->specType) { case UTC: dat = t2.d->toUtc().date(); break; case OffsetFromUTC: dat = t2.d->toUtc().addSecs(d->specUtcOffset).date(); break; case TimeZone: dat = t2.d->toZone(d->specZone).date(); // this caches the converted time in t2 break; case ClockTime: { KTimeZone local = KSystemTimeZones::local(); dat = t2.d->toZone(local, local).date(); // this caches the converted time in t2 break; } default: // invalid return 0; } return d->date().daysTo(dat); } KDateTime KDateTime::currentLocalDateTime() { #ifndef NDEBUG if (KSystemTimeZones::isSimulated()) return currentUtcDateTime().toZone(KSystemTimeZones::local()); #endif return KDateTime(QDateTime::currentDateTime(), Spec(KSystemTimeZones::local())); } KDateTime KDateTime::currentUtcDateTime() { KDateTime result; time_t t; ::time(&t); result.setTime_t(static_cast(t)); #ifndef NDEBUG return result.addSecs(KDateTimePrivate::currentDateTimeOffset); #else return result; #endif } KDateTime KDateTime::currentDateTime(const Spec &spec) { switch (spec.type()) { case UTC: return currentUtcDateTime(); case TimeZone: if (spec.timeZone() != KSystemTimeZones::local()) break; // fall through to LocalZone case LocalZone: return currentLocalDateTime(); default: break; } return currentUtcDateTime().toTimeSpec(spec); } QDate KDateTime::currentLocalDate() { return currentLocalDateTime().date(); } QTime KDateTime::currentLocalTime() { return currentLocalDateTime().time(); } KDateTime::Comparison KDateTime::compare(const KDateTime &other) const { QDateTime start1, start2; const bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != other.d->secondOccurrence()); if (conv) { // Different time specs or one is a time which occurs twice, // so convert to UTC before comparing start1 = d->toUtc(); start2 = other.d->toUtc(); } else { // Same time specs, so no need to convert to UTC start1 = d->dt(); start2 = other.d->dt(); } if (d->dateOnly() || other.d->dateOnly()) { // At least one of the instances is date-only, so we need to compare // time periods rather than just times. QDateTime end1, end2; if (conv) { if (d->dateOnly()) { KDateTime kdt(*this); kdt.setTime(QTime(23,59,59,999)); end1 = kdt.d->toUtc(); } else end1 = start1; if (other.d->dateOnly()) { KDateTime kdt(other); kdt.setTime(QTime(23,59,59,999)); end2 = kdt.d->toUtc(); } else end2 = start2; } else { if (d->dateOnly()) end1 = QDateTime(d->date(), QTime(23,59,59,999), Qt::LocalTime); else end1 = d->dt(); if (other.d->dateOnly()) end2 = QDateTime(other.d->date(), QTime(23,59,59,999), Qt::LocalTime); else end2 = other.d->dt(); } if (start1 == start2) return !d->dateOnly() ? AtStart : (end1 == end2) ? Equal : (end1 < end2) ? static_cast(AtStart|Inside) : static_cast(AtStart|Inside|AtEnd|After); if (start1 < start2) return (end1 < start2) ? Before : (end1 == end2) ? static_cast(Before|AtStart|Inside|AtEnd) : (end1 == start2) ? static_cast(Before|AtStart) : (end1 < end2) ? static_cast(Before|AtStart|Inside) : Outside; else return (start1 > end2) ? After : (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast(AtEnd|After)) : (end1 == end2) ? static_cast(Inside|AtEnd) : (end1 < end2) ? Inside : static_cast(Inside|AtEnd|After); } return (start1 == start2) ? Equal : (start1 < start2) ? Before : After; } bool KDateTime::operator==(const KDateTime &other) const { if (d == other.d) return true; // the two instances share the same data if (d->dateOnly() != other.d->dateOnly()) return false; if (d->equalSpec(*other.d)) { // Both instances are in the same time zone, so compare directly if (d->dateOnly()) return d->date() == other.d->date(); else return d->secondOccurrence() == other.d->secondOccurrence() && d->dt() == other.d->dt(); } // Don't waste time converting to UTC if the dates aren't close enough. if (qAbs(d->date().daysTo(other.d->date())) > 2) return false; if (d->dateOnly()) { // Date-only values are equal if both the start and end of day times are equal. if (d->toUtc() != other.d->toUtc()) return false; // start-of-day times differ KDateTime end1(*this); end1.setTime(QTime(23,59,59,999)); KDateTime end2(other); end2.setTime(QTime(23,59,59,999)); return end1.d->toUtc() == end2.d->toUtc(); } return d->toUtc() == other.d->toUtc(); } bool KDateTime::operator<(const KDateTime &other) const { if (d == other.d) return false; // the two instances share the same data if (d->equalSpec(*other.d)) { // Both instances are in the same time zone, so compare directly if (d->dateOnly() || other.d->dateOnly()) return d->date() < other.d->date(); if (d->secondOccurrence() == other.d->secondOccurrence()) return d->dt() < other.d->dt(); // One is the second occurrence of a date/time, during a change from // daylight saving to standard time, so only do a direct comparison // if the dates are more than 1 day apart. const int dayDiff = d->date().daysTo(other.d->date()); if (dayDiff > 1) return true; if (dayDiff < -1) return false; } else { // Don't waste time converting to UTC if the dates aren't close enough. const int dayDiff = d->date().daysTo(other.d->date()); if (dayDiff > 2) return true; if (dayDiff < -2) return false; } if (d->dateOnly()) { // This instance is date-only, so we need to compare the end of its // day with the other value. Note that if the other value is date-only, // we want to compare with the start of its day, which will happen // automatically. KDateTime kdt(*this); kdt.setTime(QTime(23,59,59,999)); return kdt.d->toUtc() < other.d->toUtc(); } return d->toUtc() < other.d->toUtc(); } QString KDateTime::toString(const QString &format) const { if (!isValid()) return QString(); enum { TZNone, UTCOffsetShort, UTCOffset, UTCOffsetColon, TZAbbrev, TZName }; KLocale *locale = KGlobal::locale(); KCalendarSystemQDate calendar(locale); QString result; QString s; bool escape = false; ushort flag = 0; for (int i = 0, end = format.length(); i < end; ++i) { int zone = TZNone; int num = NO_NUMBER; int numLength = 0; // no leading zeroes ushort ch = format[i].unicode(); if (!escape) { if (ch == '%') escape = true; else result += format[i]; continue; } if (!flag) { switch (ch) { case '%': result += QLatin1Char('%'); break; case ':': flag = ch; break; case 'Y': // year num = d->date().year(); numLength = 4; break; case 'y': // year, 2 digits num = d->date().year() % 100; numLength = 2; break; case 'm': // month, 01 - 12 numLength = 2; num = d->date().month(); break; case 'B': // month name, translated result += calendar.monthName(d->date().month(), 2000, KCalendarSystem::LongName); break; case 'b': // month name, translated, short result += calendar.monthName(d->date().month(), 2000, KCalendarSystem::ShortName); break; case 'd': // day of month, 01 - 31 numLength = 2; // fall through to 'e' case 'e': // day of month, 1 - 31 num = d->date().day(); break; case 'A': // week day name, translated result += calendar.weekDayName(d->date().dayOfWeek(), KCalendarSystem::LongDayName); break; case 'a': // week day name, translated, short result += calendar.weekDayName(d->date().dayOfWeek(), KCalendarSystem::ShortDayName); break; case 'H': // hour, 00 - 23 numLength = 2; // fall through to 'k' case 'k': // hour, 0 - 23 num = d->dt().time().hour(); break; case 'I': // hour, 01 - 12 numLength = 2; // fall through to 'l' case 'l': // hour, 1 - 12 num = (d->dt().time().hour() + 11) % 12 + 1; break; case 'M': // minutes, 00 - 59 num = d->dt().time().minute(); numLength = 2; break; case 'S': // seconds, 00 - 59 num = d->dt().time().second(); numLength = 2; break; case 'P': // am/pm { bool am = (d->dt().time().hour() < 12); QString ap = ki18n(am ? "am" : "pm").toString(locale); if (ap.isEmpty()) result += am ? QLatin1String("am") : QLatin1String("pm"); else result += ap; break; } case 'p': // AM/PM { bool am = (d->dt().time().hour() < 12); QString ap = ki18n(am ? "am" : "pm").toString(locale).toUpper(); if (ap.isEmpty()) result += am ? QLatin1String("AM") : QLatin1String("PM"); else result += ap; break; } case 'z': // UTC offset in hours and minutes zone = UTCOffset; break; case 'Z': // time zone abbreviation zone = TZAbbrev; break; default: result += QLatin1Char('%'); result += format[i]; break; } } else if (flag == ':') { // It's a "%:" sequence switch (ch) { case 'A': // week day name in English result += QLatin1String(longDay[d->date().dayOfWeek() - 1]); break; case 'a': // week day name in English, short result += QLatin1String(shortDay[d->date().dayOfWeek() - 1]); break; case 'B': // month name in English result += QLatin1String(longMonth[d->date().month() - 1]); break; case 'b': // month name in English, short result += QLatin1String(shortMonth[d->date().month() - 1]); break; case 'm': // month, 1 - 12 num = d->date().month(); break; case 'P': // am/pm result += (d->dt().time().hour() < 12) ? QLatin1String("am") : QLatin1String("pm"); break; case 'p': // AM/PM result += (d->dt().time().hour() < 12) ? QLatin1String("AM") : QLatin1String("PM"); break; case 'S': // seconds with ':' prefix, only if non-zero { int sec = d->dt().time().second(); if (sec || d->dt().time().msec()) { result += QLatin1Char(':'); num = sec; numLength = 2; } break; } case 's': // milliseconds result += s.sprintf("%03d", d->dt().time().msec()); break; case 'u': // UTC offset in hours zone = UTCOffsetShort; break; case 'z': // UTC offset in hours and minutes, with colon zone = UTCOffsetColon; break; case 'Z': // time zone name zone = TZName; break; default: result += QLatin1String("%:"); result += format[i]; break; } flag = 0; } if (!flag) escape = false; // Append any required number or time zone information if (num != NO_NUMBER) { if (!numLength) result += QString::number(num); else if (numLength == 2 || numLength == 4) { if (num < 0) { num = -num; result += QLatin1Char('-'); } result += s.sprintf((numLength == 2 ? "%02d" : "%04d"), num); } } else if (zone != TZNone) { KTimeZone tz; int offset; switch (d->specType) { case UTC: case TimeZone: tz = (d->specType == TimeZone) ? d->specZone : KTimeZone::utc(); // fall through to OffsetFromUTC case OffsetFromUTC: offset = (d->specType == TimeZone) ? d->timeZoneOffset() : (d->specType == OffsetFromUTC) ? d->specUtcOffset : 0; offset /= 60; switch (zone) { case UTCOffsetShort: // UTC offset in hours case UTCOffset: // UTC offset in hours and minutes case UTCOffsetColon: // UTC offset in hours and minutes, with colon { if (offset >= 0) result += QLatin1Char('+'); else { result += QLatin1Char('-'); offset = -offset; } QString s; result += s.sprintf(((zone == UTCOffsetColon) ? "%02d:" : "%02d"), offset/60); if (ch != 'u' || offset % 60) result += s.sprintf("%02d", offset % 60); break; } case TZAbbrev: // time zone abbreviation if (tz.isValid() && d->specType != OffsetFromUTC) result += QString::fromLatin1(tz.abbreviation(d->toUtc())); break; case TZName: // time zone name if (tz.isValid() && d->specType != OffsetFromUTC) result += tz.name(); break; } break; default: break; } } } return result; } QString KDateTime::toString(TimeFormat format) const { QString result; if (!isValid()) return result; QString s; char tzsign = '+'; int offset = 0; const char *tzcolon = ""; KTimeZone tz; switch (format) { case RFCDateDay: result += QString::fromLatin1(shortDay[d->date().dayOfWeek() - 1]); result += QLatin1String(", "); // fall through to RFCDate case RFCDate: { char seconds[8] = { 0 }; if (d->dt().time().second()) sprintf(seconds, ":%02d", d->dt().time().second()); result += s.sprintf("%02d %s ", d->date().day(), shortMonth[d->date().month() - 1]); int year = d->date().year(); if (year < 0) { result += QLatin1Char('-'); year = -year; } result += s.sprintf("%04d %02d:%02d%s ", year, d->dt().time().hour(), d->dt().time().minute(), seconds); if (d->specType == ClockTime) tz = KSystemTimeZones::local(); break; } case RFC3339Date: { QString s; result += s.sprintf("%04d-%02d-%02dT%02d:%02d:%02d", d->date().year(), d->date().month(), d->date().day(), d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second()); int msec = d->dt().time().msec(); if (msec) { int digits = 3; if (!(msec % 10)) msec /= 10, --digits; if (!(msec % 10)) msec /= 10, --digits; result += s.sprintf(".%0*d", digits, d->dt().time().msec()); } if (d->specType == UTC) return result + QLatin1Char('Z'); if (d->specType == ClockTime) tz = KSystemTimeZones::local(); tzcolon = ":"; // krazy:exclude=doublequote_chars break; } case ISODate: { // QDateTime::toString(Qt::ISODate) doesn't output fractions of a second int year = d->date().year(); if (year < 0) { result += QLatin1Char('-'); year = -year; } QString s; result += s.sprintf("%04d-%02d-%02d", year, d->date().month(), d->date().day()); if (!d->dateOnly() || d->specType != ClockTime) { result += s.sprintf("T%02d:%02d:%02d", d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second()); if (d->dt().time().msec()) { // Comma is preferred by ISO8601 as the decimal point symbol, // so use it unless '.' is the symbol used in this locale or we don't have a locale. KLocale *locale = KGlobal::locale(); result += (locale && locale->decimalSymbol() == QLatin1String(".")) ? QLatin1Char('.') : QLatin1Char(','); result += s.sprintf("%03d", d->dt().time().msec()); } } if (d->specType == UTC) return result + QLatin1Char('Z'); if (d->specType == ClockTime) return result; tzcolon = ":"; // krazy:exclude=doublequote_chars break; } case QtTextDate: case LocalDate: { Qt::DateFormat qtfmt = (format == QtTextDate) ? Qt::TextDate : Qt::SystemLocaleShortDate; if (d->dateOnly()) result = d->date().toString(qtfmt); else result = d->dt().toString(qtfmt); if (result.isEmpty() || d->specType == ClockTime) return result; result += QLatin1Char(' '); break; } default: return result; } // Return the string with UTC offset ±hhmm appended if (d->specType == OffsetFromUTC || d->specType == TimeZone || tz.isValid()) { if (d->specType == TimeZone) offset = d->timeZoneOffset(); // calculate offset and cache UTC value else offset = tz.isValid() ? tz.offsetAtZoneTime(d->dt()) : d->specUtcOffset; if (offset < 0) { offset = -offset; tzsign = '-'; } } offset /= 60; return result + s.sprintf("%c%02d%s%02d", tzsign, offset/60, tzcolon, offset%60); } KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *negZero) { if (negZero) *negZero = false; QString str = string.trimmed(); if (str.isEmpty()) return KDateTime(); switch (format) { case RFCDateDay: // format is Wdy, DD Mon YYYY hh:mm:ss ±hhmm case RFCDate: // format is [Wdy,] DD Mon YYYY hh:mm[:ss] ±hhmm { int nyear = 6; // indexes within string to values int nmonth = 4; int nday = 2; int nwday = 1; int nhour = 7; int nmin = 8; int nsec = 9; // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" 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+)$")); QStringList parts; if (!str.indexOf(rx)) { // Check that if date has '-' separators, both separators are '-'. parts = rx.capturedTexts(); bool h1 = (parts[3] == QLatin1String("-")); bool h2 = (parts[5] == QLatin1String("-")); if (h1 != h2) break; } else { // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY" 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)$")); if (str.indexOf(rx)) break; nyear = 7; nmonth = 2; nday = 3; nwday = 1; nhour = 4; nmin = 5; nsec = 6; parts = rx.capturedTexts(); } bool ok[4]; int day = parts[nday].toInt(&ok[0]); int year = parts[nyear].toInt(&ok[1]); int hour = parts[nhour].toInt(&ok[2]); int minute = parts[nmin].toInt(&ok[3]); if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) break; int second = 0; if (!parts[nsec].isEmpty()) { second = parts[nsec].toInt(&ok[0]); if (!ok[0]) break; } bool leapSecond = (second == 60); if (leapSecond) second = 59; // apparently a leap second - validate below, once time zone is known int month = 0; for ( ; month < 12 && parts[nmonth] != QLatin1String(shortMonth[month]); ++month) ; int dayOfWeek = -1; if (!parts[nwday].isEmpty()) { // Look up the weekday name while (++dayOfWeek < 7 && QLatin1String(shortDay[dayOfWeek]) != parts[nwday]) ; if (dayOfWeek >= 7) for (dayOfWeek = 0; dayOfWeek < 7 && QLatin1String(longDay[dayOfWeek]) != parts[nwday]; ++dayOfWeek) ; } if (month >= 12 || dayOfWeek >= 7 || (dayOfWeek < 0 && format == RFCDateDay)) break; int i = parts[nyear].size(); if (i < 4) { // It's an obsolete year specification with less than 4 digits year += (i == 2 && year < 50) ? 2000 : 1900; } // Parse the UTC offset part int offset = 0; // set default to '-0000' bool negOffset = false; if (parts.count() > 10) { rx = QRegExp(QString::fromLatin1("^([+-])(\\d\\d)(\\d\\d)$")); if (!parts[10].indexOf(rx)) { // It's a UTC offset ±hhmm parts = rx.capturedTexts(); offset = parts[2].toInt(&ok[0]) * 3600; int offsetMin = parts[3].toInt(&ok[1]); if (!ok[0] || !ok[1] || offsetMin > 59) break; offset += offsetMin * 60; negOffset = (parts[1] == QLatin1String("-")); if (negOffset) offset = -offset; } else { // Check for an obsolete time zone name QByteArray zone = parts[10].toLatin1(); if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') negOffset = true; // military zone: RFC 2822 treats as '-0000' else if (zone != "UT" && zone != "GMT") // treated as '+0000' { offset = (zone == "EDT") ? -4*3600 : (zone == "EST" || zone == "CDT") ? -5*3600 : (zone == "CST" || zone == "MDT") ? -6*3600 : (zone == "MST" || zone == "PDT") ? -7*3600 : (zone == "PST") ? -8*3600 : 0; if (!offset) { // Check for any other alphabetic time zone bool nonalpha = false; for (int i = 0, end = zone.size(); i < end && !nonalpha; ++i) nonalpha = !isalpha(zone[i]); if (nonalpha) break; // TODO: Attempt to recognize the time zone abbreviation? negOffset = true; // unknown time zone: RFC 2822 treats as '-0000' } } } } Status invalid = stValid; QDate qdate = checkDate(year, month+1, day, invalid); // convert date, and check for out-of-range if (!qdate.isValid()) break; KDateTime result(qdate, QTime(hour, minute, second), Spec(OffsetFromUTC, offset)); if (!result.isValid() || (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek+1)) break; // invalid date/time, or weekday doesn't correspond with date if (!offset) { if (negOffset && negZero) *negZero = true; // UTC offset given as "-0000" result.setTimeSpec(UTC); } if (leapSecond) { // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Convert the time to UTC and check that it is 00:00:00. if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) break; // the time isn't the last second of the day } if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } return result; } case RFC3339Date: // format is YYYY-MM-DDThh:mm:ss[.s]TZ { QRegExp rx(QString::fromLatin1("^(\\d{4})-(\\d\\d)-(\\d\\d)[Tt](\\d\\d):(\\d\\d):(\\d\\d)(?:\\.(\\d+))?([Zz]|([+-])(\\d\\d):(\\d\\d))$")); if (str.indexOf(rx)) break; const QStringList parts = rx.capturedTexts(); bool ok, ok1, ok2; int msecs = 0; bool leapSecond = false; int year = parts[1].toInt(&ok); int month = parts[2].toInt(&ok1); int day = parts[3].toInt(&ok2); if (!ok || !ok1 || !ok2) break; QDate d(year, month, day); if (!d.isValid()) break; int hour = parts[4].toInt(&ok); int minute = parts[5].toInt(&ok1); int second = parts[6].toInt(&ok2); if (!ok || !ok1 || !ok2) break; leapSecond = (second == 60); if (leapSecond) second = 59; // apparently a leap second - validate below, once time zone is known if (!parts[7].isEmpty()) { QString ms = parts[7] + QLatin1String("00"); ms.truncate(3); msecs = ms.toInt(&ok); if (!ok) break; if (msecs && leapSecond) break; // leap second only valid if 23:59:60.000 } QTime t(hour, minute, second, msecs); if (!t.isValid()) break; int offset = 0; SpecType spec = (parts[8].toUpper() == QLatin1String("Z")) ? UTC : OffsetFromUTC; if (spec == OffsetFromUTC) { offset = parts[10].toInt(&ok) * 3600; offset += parts[11].toInt(&ok1) * 60; if (!ok || !ok1) break; if (parts[9] == QLatin1String("-")) { if (!offset && leapSecond) break; // leap second only valid if known time zone offset = -offset; if (!offset && negZero) *negZero = true; } } if (leapSecond) { // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Convert the time to UTC and check that it is 00:00:00. if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) break; // the time isn't the last second of the day } return KDateTime(d, t, Spec(spec, offset)); } case ISODate: { /* * Extended format: [±]YYYY-MM-DD[Thh[:mm[:ss.s]][TZ]] * Basic format: [±]YYYYMMDD[Thh[mm[ss.s]][TZ]] * Extended format: [±]YYYY-DDD[Thh[:mm[:ss.s]][TZ]] * Basic format: [±]YYYYDDD[Thh[mm[ss.s]][TZ]] * In the first three formats, the year may be expanded to more than 4 digits. * * QDateTime::fromString(Qt::ISODate) is a rather limited implementation * of parsing ISO 8601 format date/time strings, so it isn't used here. * This implementation isn't complete either, but it's better. * * ISO 8601 allows truncation, but for a combined date & time, the date part cannot * be truncated from the right, and the time part cannot be truncated from the left. * In other words, only the outer parts of the string can be omitted. * The standard does not actually define how to interpret omitted parts - it is up * to those interchanging the data to agree on a scheme. */ bool dateOnly = false; // Check first for the extended format of ISO 8601 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))?)?$")); if (str.indexOf(rx)) { // It's not the extended format - check for the basic format rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$")); if (str.indexOf(rx)) { rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$")); if (str.indexOf(rx)) { // Check for date-only formats dateOnly = true; rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)$")); if (str.indexOf(rx)) { // It's not the extended format - check for the basic format rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})$")); if (str.indexOf(rx)) { rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})$")); if (str.indexOf(rx)) break; } } } } } const QStringList parts = rx.capturedTexts(); bool ok, ok1; QDate d; int hour = 0; int minute = 0; int second = 0; int msecs = 0; bool leapSecond = false; int year = parts[2].toInt(&ok); if (!ok) break; if (parts[1] == QLatin1String("-")) year = -year; if (!dateOnly) { hour = parts[4].toInt(&ok); if (!ok) break; if (!parts[5].isEmpty()) { minute = parts[5].toInt(&ok); if (!ok) break; } if (!parts[6].isEmpty()) { second = parts[6].toInt(&ok); if (!ok) break; } leapSecond = (second == 60); if (leapSecond) second = 59; // apparently a leap second - validate below, once time zone is known if (!parts[7].isEmpty()) { QString ms = parts[7] + QLatin1String("00"); ms.truncate(3); msecs = ms.toInt(&ok); if (!ok) break; } } int month, day; Status invalid = stValid; if (parts[3].length() == 3) { // A day of the year is specified day = parts[3].toInt(&ok); if (!ok || day < 1 || day > 366) break; d = checkDate(year, 1, 1, invalid).addDays(day - 1); // convert date, and check for out-of-range if (!d.isValid() || (!invalid && d.year() != year)) break; day = d.day(); month = d.month(); } else { // A month and day are specified month = parts[3].left(2).toInt(&ok); day = parts[3].right(2).toInt(&ok1); if (!ok || !ok1) break; d = checkDate(year, month, day, invalid); // convert date, and check for out-of-range if (!d.isValid()) break; } if (dateOnly) { if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } return KDateTime(d, Spec(ClockTime)); } if (hour == 24 && !minute && !second && !msecs) { // A time of 24:00:00 is allowed by ISO 8601, and means midnight at the end of the day d = d.addDays(1); hour = 0; } QTime t(hour, minute, second, msecs); if (!t.isValid()) break; if (parts[8].isEmpty()) { // No UTC offset is specified. Don't try to validate leap seconds. if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } return KDateTime(d, t, KDateTimePrivate::fromStringDefault()); } int offset = 0; SpecType spec = (parts[8] == QLatin1String("Z")) ? UTC : OffsetFromUTC; if (spec == OffsetFromUTC) { offset = parts[10].toInt(&ok) * 3600; if (!ok) break; if (!parts[11].isEmpty()) { offset += parts[11].toInt(&ok) * 60; if (!ok) break; } if (parts[9] == QLatin1String("-")) { offset = -offset; if (!offset && negZero) *negZero = true; } } if (leapSecond) { // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Convert the time to UTC and check that it is 00:00:00. if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) break; // the time isn't the last second of the day } if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } return KDateTime(d, t, Spec(spec, offset)); } case QtTextDate: // format is Wdy Mth DD [hh:mm:ss] YYYY [±hhmm] { int offset = 0; QRegExp rx(QString::fromLatin1("^(\\S+\\s+\\S+\\s+\\d\\d\\s+(\\d\\d:\\d\\d:\\d\\d\\s+)?\\d\\d\\d\\d)\\s*(.*)$")); if (str.indexOf(rx) < 0) break; QStringList parts = rx.capturedTexts(); QDate qd; QDateTime qdt; bool dateOnly = parts[2].isEmpty(); if (dateOnly) { qd = QDate::fromString(parts[1], Qt::TextDate); if (!qd.isValid()) break; } else { qdt = QDateTime::fromString(parts[1], Qt::TextDate); if (!qdt.isValid()) break; } if (parts[3].isEmpty()) { // No time zone offset specified, so return a local clock time if (dateOnly) return KDateTime(qd, KDateTimePrivate::fromStringDefault()); else { // Do it this way to prevent UTC conversions changing the time return KDateTime(qdt.date(), qdt.time(), KDateTimePrivate::fromStringDefault()); } } rx = QRegExp(QString::fromLatin1("([+-])([\\d][\\d])(?::?([\\d][\\d]))?$")); if (parts[3].indexOf(rx) < 0) break; // Extract the UTC offset at the end of the string bool ok; parts = rx.capturedTexts(); offset = parts[2].toInt(&ok) * 3600; if (!ok) break; if (parts.count() > 3) { offset += parts[3].toInt(&ok) * 60; if (!ok) break; } if (parts[1] == QLatin1String("-")) { offset = -offset; if (!offset && negZero) *negZero = true; } if (dateOnly) return KDateTime(qd, Spec((offset ? OffsetFromUTC : UTC), offset)); qdt.setTimeSpec(offset ? Qt::LocalTime : Qt::UTC); return KDateTime(qdt, Spec((offset ? OffsetFromUTC : UTC), offset)); } case LocalDate: default: break; } return KDateTime(); } KDateTime KDateTime::fromString(const QString &string, const QString &format, const KTimeZones *zones, bool offsetIfAmbiguous) { int utcOffset = 0; // UTC offset in seconds bool dateOnly = false; Status invalid = stValid; QString zoneName; QByteArray zoneAbbrev; QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly, invalid); if (!qdt.isValid()) return KDateTime(); if (zones) { // Try to find a time zone match bool zname = false; KTimeZone zone; if (!zoneName.isEmpty()) { // A time zone name has been found. // Use the time zone with that name. zone = zones->zone(zoneName); zname = true; } else if (!invalid) { if (!zoneAbbrev.isEmpty()) { // A time zone abbreviation has been found. // Use the time zone which contains it, if any, provided that the // abbreviation applies at the specified date/time. bool useUtcOffset = false; const KTimeZones::ZoneMap z = zones->zones(); for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) { if (it.value().abbreviations().contains(zoneAbbrev)) { int offset2; int offset = it.value().offsetAtZoneTime(qdt, &offset2); QDateTime ut(qdt); ut.setTimeSpec(Qt::UTC); ut = ut.addSecs(-offset); if (it.value().abbreviation(ut) != zoneAbbrev) { if (offset == offset2) continue; // abbreviation doesn't apply at specified time ut = ut.addSecs(offset - offset2); if (it.value().abbreviation(ut) != zoneAbbrev) continue; // abbreviation doesn't apply at specified time offset = offset2; } // Found a time zone which uses this abbreviation at the specified date/time if (zone.isValid()) { // Abbreviation is used by more than one time zone if (!offsetIfAmbiguous || offset != utcOffset) return KDateTime(); useUtcOffset = true; } else { zone = it.value(); utcOffset = offset; } } } if (useUtcOffset) { zone = KTimeZone(); if (!utcOffset) qdt.setTimeSpec(Qt::UTC); } else zname = true; } else if (utcOffset || qdt.timeSpec() == Qt::UTC) { // A UTC offset has been found. // Use the time zone which contains it, if any. // For a date-only value, use the start of the day. QDateTime dtUTC = qdt; dtUTC.setTimeSpec(Qt::UTC); dtUTC = dtUTC.addSecs(-utcOffset); const KTimeZones::ZoneMap z = zones->zones(); for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) { QList offsets = it.value().utcOffsets(); if ((offsets.isEmpty() || offsets.contains(utcOffset)) && it.value().offsetAtUtc(dtUTC) == utcOffset) { // Found a time zone which uses this offset at the specified time if (zone.isValid() || !utcOffset) { // UTC offset is used by more than one time zone if (!offsetIfAmbiguous) return KDateTime(); if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } if (dateOnly) return KDateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset)); qdt.setTimeSpec(Qt::LocalTime); return KDateTime(qdt, Spec(OffsetFromUTC, utcOffset)); } zone = it.value(); } } } } if (!zone.isValid() && zname) return KDateTime(); // an unknown zone name or abbreviation was found if (zone.isValid() && !invalid) { if (dateOnly) return KDateTime(qdt.date(), Spec(zone)); return KDateTime(qdt, Spec(zone)); } } // No time zone match was found if (invalid) { KDateTime dt; // date out of range - return invalid KDateTime ... dt.d->status = invalid; // ... with reason for error return dt; } KDateTime result; if (utcOffset) { qdt.setTimeSpec(Qt::LocalTime); result = KDateTime(qdt, Spec(OffsetFromUTC, utcOffset)); } else if (qdt.timeSpec() == Qt::UTC) result = KDateTime(qdt, UTC); else { result = KDateTime(qdt, Spec(ClockTime)); result.setTimeSpec(KDateTimePrivate::fromStringDefault()); } if (dateOnly) result.setDateOnly(true); return result; } void KDateTime::setFromStringDefault(const Spec &spec) { KDateTimePrivate::fromStringDefault() = spec; } void KDateTime::setSimulatedSystemTime(const KDateTime& newTime) { Q_UNUSED(newTime); #ifndef NDEBUG if (newTime.isValid()) { KDateTimePrivate::currentDateTimeOffset = realCurrentLocalDateTime().secsTo_long(newTime); KSystemTimeZones::setLocalZone(newTime.timeZone()); } else { KDateTimePrivate::currentDateTimeOffset = 0; KSystemTimeZones::setLocalZone(KTimeZone()); } #endif } KDateTime KDateTime::realCurrentLocalDateTime() { #ifndef NDEBUG return KDateTime(QDateTime::currentDateTime(), KSystemTimeZones::realLocalZone()); #else return KDateTime(QDateTime::currentDateTime(), Spec(KSystemTimeZones::local())); #endif } QT_BEGIN_NAMESPACE QDataStream & operator<<(QDataStream &s, const KDateTime &dt) { s << dt.date() << dt.time() << dt.timeSpec() << quint8(dt.isDateOnly() ? 0x01 : 0x00); return s; } QDataStream & operator>>(QDataStream &s, KDateTime &kdt) { QDate d; QTime t; KDateTime::Spec spec; quint8 flags; s >> d >> t >> spec >> flags; if (flags & 0x01) kdt = KDateTime(d, spec); else kdt = KDateTime(d, t, spec); return s; } QT_END_NAMESPACE /* * Extracts a QDateTime from a string, given a format string. * The date/time is set to Qt::UTC if a zero UTC offset is found, * otherwise it is Qt::LocalTime. If Qt::LocalTime is returned and * utcOffset == 0, that indicates that no UTC offset was found. */ QDateTime fromStr(const QString& string, const QString& format, int& utcOffset, QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status &status) { status = stValid; QString str = string.simplified(); int year = NO_NUMBER; int month = NO_NUMBER; int day = NO_NUMBER; int dayOfWeek = NO_NUMBER; int hour = NO_NUMBER; int minute = NO_NUMBER; int second = NO_NUMBER; int millisec = NO_NUMBER; int ampm = NO_NUMBER; int tzoffset = NO_NUMBER; zoneName.clear(); zoneAbbrev.clear(); enum { TZNone, UTCOffset, UTCOffsetColon, TZAbbrev, TZName }; KLocale *locale = KGlobal::locale(); KCalendarSystemQDate calendar(locale); int zone; int s = 0; int send = str.length(); bool escape = false; ushort flag = 0; for (int f = 0, fend = format.length(); f < fend && s < send; ++f) { zone = TZNone; ushort ch = format[f].unicode(); if (!escape) { if (ch == '%') escape = true; else if (format[f].isSpace()) { if (str[s].isSpace()) ++s; } else if (format[f] == str[s]) ++s; else return QDateTime(); continue; } if (!flag) { switch (ch) { case '%': if (str[s++] != QLatin1Char('%')) return QDateTime(); break; case ':': flag = ch; break; case 'Y': // full year, 4 digits if (!getNumber(str, s, 4, 4, NO_NUMBER, -1, year)) return QDateTime(); break; case 'y': // year, 2 digits if (!getNumber(str, s, 2, 2, 0, 99, year)) return QDateTime(); year += (year <= 50) ? 2000 : 1999; break; case 'm': // month, 2 digits, 01 - 12 if (!getNumber(str, s, 2, 2, 1, 12, month)) return QDateTime(); break; case 'B': case 'b': // month name, translated or English { int m = matchMonth(str, s, &calendar); if (m <= 0 || (month != NO_NUMBER && month != m)) return QDateTime(); month = m; break; } case 'd': // day of month, 2 digits, 01 - 31 if (!getNumber(str, s, 2, 2, 1, 31, day)) return QDateTime(); break; case 'e': // day of month, 1 - 31 if (!getNumber(str, s, 1, 2, 1, 31, day)) return QDateTime(); break; case 'A': case 'a': // week day name, translated or English { int dow = matchDay(str, s, &calendar); if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) return QDateTime(); dayOfWeek = dow; break; } case 'H': // hour, 2 digits, 00 - 23 if (!getNumber(str, s, 2, 2, 0, 23, hour)) return QDateTime(); break; case 'k': // hour, 0 - 23 if (!getNumber(str, s, 1, 2, 0, 23, hour)) return QDateTime(); break; case 'I': // hour, 2 digits, 01 - 12 if (!getNumber(str, s, 2, 2, 1, 12, hour)) return QDateTime(); break; case 'l': // hour, 1 - 12 if (!getNumber(str, s, 1, 2, 1, 12, hour)) return QDateTime(); break; case 'M': // minutes, 2 digits, 00 - 59 if (!getNumber(str, s, 2, 2, 0, 59, minute)) return QDateTime(); break; case 'S': // seconds, 2 digits, 00 - 59 if (!getNumber(str, s, 2, 2, 0, 59, second)) return QDateTime(); break; case 's': // seconds, 0 - 59 if (!getNumber(str, s, 1, 2, 0, 59, second)) return QDateTime(); break; case 'P': case 'p': // am/pm { int ap = getAmPm(str, s, locale); if (!ap || (ampm != NO_NUMBER && ampm != ap)) return QDateTime(); ampm = ap; break; } case 'z': // UTC offset in hours and optionally minutes zone = UTCOffset; break; case 'Z': // time zone abbreviation zone = TZAbbrev; break; case 't': // whitespace if (str[s++] != QLatin1Char(' ')) return QDateTime(); break; default: if (s + 2 > send || str[s++] != QLatin1Char('%') || str[s++] != format[f]) return QDateTime(); break; } } else if (flag == ':') { // It's a "%:" sequence switch (ch) { case 'Y': // full year, >= 4 digits if (!getNumber(str, s, 4, 100, NO_NUMBER, -1, year)) return QDateTime(); break; case 'A': case 'a': // week day name in English { int dow = matchDay(str, s, 0); if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) return QDateTime(); dayOfWeek = dow; break; } case 'B': case 'b': // month name in English { int m = matchMonth(str, s, 0); if (m <= 0 || (month != NO_NUMBER && month != m)) return QDateTime(); month = m; break; } case 'm': // month, 1 - 12 if (!getNumber(str, s, 1, 2, 1, 12, month)) return QDateTime(); break; case 'P': case 'p': // am/pm in English { int ap = getAmPm(str, s, 0); if (!ap || (ampm != NO_NUMBER && ampm != ap)) return QDateTime(); ampm = ap; break; } case 'M': // minutes, 0 - 59 if (!getNumber(str, s, 1, 2, 0, 59, minute)) return QDateTime(); break; case 'S': // seconds with ':' prefix, defaults to zero if (str[s] != QLatin1Char(':')) { second = 0; break; } ++s; if (!getNumber(str, s, 1, 2, 0, 59, second)) return QDateTime(); break; case 's': // milliseconds, with decimal point prefix { if (str[s] != QLatin1Char('.')) { // If no locale, try comma, it is preferred by ISO8601 as the decimal point symbol QString dpt = locale == 0 ? QString::fromLatin1(",") : locale->decimalSymbol(); if (!str.mid(s).startsWith(dpt)) return QDateTime(); s += dpt.length() - 1; } ++s; if (s >= send) return QDateTime(); QString val = str.mid(s); int i = 0; for (int end = val.length(); i < end && val[i].isDigit(); ++i) ; if (!i) return QDateTime(); val.truncate(i); val += QLatin1String("00"); val.truncate(3); int ms = val.toInt(); if (millisec != NO_NUMBER && millisec != ms) return QDateTime(); millisec = ms; s += i; break; } case 'u': // UTC offset in hours and optionally minutes zone = UTCOffset; break; case 'z': // UTC offset in hours and minutes, with colon zone = UTCOffsetColon; break; case 'Z': // time zone name zone = TZName; break; default: if (s + 3 > send || str[s++] != QLatin1Char('%') || str[s++] != QLatin1Char(':') || str[s++] != format[f]) return QDateTime(); break; } flag = 0; } if (!flag) escape = false; if (zone != TZNone) { // Read time zone or UTC offset switch (zone) { case UTCOffset: case UTCOffsetColon: if (!zoneAbbrev.isEmpty() || !zoneName.isEmpty()) return QDateTime(); if (!getUTCOffset(str, s, (zone == UTCOffsetColon), tzoffset)) return QDateTime(); break; case TZAbbrev: // time zone abbreviation { if (tzoffset != NO_NUMBER || !zoneName.isEmpty()) return QDateTime(); int start = s; while (s < send && str[s].isLetterOrNumber()) ++s; if (s == start) return QDateTime(); QString z = str.mid(start, s - start); if (!zoneAbbrev.isEmpty() && z.toLatin1() != zoneAbbrev) return QDateTime(); zoneAbbrev = z.toLatin1(); break; } case TZName: // time zone name { if (tzoffset != NO_NUMBER || !zoneAbbrev.isEmpty()) return QDateTime(); QString z; if (f + 1 >= fend) { z = str.mid(s); s = send; } else { // Get the terminating character for the zone name QChar endchar = format[f + 1]; if (endchar == QLatin1Char('%') && f + 2 < fend) { QChar endchar2 = format[f + 2]; if (endchar2 == QLatin1Char('n') || endchar2 == QLatin1Char('t')) endchar = QLatin1Char(' '); } // Extract from the input string up to the terminating character int start = s; for ( ; s < send && str[s] != endchar; ++s) ; if (s == start) return QDateTime(); z = str.mid(start, s - start); } if (!zoneName.isEmpty() && z != zoneName) return QDateTime(); zoneName = z; break; } default: break; } } } if (year == NO_NUMBER) year = KDateTime::currentLocalDate().year(); if (month == NO_NUMBER) month = 1; QDate d = checkDate(year, month, (day > 0 ? day : 1), status); // convert date, and check for out-of-range if (!d.isValid()) return QDateTime(); if (dayOfWeek != NO_NUMBER && !status) { if (day == NO_NUMBER) { day = 1 + dayOfWeek - QDate(year, month, 1).dayOfWeek(); if (day <= 0) day += 7; } else { if (QDate(year, month, day).dayOfWeek() != dayOfWeek) return QDateTime(); } } if (day == NO_NUMBER) day = 1; dateOnly = (hour == NO_NUMBER && minute == NO_NUMBER && second == NO_NUMBER && millisec == NO_NUMBER); if (hour == NO_NUMBER) hour = 0; if (minute == NO_NUMBER) minute = 0; if (second == NO_NUMBER) second = 0; if (millisec == NO_NUMBER) millisec = 0; if (ampm != NO_NUMBER) { if (!hour || hour > 12) return QDateTime(); if (ampm == 1 && hour == 12) hour = 0; else if (ampm == 2 && hour < 12) hour += 12; } QDateTime dt(d, QTime(hour, minute, second, millisec), (tzoffset == 0 ? Qt::UTC : Qt::LocalTime)); utcOffset = (tzoffset == NO_NUMBER) ? 0 : tzoffset*60; return dt; } /* * Find which day name matches the specified part of a string. * 'offset' is incremented by the length of the match. * Reply = day number (1 - 7), or <= 0 if no match. */ int matchDay(const QString &string, int &offset, KCalendarSystem *calendar) { int dayOfWeek; QString part = string.mid(offset); if (part.isEmpty()) return -1; if (calendar) { // Check for localised day name first for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) { QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::LongDayName); if (part.startsWith(name, Qt::CaseInsensitive)) { offset += name.length(); return dayOfWeek; } } for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) { QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::ShortDayName); if (part.startsWith(name, Qt::CaseInsensitive)) { offset += name.length(); return dayOfWeek; } } } // Check for English day name dayOfWeek = findString(part, longDay, 7, offset); if (dayOfWeek < 0) dayOfWeek = findString(part, shortDay, 7, offset); return dayOfWeek + 1; } /* * Find which month name matches the specified part of a string. * 'offset' is incremented by the length of the match. * Reply = month number (1 - 12), or <= 0 if no match. */ int matchMonth(const QString &string, int &offset, KCalendarSystem *calendar) { int month; QString part = string.mid(offset); if (part.isEmpty()) return -1; if (calendar) { // Check for localised month name first for (month = 1; month <= 12; ++month) { QString name = calendar->monthName(month, 2000, KCalendarSystem::LongName); if (part.startsWith(name, Qt::CaseInsensitive)) { offset += name.length(); return month; } } for (month = 1; month <= 12; ++month) { QString name = calendar->monthName(month, 2000, KCalendarSystem::ShortName); if (part.startsWith(name, Qt::CaseInsensitive)) { offset += name.length(); return month; } } } // Check for English month name month = findString(part, longMonth, 12, offset); if (month < 0) month = findString(part, shortMonth, 12, offset); return month + 1; } /* * Read a UTC offset from the input string. */ bool getUTCOffset(const QString &string, int &offset, bool colon, int &result) { int sign; int len = string.length(); if (offset >= len) return false; switch (string[offset++].unicode()) { case '+': sign = 1; break; case '-': sign = -1; break; default: return false; } int tzhour = NO_NUMBER; int tzmin = NO_NUMBER; if (!getNumber(string, offset, 2, 2, 0, 99, tzhour)) return false; if (colon) { if (offset >= len || string[offset++] != QLatin1Char(':')) return false; } if (offset >= len || !string[offset].isDigit()) tzmin = 0; else { if (!getNumber(string, offset, 2, 2, 0, 59, tzmin)) return false; } tzmin += tzhour * 60; if (result != NO_NUMBER && result != tzmin) return false; result = sign * tzmin; return true; } /* * Read an am/pm indicator from the input string. * 'offset' is incremented by the length of the match. * Reply = 1 (am), 2 (pm), or 0 if no match. */ int getAmPm(const QString &string, int &offset, KLocale *locale) { QString part = string.mid(offset); int ap = 0; int n = 2; if (locale) { // Check localised form first QString aps = ki18n("am").toString(locale); if (part.startsWith(aps, Qt::CaseInsensitive)) { ap = 1; n = aps.length(); } else { aps = ki18n("pm").toString(locale); if (part.startsWith(aps, Qt::CaseInsensitive)) { ap = 2; n = aps.length(); } } } if (!ap) { if (part.startsWith(QLatin1String("am"), Qt::CaseInsensitive)) ap = 1; else if (part.startsWith(QLatin1String("pm"), Qt::CaseInsensitive)) ap = 2; } if (ap) offset += n; return ap; } /* Convert part of 'string' to a number. * If converted number differs from any current value in 'result', the function fails. * Reply = true if successful. */ bool getNumber(const QString& string, int& offset, int mindigits, int maxdigits, int minval, int maxval, int& result) { int end = string.size(); bool neg = false; if (minval == NO_NUMBER && offset < end && string[offset] == QLatin1Char('-')) { neg = true; ++offset; } if (offset + maxdigits > end) maxdigits = end - offset; int ndigits; for (ndigits = 0; ndigits < maxdigits && string[offset + ndigits].isDigit(); ++ndigits) ; if (ndigits < mindigits) return false; bool ok; int n = string.mid(offset, ndigits).toInt(&ok); if (neg) n = -n; if (!ok || (result != NO_NUMBER && n != result) || (minval != NO_NUMBER && n < minval) || (n > maxval && maxval >= 0)) return false; result = n; offset += ndigits; return true; } int findString_internal(const QString &string, const char *array, int count, int &offset, int disp) { for (int i = 0; i < count; ++i) { if (string.startsWith(QLatin1String(array + i * disp), Qt::CaseInsensitive)) { offset += qstrlen(array + i * disp); return i; } } return -1; } /* * Return the QDate for a given year, month and day. * If in error, check whether the reason is that the year is out of range. * If so, return a valid (but wrong) date but with 'status' set to the * appropriate error code. If no error, 'status' is set to stValid. */ QDate checkDate(int year, int month, int day, Status &status) { status = stValid; QDate qdate(year, month, day); if (qdate.isValid()) return qdate; // Invalid date - check whether it's simply out of range if (year < MIN_YEAR) { bool leap = (year % 4 == 0) && (year % 100 || year % 400 == 0); qdate.setYMD((leap ? 2000 : 2001), month, day); if (qdate.isValid()) status = stTooEarly; } return qdate; }