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