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