mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 19:02:48 +00:00
3079 lines
105 KiB
C++
3079 lines
105 KiB
C++
/*
|
|
This file is part of the KDE libraries
|
|
Copyright (c) 2005-2011 David Jarvie <djarvie@kde.org>
|
|
|
|
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 <config.h>
|
|
#include <config-date.h>
|
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include <QtCore/QDateTime>
|
|
#include <QtCore/QRegExp>
|
|
#include <QtCore/QStringList>
|
|
#include <QtCore/QSharedData>
|
|
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include "kcalendarsystemqdate_p.h"
|
|
#include <ksystemtimezone.h>
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
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<int disp> 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<quint8>('u');
|
|
break;
|
|
case KDateTime::OffsetFromUTC:
|
|
s << static_cast<quint8>('o') << spec.utcOffset();
|
|
break;
|
|
case KDateTime::TimeZone:
|
|
s << static_cast<quint8>('z') << (spec.timeZone().isValid() ? spec.timeZone().name() : QString());
|
|
break;
|
|
case KDateTime::ClockTime:
|
|
s << static_cast<quint8>('c');
|
|
break;
|
|
case KDateTime::Invalid:
|
|
default:
|
|
s << static_cast<quint8>(' ');
|
|
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<char>(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<KDateTimePrivate*>(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<KDateTimePrivate>, 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;
|
|
}
|
|
|
|
inline static bool kIsNightTime(const QDateTime &datetime)
|
|
{
|
|
const int month = datetime.date().month();
|
|
const int hour = datetime.time().hour();
|
|
if (month <= 3 || month >= 9) {
|
|
return (hour >= 19 || hour <= 6);
|
|
}
|
|
return (hour >= 20 || hour <= 5);
|
|
}
|
|
|
|
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::isNightTime() const { return kIsNightTime(d->dt()); }
|
|
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<int>(seconds / 86400);
|
|
int secs = static_cast<int>(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<int>(msecs / 86400000)));
|
|
return result;
|
|
}
|
|
qint64 secs = msecs / 1000;
|
|
int oldms = d->dt().time().msec();
|
|
int ms = oldms + static_cast<int>(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<int>(secs / 86400);
|
|
int seconds = static_cast<int>(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<int>(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<qint64>(d->date().daysTo(dat)) * 86400;
|
|
}
|
|
if (t2.d->dateOnly())
|
|
return static_cast<qint64>(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<qint64>(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<qint64>(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<Comparison>(AtStart|Inside)
|
|
: static_cast<Comparison>(AtStart|Inside|AtEnd|After);
|
|
if (start1 < start2)
|
|
return (end1 < start2) ? Before
|
|
: (end1 == end2) ? static_cast<Comparison>(Before|AtStart|Inside|AtEnd)
|
|
: (end1 == start2) ? static_cast<Comparison>(Before|AtStart)
|
|
: (end1 < end2) ? static_cast<Comparison>(Before|AtStart|Inside) : Outside;
|
|
else
|
|
return (start1 > end2) ? After
|
|
: (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast<Comparison>(AtEnd|After))
|
|
: (end1 == end2) ? static_cast<Comparison>(Inside|AtEnd)
|
|
: (end1 < end2) ? Inside : static_cast<Comparison>(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<int> 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;
|
|
}
|
|
|