KDECore
kdatetime.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the KDE libraries 00003 Copyright (c) 2005-2011 David Jarvie <djarvie@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kdatetime.h" 00022 00023 #include <config.h> 00024 #include <config-date.h> 00025 00026 #ifdef HAVE_SYS_TIME_H 00027 #include <sys/time.h> 00028 #endif 00029 #ifdef HAVE_TIME_H 00030 #include <time.h> 00031 #endif 00032 #include <stdlib.h> 00033 #include <stdio.h> 00034 #include <ctype.h> 00035 00036 #include <QtCore/QDateTime> 00037 #include <QtCore/QRegExp> 00038 #include <QtCore/QStringList> 00039 #include <QtCore/QSharedData> 00040 00041 #include <kglobal.h> 00042 #include <klocale.h> 00043 #include "kcalendarsystemqdate_p.h" 00044 #include <ksystemtimezone.h> 00045 #include <kdebug.h> 00046 00047 #ifdef Q_OS_WIN 00048 #include <windows.h> // SYSTEMTIME 00049 #endif 00050 00051 00052 static const char shortDay[][4] = { 00053 "Mon", "Tue", "Wed", 00054 "Thu", "Fri", "Sat", 00055 "Sun" 00056 }; 00057 static const char longDay[][10] = { 00058 "Monday", "Tuesday", "Wednesday", 00059 "Thursday", "Friday", "Saturday", 00060 "Sunday" 00061 }; 00062 static const char shortMonth[][4] = { 00063 "Jan", "Feb", "Mar", "Apr", 00064 "May", "Jun", "Jul", "Aug", 00065 "Sep", "Oct", "Nov", "Dec" 00066 }; 00067 static const char longMonth[][10] = { 00068 "January", "February", "March", 00069 "April", "May", "June", 00070 "July", "August", "September", 00071 "October", "November", "December" 00072 }; 00073 00074 00075 // The reason for the KDateTime being invalid, returned from KDateTime::fromString() 00076 enum Status { 00077 stValid = 0, // either valid, or really invalid 00078 stTooEarly // invalid (valid date before QDate range) 00079 }; 00080 00081 00082 static QDateTime fromStr(const QString& string, const QString& format, int& utcOffset, 00083 QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status&); 00084 static int matchDay(const QString &string, int &offset, KCalendarSystem*); 00085 static int matchMonth(const QString &string, int &offset, KCalendarSystem*); 00086 static bool getUTCOffset(const QString &string, int &offset, bool colon, int &result); 00087 static int getAmPm(const QString &string, int &offset, KLocale*); 00088 static bool getNumber(const QString &string, int &offset, int mindigits, int maxdigits, int minval, int maxval, int &result); 00089 static int findString_internal(const QString &string, const char *ptr, int count, int &offset, int disp); 00090 template<int disp> static inline 00091 int findString(const QString &string, const char array[][disp], int count, int &offset) 00092 { return findString_internal(string, array[0], count, offset, disp); } 00093 static QDate checkDate(int year, int month, int day, Status&); 00094 00095 static const int MIN_YEAR = -4712; // minimum year which QDate allows 00096 static const int NO_NUMBER = 0x8000000; // indicates that no number is present in string conversion functions 00097 00098 #ifdef COMPILING_TESTS 00099 KDECORE_EXPORT int KDateTime_utcCacheHit = 0; 00100 KDECORE_EXPORT int KDateTime_zoneCacheHit = 0; 00101 #endif 00102 00103 /*----------------------------------------------------------------------------*/ 00104 00105 class KDateTimeSpecPrivate 00106 { 00107 public: 00108 KDateTimeSpecPrivate() : utcOffset(0) {} 00109 // *** NOTE: This structure is replicated in KDateTimePrivate. Any changes must be copied there. 00110 KTimeZone tz; // if type == TimeZone, the instance's time zone. 00111 int utcOffset; // if type == OffsetFromUTC, the offset from UTC 00112 KDateTime::SpecType type; // time spec type 00113 }; 00114 00115 00116 KDateTime::Spec::Spec() 00117 : d(new KDateTimeSpecPrivate) 00118 { 00119 d->type = KDateTime::Invalid; 00120 } 00121 00122 KDateTime::Spec::Spec(const KTimeZone &tz) 00123 : d(new KDateTimeSpecPrivate()) 00124 { 00125 setType(tz); 00126 } 00127 00128 KDateTime::Spec::Spec(SpecType type, int utcOffset) 00129 : d(new KDateTimeSpecPrivate()) 00130 { 00131 setType(type, utcOffset); 00132 } 00133 00134 KDateTime::Spec::Spec(const Spec& spec) 00135 : d(new KDateTimeSpecPrivate()) 00136 { 00137 operator=(spec); 00138 } 00139 00140 KDateTime::Spec::~Spec() 00141 { 00142 delete d; 00143 } 00144 00145 KDateTime::Spec &KDateTime::Spec::operator=(const Spec& spec) 00146 { 00147 if (&spec != this) 00148 { 00149 d->type = spec.d->type; 00150 if (d->type == KDateTime::TimeZone) 00151 d->tz = spec.d->tz; 00152 else if (d->type == KDateTime::OffsetFromUTC) 00153 d->utcOffset = spec.d->utcOffset; 00154 } 00155 return *this; 00156 } 00157 00158 void KDateTime::Spec::setType(SpecType type, int utcOffset) 00159 { 00160 switch (type) 00161 { 00162 case KDateTime::OffsetFromUTC: 00163 d->utcOffset = utcOffset; 00164 // fall through to UTC 00165 case KDateTime::UTC: 00166 case KDateTime::ClockTime: 00167 d->type = type; 00168 break; 00169 case KDateTime::LocalZone: 00170 d->tz = KSystemTimeZones::local(); 00171 d->type = KDateTime::TimeZone; 00172 break; 00173 case KDateTime::TimeZone: 00174 default: 00175 d->type = KDateTime::Invalid; 00176 break; 00177 } 00178 } 00179 00180 void KDateTime::Spec::setType(const KTimeZone &tz) 00181 { 00182 if (tz == KTimeZone::utc()) 00183 d->type = KDateTime::UTC; 00184 else if (tz.isValid()) 00185 { 00186 d->type = KDateTime::TimeZone; 00187 d->tz = tz; 00188 } 00189 else 00190 d->type = KDateTime::Invalid; 00191 } 00192 00193 KTimeZone KDateTime::Spec::timeZone() const 00194 { 00195 if (d->type == KDateTime::TimeZone) 00196 return d->tz; 00197 if (d->type == KDateTime::UTC) 00198 return KTimeZone::utc(); 00199 return KTimeZone(); 00200 } 00201 00202 bool KDateTime::Spec::isUtc() const 00203 { 00204 if (d->type == KDateTime::UTC 00205 || (d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) 00206 return true; 00207 return false; 00208 } 00209 00210 KDateTime::Spec KDateTime::Spec::UTC() { return Spec(KDateTime::UTC); } 00211 KDateTime::Spec KDateTime::Spec::ClockTime() { return Spec(KDateTime::ClockTime); } 00212 KDateTime::Spec KDateTime::Spec::LocalZone() { return Spec(KDateTime::LocalZone); } 00213 KDateTime::Spec KDateTime::Spec::OffsetFromUTC(int utcOffset) { return Spec(KDateTime::OffsetFromUTC, utcOffset); } 00214 KDateTime::SpecType KDateTime::Spec::type() const { return d->type; } 00215 bool KDateTime::Spec::isValid() const { return d->type != KDateTime::Invalid; } 00216 bool KDateTime::Spec::isLocalZone() const { return d->type == KDateTime::TimeZone && d->tz == KSystemTimeZones::local(); } 00217 bool KDateTime::Spec::isClockTime() const { return d->type == KDateTime::ClockTime; } 00218 bool KDateTime::Spec::isOffsetFromUtc() const { return d->type == KDateTime::OffsetFromUTC; } 00219 int KDateTime::Spec::utcOffset() const { return d->type == KDateTime::OffsetFromUTC ? d->utcOffset : 0; } 00220 00221 bool KDateTime::Spec::operator==(const Spec &other) const 00222 { 00223 if (d->type != other.d->type 00224 || (d->type == KDateTime::TimeZone && d->tz != other.d->tz) 00225 || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) 00226 return false; 00227 return true; 00228 } 00229 00230 bool KDateTime::Spec::equivalentTo(const Spec &other) const 00231 { 00232 if (d->type == other.d->type) 00233 { 00234 if ((d->type == KDateTime::TimeZone && d->tz != other.d->tz) 00235 || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) 00236 return false; 00237 return true; 00238 } 00239 else 00240 { 00241 if ((d->type == KDateTime::UTC && other.d->type == KDateTime::OffsetFromUTC && other.d->utcOffset == 0) 00242 || (other.d->type == KDateTime::UTC && d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) 00243 return true; 00244 return false; 00245 } 00246 } 00247 00248 QDataStream & operator<<(QDataStream &s, const KDateTime::Spec &spec) 00249 { 00250 // The specification type is encoded in order to insulate from changes 00251 // to the SpecType enum. 00252 switch (spec.type()) 00253 { 00254 case KDateTime::UTC: 00255 s << static_cast<quint8>('u'); 00256 break; 00257 case KDateTime::OffsetFromUTC: 00258 s << static_cast<quint8>('o') << spec.utcOffset(); 00259 break; 00260 case KDateTime::TimeZone: 00261 s << static_cast<quint8>('z') << (spec.timeZone().isValid() ? spec.timeZone().name() : QString()); 00262 break; 00263 case KDateTime::ClockTime: 00264 s << static_cast<quint8>('c'); 00265 break; 00266 case KDateTime::Invalid: 00267 default: 00268 s << static_cast<quint8>(' '); 00269 break; 00270 } 00271 return s; 00272 } 00273 00274 QDataStream & operator>>(QDataStream &s, KDateTime::Spec &spec) 00275 { 00276 // The specification type is encoded in order to insulate from changes 00277 // to the SpecType enum. 00278 quint8 t; 00279 s >> t; 00280 switch (static_cast<char>(t)) 00281 { 00282 case 'u': 00283 spec.setType(KDateTime::UTC); 00284 break; 00285 case 'o': 00286 { 00287 int utcOffset; 00288 s >> utcOffset; 00289 spec.setType(KDateTime::OffsetFromUTC, utcOffset); 00290 break; 00291 } 00292 case 'z': 00293 { 00294 QString zone; 00295 s >> zone; 00296 KTimeZone tz = KSystemTimeZones::zone(zone); 00297 spec.setType(tz); 00298 break; 00299 } 00300 case 'c': 00301 spec.setType(KDateTime::ClockTime); 00302 break; 00303 default: 00304 spec.setType(KDateTime::Invalid); 00305 break; 00306 } 00307 return s; 00308 } 00309 00310 00311 /*----------------------------------------------------------------------------*/ 00312 00313 K_GLOBAL_STATIC_WITH_ARGS(KDateTime::Spec, s_fromStringDefault, (KDateTime::ClockTime)) 00314 00315 class KDateTimePrivate : public QSharedData 00316 { 00317 public: 00318 KDateTimePrivate() 00319 : QSharedData(), 00320 specType(KDateTime::Invalid), 00321 status(stValid), 00322 utcCached(true), 00323 convertedCached(false), 00324 m2ndOccurrence(false), 00325 mDateOnly(false) 00326 { 00327 } 00328 00329 KDateTimePrivate(const QDateTime &d, const KDateTime::Spec &s, bool donly = false) 00330 : QSharedData(), 00331 mDt(d), 00332 specType(s.type()), 00333 status(stValid), 00334 utcCached(false), 00335 convertedCached(false), 00336 m2ndOccurrence(false), 00337 mDateOnly(donly) 00338 { 00339 switch (specType) 00340 { 00341 case KDateTime::TimeZone: 00342 specZone = s.timeZone(); 00343 break; 00344 case KDateTime::OffsetFromUTC: 00345 specUtcOffset= s.utcOffset(); 00346 break; 00347 case KDateTime::Invalid: 00348 utcCached = true; 00349 // fall through to UTC 00350 case KDateTime::UTC: 00351 default: 00352 break; 00353 } 00354 } 00355 00356 KDateTimePrivate(const KDateTimePrivate &rhs) 00357 : QSharedData(rhs), 00358 mDt(rhs.mDt), 00359 specZone(rhs.specZone), 00360 specUtcOffset(rhs.specUtcOffset), 00361 ut(rhs.ut), 00362 converted(rhs.converted), 00363 specType(rhs.specType), 00364 status(rhs.status), 00365 utcCached(rhs.utcCached), 00366 convertedCached(rhs.convertedCached), 00367 m2ndOccurrence(rhs.m2ndOccurrence), 00368 mDateOnly(rhs.mDateOnly), 00369 converted2ndOccur(rhs.converted2ndOccur) 00370 {} 00371 00372 ~KDateTimePrivate() {} 00373 const QDateTime& dt() const { return mDt; } 00374 const QDate date() const { return mDt.date(); } 00375 KDateTime::Spec spec() const; 00376 QDateTime utc() const { return QDateTime(ut.date, ut.time, Qt::UTC); } 00377 bool dateOnly() const { return mDateOnly; } 00378 bool secondOccurrence() const { return m2ndOccurrence; } 00379 void setDt(const QDateTime &dt) { mDt = dt; utcCached = convertedCached = m2ndOccurrence = false; } 00380 void setDtFromUtc(const QDateTime &utcdt); 00381 void setDate(const QDate &d) { mDt.setDate(d); utcCached = convertedCached = m2ndOccurrence = false; } 00382 void setTime(const QTime &t) { mDt.setTime(t); utcCached = convertedCached = mDateOnly = m2ndOccurrence = false; } 00383 void setDtTimeSpec(Qt::TimeSpec s) { mDt.setTimeSpec(s); utcCached = convertedCached = m2ndOccurrence = false; } 00384 void setSpec(const KDateTime::Spec&); 00385 void setDateOnly(bool d); 00386 int timeZoneOffset() const; 00387 QDateTime toUtc(const KTimeZone &local = KTimeZone()) const; 00388 QDateTime toZone(const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const; 00389 void newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const; 00390 bool equalSpec(const KDateTimePrivate&) const; 00391 void clearCache() { utcCached = convertedCached = false; } 00392 void setDt(const QDateTime &dt, const QDateTime &utcDt) 00393 { 00394 mDt = dt; 00395 ut.date = utcDt.date(); 00396 ut.time = utcDt.time(); 00397 utcCached = true; 00398 convertedCached = false; 00399 m2ndOccurrence = false; 00400 } 00401 void setUtc(const QDateTime &dt) const 00402 { 00403 ut.date = dt.date(); 00404 ut.time = dt.time(); 00405 utcCached = true; 00406 convertedCached = false; 00407 } 00408 00409 /* Initialise the date/time for specType = UTC, from a time zone time, 00410 * and cache the time zone time. 00411 */ 00412 void setUtcFromTz(const QDateTime &dt, const KTimeZone &tz) 00413 { 00414 if (specType == KDateTime::UTC) 00415 { 00416 mDt = tz.toUtc(dt); 00417 utcCached = false; 00418 converted.date = dt.date(); 00419 converted.time = dt.time(); 00420 converted.tz = tz; 00421 convertedCached = true; 00422 converted2ndOccur = false; // KTimeZone::toUtc() returns the first occurrence 00423 } 00424 } 00425 00426 // Default time spec used by fromString() 00427 static KDateTime::Spec& fromStringDefault() 00428 { 00429 return *s_fromStringDefault; 00430 } 00431 00432 00433 static QTime sod; // start of day (00:00:00) 00434 #ifndef NDEBUG 00435 static qint64 currentDateTimeOffset; // offset to apply to current system time 00436 #endif 00437 00438 /* Because some applications create thousands of instances of KDateTime, this 00439 * data structure is designed to minimize memory usage. Ensure that all small 00440 * members are kept together at the end! 00441 */ 00442 private: 00443 QDateTime mDt; 00444 public: 00445 KTimeZone specZone; // if specType == TimeZone, the instance's time zone 00446 // if specType == ClockTime, the local time zone used to calculate the cached UTC time (mutable) 00447 int specUtcOffset; // if specType == OffsetFromUTC, the offset from UTC 00448 mutable struct ut { // cached UTC equivalent of 'mDt'. Saves space compared to storing QDateTime. 00449 QDate date; 00450 QTime time; 00451 } ut; 00452 private: 00453 mutable struct converted { // cached conversion to another time zone (if 'tz' is valid) 00454 QDate date; 00455 QTime time; 00456 KTimeZone tz; 00457 } converted; 00458 public: 00459 KDateTime::SpecType specType : 4; // time spec type (N.B. need 3 bits + sign bit, since enums are signed on some platforms) 00460 Status status : 2; // reason for invalid status 00461 mutable bool utcCached : 1; // true if 'ut' is valid 00462 mutable bool convertedCached : 1; // true if 'converted' is valid 00463 mutable bool m2ndOccurrence : 1; // this is the second occurrence of a time zone time 00464 private: 00465 bool mDateOnly : 1; // true to ignore the time part 00466 mutable bool converted2ndOccur : 1; // this is the second occurrence of 'converted' time 00467 }; 00468 00469 00470 QTime KDateTimePrivate::sod(0,0,0); 00471 #ifndef NDEBUG 00472 qint64 KDateTimePrivate::currentDateTimeOffset = 0; 00473 #endif 00474 00475 KDateTime::Spec KDateTimePrivate::spec() const 00476 { 00477 if (specType == KDateTime::TimeZone) 00478 return KDateTime::Spec(specZone); 00479 else 00480 return KDateTime::Spec(specType, specUtcOffset); 00481 } 00482 00483 void KDateTimePrivate::setSpec(const KDateTime::Spec &other) 00484 { 00485 if (specType == other.type()) 00486 { 00487 switch (specType) 00488 { 00489 case KDateTime::TimeZone: 00490 { 00491 KTimeZone tz = other.timeZone(); 00492 if (specZone == tz) 00493 return; 00494 specZone = tz; 00495 break; 00496 } 00497 case KDateTime::OffsetFromUTC: 00498 { 00499 int offset = other.utcOffset(); 00500 if (specUtcOffset == offset) 00501 return; 00502 specUtcOffset = offset; 00503 break; 00504 } 00505 default: 00506 return; 00507 } 00508 utcCached = false; 00509 } 00510 else 00511 { 00512 specType = other.type(); 00513 switch (specType) 00514 { 00515 case KDateTime::TimeZone: 00516 specZone = other.timeZone(); 00517 break; 00518 case KDateTime::OffsetFromUTC: 00519 specUtcOffset = other.utcOffset(); 00520 break; 00521 case KDateTime::Invalid: 00522 ut.date = QDate(); // cache an invalid UTC value 00523 utcCached = true; 00524 // fall through to UTC 00525 case KDateTime::UTC: 00526 default: 00527 break; 00528 } 00529 } 00530 convertedCached = false; 00531 setDtTimeSpec((specType == KDateTime::UTC) ? Qt::UTC : Qt::LocalTime); // this clears cached UTC value 00532 } 00533 00534 bool KDateTimePrivate::equalSpec(const KDateTimePrivate &other) const 00535 { 00536 if (specType != other.specType 00537 || (specType == KDateTime::TimeZone && specZone != other.specZone) 00538 || (specType == KDateTime::OffsetFromUTC && specUtcOffset != other.specUtcOffset)) 00539 return false; 00540 return true; 00541 } 00542 00543 void KDateTimePrivate::setDateOnly(bool dateOnly) 00544 { 00545 if (dateOnly != mDateOnly) 00546 { 00547 mDateOnly = dateOnly; 00548 if (dateOnly && mDt.time() != sod) 00549 { 00550 mDt.setTime(sod); 00551 utcCached = false; 00552 convertedCached = false; 00553 } 00554 m2ndOccurrence = false; 00555 } 00556 } 00557 00558 /* Sets the date/time to a given UTC date/time. The time spec is not changed. */ 00559 void KDateTimePrivate::setDtFromUtc(const QDateTime &utcdt) 00560 { 00561 switch (specType) 00562 { 00563 case KDateTime::UTC: 00564 setDt(utcdt); 00565 break; 00566 case KDateTime::OffsetFromUTC: 00567 { 00568 QDateTime local = utcdt.addSecs(specUtcOffset); 00569 local.setTimeSpec(Qt::LocalTime); 00570 setDt(local, utcdt); 00571 break; 00572 } 00573 case KDateTime::TimeZone: 00574 { 00575 bool second; 00576 setDt(specZone.toZoneTime(utcdt, &second), utcdt); 00577 m2ndOccurrence = second; 00578 break; 00579 } 00580 case KDateTime::ClockTime: 00581 specZone = KSystemTimeZones::local(); 00582 setDt(specZone.toZoneTime(utcdt), utcdt); 00583 break; 00584 default: // invalid 00585 break; 00586 } 00587 } 00588 00589 /* 00590 * Returns the UTC offset for the date/time, provided that it is a time zone type. 00591 */ 00592 int KDateTimePrivate::timeZoneOffset() const 00593 { 00594 if (specType != KDateTime::TimeZone) 00595 return KTimeZone::InvalidOffset; 00596 if (utcCached) 00597 { 00598 QDateTime dt = mDt; 00599 dt.setTimeSpec(Qt::UTC); 00600 return utc().secsTo(dt); 00601 } 00602 int secondOffset; 00603 if (!specZone.isValid()) { 00604 return KTimeZone::InvalidOffset; 00605 } 00606 int offset = specZone.offsetAtZoneTime(mDt, &secondOffset); 00607 if (m2ndOccurrence) 00608 { 00609 m2ndOccurrence = (secondOffset != offset); // cancel "second occurrence" flag if not applicable 00610 offset = secondOffset; 00611 } 00612 if (offset == KTimeZone::InvalidOffset) 00613 { 00614 ut.date = QDate(); 00615 utcCached = true; 00616 convertedCached = false; 00617 } 00618 else 00619 { 00620 // Calculate the UTC time from the offset and cache it 00621 QDateTime utcdt = mDt; 00622 utcdt.setTimeSpec(Qt::UTC); 00623 setUtc(utcdt.addSecs(-offset)); 00624 } 00625 return offset; 00626 } 00627 00628 /* 00629 * Returns the date/time converted to UTC. 00630 * Depending on which KTimeZone class is involved, conversion to UTC may require 00631 * significant calculation, so the calculated UTC value is cached. 00632 */ 00633 QDateTime KDateTimePrivate::toUtc(const KTimeZone &local) const 00634 { 00635 KTimeZone loc(local); 00636 if (utcCached) 00637 { 00638 // Return cached UTC value 00639 if (specType == KDateTime::ClockTime) 00640 { 00641 // ClockTime uses the dynamic current local system time zone. 00642 // Check for a time zone change before using the cached UTC value. 00643 if (!local.isValid()) 00644 loc = KSystemTimeZones::local(); 00645 if (specZone == loc) 00646 { 00647 // kDebug() << "toUtc(): cached -> " << utc() << endl, 00648 #ifdef COMPILING_TESTS 00649 ++KDateTime_utcCacheHit; 00650 #endif 00651 return utc(); 00652 } 00653 } 00654 else 00655 { 00656 // kDebug() << "toUtc(): cached -> " << utc() << endl, 00657 #ifdef COMPILING_TESTS 00658 ++KDateTime_utcCacheHit; 00659 #endif 00660 return utc(); 00661 } 00662 } 00663 00664 // No cached UTC value, so calculate it 00665 switch (specType) 00666 { 00667 case KDateTime::UTC: 00668 return mDt; 00669 case KDateTime::OffsetFromUTC: 00670 { 00671 if (!mDt.isValid()) 00672 break; 00673 QDateTime dt = QDateTime(mDt.date(), mDt.time(), Qt::UTC).addSecs(-specUtcOffset); 00674 setUtc(dt); 00675 // kDebug() << "toUtc(): calculated -> " << dt << endl, 00676 return dt; 00677 } 00678 case KDateTime::ClockTime: 00679 { 00680 if (!mDt.isValid()) 00681 break; 00682 if (!loc.isValid()) 00683 loc = KSystemTimeZones::local(); 00684 const_cast<KDateTimePrivate*>(this)->specZone = loc; 00685 QDateTime dt(specZone.toUtc(mDt)); 00686 setUtc(dt); 00687 // kDebug() << "toUtc(): calculated -> " << dt << endl, 00688 return dt; 00689 } 00690 case KDateTime::TimeZone: 00691 if (!mDt.isValid()) 00692 break; 00693 timeZoneOffset(); // calculate offset and cache UTC value 00694 // kDebug() << "toUtc(): calculated -> " << utc() << endl, 00695 return utc(); 00696 default: 00697 break; 00698 } 00699 00700 // Invalid - mark it cached to avoid having to process it again 00701 ut.date = QDate(); // (invalid) 00702 utcCached = true; 00703 convertedCached = false; 00704 // kDebug() << "toUtc(): invalid"; 00705 return mDt; 00706 } 00707 00708 /* Convert this value to another time zone. 00709 * The value is cached to save having to repeatedly calculate it. 00710 * The caller should check for an invalid date/time. 00711 */ 00712 QDateTime KDateTimePrivate::toZone(const KTimeZone &zone, const KTimeZone &local) const 00713 { 00714 if (convertedCached && converted.tz == zone) 00715 { 00716 // Converted value is already cached 00717 #ifdef COMPILING_TESTS 00718 // kDebug() << "KDateTimePrivate::toZone(" << zone->name() << "): " << mDt << " cached"; 00719 ++KDateTime_zoneCacheHit; 00720 #endif 00721 return QDateTime(converted.date, converted.time, Qt::LocalTime); 00722 } 00723 else 00724 { 00725 // Need to convert the value 00726 bool second; 00727 QDateTime result = zone.toZoneTime(toUtc(local), &second); 00728 converted.date = result.date(); 00729 converted.time = result.time(); 00730 converted.tz = zone; 00731 convertedCached = true; 00732 converted2ndOccur = second; 00733 return result; 00734 } 00735 } 00736 00737 /* Convert this value to another time zone, and write it into the specified instance. 00738 * The value is cached to save having to repeatedly calculate it. 00739 * The caller should check for an invalid date/time. 00740 */ 00741 void KDateTimePrivate::newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local) const 00742 { 00743 newd->mDt = toZone(zone, local); 00744 newd->specZone = zone; 00745 newd->specType = KDateTime::TimeZone; 00746 newd->utcCached = utcCached; 00747 newd->mDateOnly = mDateOnly; 00748 newd->m2ndOccurrence = converted2ndOccur; 00749 switch (specType) 00750 { 00751 case KDateTime::UTC: 00752 newd->ut.date = mDt.date(); // cache the UTC value 00753 newd->ut.time = mDt.time(); 00754 break; 00755 case KDateTime::TimeZone: 00756 // This instance is also type time zone, so cache its value in the new instance 00757 newd->converted.date = mDt.date(); 00758 newd->converted.time = mDt.time(); 00759 newd->converted.tz = specZone; 00760 newd->convertedCached = true; 00761 newd->converted2ndOccur = m2ndOccurrence; 00762 newd->ut = ut; 00763 return; 00764 default: 00765 newd->ut = ut; 00766 break; 00767 } 00768 newd->convertedCached = false; 00769 } 00770 00771 00772 /*----------------------------------------------------------------------------*/ 00773 K_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KDateTimePrivate>, emptyDateTimePrivate, (new KDateTimePrivate)) 00774 00775 KDateTime::KDateTime() 00776 : d(*emptyDateTimePrivate) 00777 { 00778 } 00779 00780 KDateTime::KDateTime(const QDate &date, const Spec &spec) 00781 : d(new KDateTimePrivate(QDateTime(date, KDateTimePrivate::sod, Qt::LocalTime), spec, true)) 00782 { 00783 if (spec.type() == UTC) 00784 d->setDtTimeSpec(Qt::UTC); 00785 } 00786 00787 KDateTime::KDateTime(const QDate &date, const QTime &time, const Spec &spec) 00788 : d(new KDateTimePrivate(QDateTime(date, time, Qt::LocalTime), spec)) 00789 { 00790 if (spec.type() == UTC) 00791 d->setDtTimeSpec(Qt::UTC); 00792 } 00793 00794 KDateTime::KDateTime(const QDateTime &dt, const Spec &spec) 00795 : d(new KDateTimePrivate(dt, spec)) 00796 { 00797 // If the supplied date/time is UTC and we need local time, or vice versa, convert it. 00798 if (spec.type() == UTC) 00799 { 00800 if (dt.timeSpec() == Qt::LocalTime) 00801 d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time 00802 } 00803 else if (dt.timeSpec() == Qt::UTC) 00804 d->setDtFromUtc(dt); 00805 } 00806 00807 KDateTime::KDateTime(const QDateTime &dt) 00808 : d(new KDateTimePrivate(dt, (dt.timeSpec() == Qt::LocalTime ? Spec(LocalZone) : Spec(UTC)))) 00809 { 00810 } 00811 00812 KDateTime::KDateTime(const KDateTime &other) 00813 : d(other.d) 00814 { 00815 } 00816 00817 KDateTime::~KDateTime() 00818 { 00819 } 00820 00821 KDateTime &KDateTime::operator=(const KDateTime &other) 00822 { 00823 if (&other != this) 00824 d = other.d; 00825 return *this; 00826 } 00827 00828 void KDateTime::detach() { d.detach(); } 00829 bool KDateTime::isNull() const { return d->dt().isNull(); } 00830 bool KDateTime::isValid() const { return d->specType != Invalid && d->dt().isValid(); } 00831 bool KDateTime::outOfRange() const { return d->status == stTooEarly; } 00832 bool KDateTime::isDateOnly() const { return d->dateOnly(); } 00833 bool KDateTime::isLocalZone() const { return d->specType == TimeZone && d->specZone == KSystemTimeZones::local(); } 00834 bool KDateTime::isClockTime() const { return d->specType == ClockTime; } 00835 bool KDateTime::isUtc() const { return d->specType == UTC || (d->specType == OffsetFromUTC && d->specUtcOffset == 0); } 00836 bool KDateTime::isOffsetFromUtc() const { return d->specType == OffsetFromUTC; } 00837 bool KDateTime::isSecondOccurrence() const { return d->specType == TimeZone && d->secondOccurrence(); } 00838 QDate KDateTime::date() const { return d->date(); } 00839 QTime KDateTime::time() const { return d->dt().time(); } 00840 QDateTime KDateTime::dateTime() const { return d->dt(); } 00841 00842 KDateTime::Spec KDateTime::timeSpec() const { return d->spec(); } 00843 KDateTime::SpecType KDateTime::timeType() const { return d->specType; } 00844 00845 KTimeZone KDateTime::timeZone() const 00846 { 00847 switch (d->specType) 00848 { 00849 case TimeZone: 00850 return d->specZone; 00851 case UTC: 00852 return KTimeZone::utc(); 00853 default: 00854 return KTimeZone(); 00855 } 00856 } 00857 00858 int KDateTime::utcOffset() const 00859 { 00860 switch (d->specType) 00861 { 00862 case TimeZone: 00863 return d->timeZoneOffset(); // calculate offset and cache UTC value 00864 case OffsetFromUTC: 00865 return d->specUtcOffset; 00866 default: 00867 return 0; 00868 } 00869 } 00870 00871 KDateTime KDateTime::toUtc() const 00872 { 00873 if (!isValid()) 00874 return KDateTime(); 00875 if (d->specType == UTC) 00876 return *this; 00877 if (d->dateOnly()) 00878 return KDateTime(d->date(), UTC); 00879 QDateTime udt = d->toUtc(); 00880 if (!udt.isValid()) 00881 return KDateTime(); 00882 return KDateTime(udt, UTC); 00883 } 00884 00885 KDateTime KDateTime::toOffsetFromUtc() const 00886 { 00887 if (!isValid()) 00888 return KDateTime(); 00889 int offset = 0; 00890 switch (d->specType) 00891 { 00892 case OffsetFromUTC: 00893 return *this; 00894 case UTC: 00895 { 00896 if (d->dateOnly()) 00897 return KDateTime(d->date(), Spec(OffsetFromUTC, 0)); 00898 QDateTime qdt = d->dt(); 00899 qdt.setTimeSpec(Qt::LocalTime); 00900 return KDateTime(qdt, Spec(OffsetFromUTC, 0)); 00901 } 00902 case TimeZone: 00903 offset = d->timeZoneOffset(); // calculate offset and cache UTC value 00904 break; 00905 case ClockTime: 00906 offset = KSystemTimeZones::local().offsetAtZoneTime(d->dt()); 00907 break; 00908 default: 00909 return KDateTime(); 00910 } 00911 if (d->dateOnly()) 00912 return KDateTime(d->date(), Spec(OffsetFromUTC, offset)); 00913 return KDateTime(d->dt(), Spec(OffsetFromUTC, offset)); 00914 } 00915 00916 KDateTime KDateTime::toOffsetFromUtc(int utcOffset) const 00917 { 00918 if (!isValid()) 00919 return KDateTime(); 00920 if (d->specType == OffsetFromUTC && d->specUtcOffset == utcOffset) 00921 return *this; 00922 if (d->dateOnly()) 00923 return KDateTime(d->date(), Spec(OffsetFromUTC, utcOffset)); 00924 return KDateTime(d->toUtc(), Spec(OffsetFromUTC, utcOffset)); 00925 } 00926 00927 KDateTime KDateTime::toLocalZone() const 00928 { 00929 if (!isValid()) 00930 return KDateTime(); 00931 KTimeZone local = KSystemTimeZones::local(); 00932 if (d->specType == TimeZone && d->specZone == local) 00933 return *this; // it's already local zone. Preserve UTC cache, if any 00934 if (d->dateOnly()) 00935 return KDateTime(d->date(), Spec(local)); 00936 switch (d->specType) 00937 { 00938 case TimeZone: 00939 case OffsetFromUTC: 00940 case UTC: 00941 { 00942 KDateTime result; 00943 d->newToZone(result.d, local, local); // cache the time zone conversion 00944 return result; 00945 } 00946 case ClockTime: 00947 return KDateTime(d->dt(), Spec(local)); 00948 default: 00949 return KDateTime(); 00950 } 00951 } 00952 00953 KDateTime KDateTime::toClockTime() const 00954 { 00955 if (!isValid()) 00956 return KDateTime(); 00957 if (d->specType == ClockTime) 00958 return *this; 00959 if (d->dateOnly()) 00960 return KDateTime(d->date(), Spec(ClockTime)); 00961 KDateTime result = toLocalZone(); 00962 result.d->specType = ClockTime; // cached value (if any) is unaffected 00963 return result; 00964 } 00965 00966 KDateTime KDateTime::toZone(const KTimeZone &zone) const 00967 { 00968 if (!zone.isValid() || !isValid()) 00969 return KDateTime(); 00970 if (d->specType == TimeZone && d->specZone == zone) 00971 return *this; // preserve UTC cache, if any 00972 if (d->dateOnly()) 00973 return KDateTime(d->date(), Spec(zone)); 00974 KDateTime result; 00975 d->newToZone(result.d, zone); // cache the time zone conversion 00976 return result; 00977 } 00978 00979 KDateTime KDateTime::toTimeSpec(const KDateTime &dt) const 00980 { 00981 return toTimeSpec(dt.timeSpec()); 00982 } 00983 00984 KDateTime KDateTime::toTimeSpec(const Spec &spec) const 00985 { 00986 if (spec == d->spec()) 00987 return *this; 00988 if (!isValid()) 00989 return KDateTime(); 00990 if (d->dateOnly()) 00991 return KDateTime(d->date(), spec); 00992 if (spec.type() == TimeZone) 00993 { 00994 KDateTime result; 00995 d->newToZone(result.d, spec.timeZone()); // cache the time zone conversion 00996 return result; 00997 } 00998 return KDateTime(d->toUtc(), spec); 00999 } 01000 01001 uint KDateTime::toTime_t() const 01002 { 01003 QDateTime qdt = d->toUtc(); 01004 if (!qdt.isValid()) 01005 return uint(-1); 01006 return qdt.toTime_t(); 01007 } 01008 01009 void KDateTime::setTime_t(qint64 seconds) 01010 { 01011 d->setSpec(UTC); 01012 int days = static_cast<int>(seconds / 86400); 01013 int secs = static_cast<int>(seconds % 86400); 01014 QDateTime dt; 01015 dt.setTimeSpec(Qt::UTC); // prevent QDateTime::setTime_t() converting to local time 01016 dt.setTime_t(0); 01017 d->setDt(dt.addDays(days).addSecs(secs)); 01018 } 01019 01020 void KDateTime::setDateOnly(bool dateOnly) 01021 { 01022 d->setDateOnly(dateOnly); 01023 } 01024 01025 void KDateTime::setDate(const QDate &date) 01026 { 01027 d->setDate(date); 01028 } 01029 01030 void KDateTime::setTime(const QTime &time) 01031 { 01032 d->setTime(time); 01033 } 01034 01035 void KDateTime::setDateTime(const QDateTime &dt) 01036 { 01037 d->clearCache(); 01038 d->setDateOnly(false); 01039 if (dt.timeSpec() == Qt::LocalTime) 01040 { 01041 if (d->specType == UTC) 01042 d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time 01043 else 01044 d->setDt(dt); 01045 } 01046 else 01047 d->setDtFromUtc(dt); // a UTC time has been supplied 01048 } 01049 01050 void KDateTime::setTimeSpec(const Spec &other) 01051 { 01052 d->setSpec(other); 01053 } 01054 01055 void KDateTime::setSecondOccurrence(bool second) 01056 { 01057 if (d->specType == KDateTime::TimeZone && second != d->m2ndOccurrence) 01058 { 01059 d->m2ndOccurrence = second; 01060 d->clearCache(); 01061 if (second) 01062 { 01063 // Check whether a second occurrence is actually possible, and 01064 // if not, reset m2ndOccurrence. 01065 d->timeZoneOffset(); // check, and cache UTC value 01066 } 01067 } 01068 } 01069 01070 KDateTime KDateTime::addMSecs(qint64 msecs) const 01071 { 01072 if (!msecs) 01073 return *this; // retain cache - don't create another instance 01074 if (!isValid()) 01075 return KDateTime(); 01076 if (d->dateOnly()) 01077 { 01078 KDateTime result(*this); 01079 result.d->setDate(d->date().addDays(static_cast<int>(msecs / 86400000))); 01080 return result; 01081 } 01082 qint64 secs = msecs / 1000; 01083 int oldms = d->dt().time().msec(); 01084 int ms = oldms + static_cast<int>(msecs % 1000); 01085 if (msecs >= 0) 01086 { 01087 if (ms >= 1000) 01088 { 01089 ++secs; 01090 ms -= 1000; 01091 } 01092 } 01093 else 01094 { 01095 if (ms < 0) 01096 { 01097 --secs; 01098 ms += 1000; 01099 } 01100 } 01101 KDateTime result = addSecs(secs); 01102 QTime t = result.time(); 01103 result.d->setTime(QTime(t.hour(), t.minute(), t.second(), ms)); 01104 return result; 01105 } 01106 01107 KDateTime KDateTime::addSecs(qint64 secs) const 01108 { 01109 if (!secs) 01110 return *this; // retain cache - don't create another instance 01111 if (!isValid()) 01112 return KDateTime(); 01113 int days = static_cast<int>(secs / 86400); 01114 int seconds = static_cast<int>(secs % 86400); 01115 if (d->dateOnly()) 01116 { 01117 KDateTime result(*this); 01118 result.d->setDate(d->date().addDays(days)); 01119 return result; 01120 } 01121 if (d->specType == ClockTime) 01122 { 01123 QDateTime qdt = d->dt(); 01124 qdt.setTimeSpec(Qt::UTC); // set time as UTC to avoid daylight savings adjustments in addSecs() 01125 qdt = qdt.addDays(days).addSecs(seconds); 01126 qdt.setTimeSpec(Qt::LocalTime); 01127 return KDateTime(qdt, Spec(ClockTime)); 01128 } 01129 return KDateTime(d->toUtc().addDays(days).addSecs(seconds), d->spec()); 01130 } 01131 01132 KDateTime KDateTime::addDays(int days) const 01133 { 01134 if (!days) 01135 return *this; // retain cache - don't create another instance 01136 KDateTime result(*this); 01137 result.d->setDate(d->date().addDays(days)); 01138 return result; 01139 } 01140 01141 KDateTime KDateTime::addMonths(int months) const 01142 { 01143 if (!months) 01144 return *this; // retain cache - don't create another instance 01145 KDateTime result(*this); 01146 result.d->setDate(d->date().addMonths(months)); 01147 return result; 01148 } 01149 01150 KDateTime KDateTime::addYears(int years) const 01151 { 01152 if (!years) 01153 return *this; // retain cache - don't create another instance 01154 KDateTime result(*this); 01155 result.d->setDate(d->date().addYears(years)); 01156 return result; 01157 } 01158 01159 int KDateTime::secsTo(const KDateTime &t2) const 01160 { 01161 return static_cast<int>(secsTo_long(t2)); 01162 } 01163 01164 qint64 KDateTime::secsTo_long(const KDateTime &t2) const 01165 { 01166 if (!isValid() || !t2.isValid()) 01167 return 0; 01168 if (d->dateOnly()) 01169 { 01170 QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date(); 01171 return static_cast<qint64>(d->date().daysTo(dat)) * 86400; 01172 } 01173 if (t2.d->dateOnly()) 01174 return static_cast<qint64>(toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date())) * 86400; 01175 01176 QDateTime dt1, dt2; 01177 if (d->specType == ClockTime && t2.d->specType == ClockTime) 01178 { 01179 // Set both times as UTC to avoid daylight savings adjustments in secsTo() 01180 dt1 = d->dt(); 01181 dt1.setTimeSpec(Qt::UTC); 01182 dt2 = t2.d->dt(); 01183 dt2.setTimeSpec(Qt::UTC); 01184 return dt1.secsTo(dt2); 01185 } 01186 else 01187 { 01188 dt1 = d->toUtc(); 01189 dt2 = t2.d->toUtc(); 01190 } 01191 return static_cast<qint64>(dt1.date().daysTo(dt2.date())) * 86400 01192 + dt1.time().secsTo(dt2.time()); 01193 } 01194 01195 int KDateTime::daysTo(const KDateTime &t2) const 01196 { 01197 if (!isValid() || !t2.isValid()) 01198 return 0; 01199 if (d->dateOnly()) 01200 { 01201 QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date(); 01202 return d->date().daysTo(dat); 01203 } 01204 if (t2.d->dateOnly()) 01205 return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date()); 01206 01207 QDate dat; 01208 switch (d->specType) 01209 { 01210 case UTC: 01211 dat = t2.d->toUtc().date(); 01212 break; 01213 case OffsetFromUTC: 01214 dat = t2.d->toUtc().addSecs(d->specUtcOffset).date(); 01215 break; 01216 case TimeZone: 01217 dat = t2.d->toZone(d->specZone).date(); // this caches the converted time in t2 01218 break; 01219 case ClockTime: 01220 { 01221 KTimeZone local = KSystemTimeZones::local(); 01222 dat = t2.d->toZone(local, local).date(); // this caches the converted time in t2 01223 break; 01224 } 01225 default: // invalid 01226 return 0; 01227 } 01228 return d->date().daysTo(dat); 01229 } 01230 01231 KDateTime KDateTime::currentLocalDateTime() 01232 { 01233 #ifndef NDEBUG 01234 if (KSystemTimeZones::isSimulated()) 01235 return currentUtcDateTime().toZone(KSystemTimeZones::local()); 01236 #endif 01237 return KDateTime(QDateTime::currentDateTime(), Spec(KSystemTimeZones::local())); 01238 } 01239 01240 KDateTime KDateTime::currentUtcDateTime() 01241 { 01242 KDateTime result; 01243 #ifdef Q_OS_WIN 01244 SYSTEMTIME st; 01245 memset(&st, 0, sizeof(SYSTEMTIME)); 01246 GetSystemTime(&st); 01247 result = KDateTime(QDate(st.wYear, st.wMonth, st.wDay), 01248 QTime(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds), 01249 UTC); 01250 #else 01251 time_t t; 01252 ::time(&t); 01253 result.setTime_t(static_cast<qint64>(t)); 01254 #endif 01255 #ifndef NDEBUG 01256 return result.addSecs(KDateTimePrivate::currentDateTimeOffset); 01257 #else 01258 return result; 01259 #endif 01260 } 01261 01262 KDateTime KDateTime::currentDateTime(const Spec &spec) 01263 { 01264 switch (spec.type()) 01265 { 01266 case UTC: 01267 return currentUtcDateTime(); 01268 case TimeZone: 01269 if (spec.timeZone() != KSystemTimeZones::local()) 01270 break; 01271 // fall through to LocalZone 01272 case LocalZone: 01273 return currentLocalDateTime(); 01274 default: 01275 break; 01276 } 01277 return currentUtcDateTime().toTimeSpec(spec); 01278 } 01279 01280 QDate KDateTime::currentLocalDate() 01281 { 01282 return currentLocalDateTime().date(); 01283 } 01284 01285 QTime KDateTime::currentLocalTime() 01286 { 01287 return currentLocalDateTime().time(); 01288 } 01289 01290 KDateTime::Comparison KDateTime::compare(const KDateTime &other) const 01291 { 01292 QDateTime start1, start2; 01293 const bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != other.d->secondOccurrence()); 01294 if (conv) 01295 { 01296 // Different time specs or one is a time which occurs twice, 01297 // so convert to UTC before comparing 01298 start1 = d->toUtc(); 01299 start2 = other.d->toUtc(); 01300 } 01301 else 01302 { 01303 // Same time specs, so no need to convert to UTC 01304 start1 = d->dt(); 01305 start2 = other.d->dt(); 01306 } 01307 if (d->dateOnly() || other.d->dateOnly()) 01308 { 01309 // At least one of the instances is date-only, so we need to compare 01310 // time periods rather than just times. 01311 QDateTime end1, end2; 01312 if (conv) 01313 { 01314 if (d->dateOnly()) 01315 { 01316 KDateTime kdt(*this); 01317 kdt.setTime(QTime(23,59,59,999)); 01318 end1 = kdt.d->toUtc(); 01319 } 01320 else 01321 end1 = start1; 01322 if (other.d->dateOnly()) 01323 { 01324 KDateTime kdt(other); 01325 kdt.setTime(QTime(23,59,59,999)); 01326 end2 = kdt.d->toUtc(); 01327 } 01328 else 01329 end2 = start2; 01330 } 01331 else 01332 { 01333 if (d->dateOnly()) 01334 end1 = QDateTime(d->date(), QTime(23,59,59,999), Qt::LocalTime); 01335 else 01336 end1 = d->dt(); 01337 if (other.d->dateOnly()) 01338 end2 = QDateTime(other.d->date(), QTime(23,59,59,999), Qt::LocalTime); 01339 else 01340 end2 = other.d->dt(); 01341 } 01342 if (start1 == start2) 01343 return !d->dateOnly() ? AtStart : (end1 == end2) ? Equal 01344 : (end1 < end2) ? static_cast<Comparison>(AtStart|Inside) 01345 : static_cast<Comparison>(AtStart|Inside|AtEnd|After); 01346 if (start1 < start2) 01347 return (end1 < start2) ? Before 01348 : (end1 == end2) ? static_cast<Comparison>(Before|AtStart|Inside|AtEnd) 01349 : (end1 == start2) ? static_cast<Comparison>(Before|AtStart) 01350 : (end1 < end2) ? static_cast<Comparison>(Before|AtStart|Inside) : Outside; 01351 else 01352 return (start1 > end2) ? After 01353 : (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast<Comparison>(AtEnd|After)) 01354 : (end1 == end2) ? static_cast<Comparison>(Inside|AtEnd) 01355 : (end1 < end2) ? Inside : static_cast<Comparison>(Inside|AtEnd|After); 01356 } 01357 return (start1 == start2) ? Equal : (start1 < start2) ? Before : After; 01358 } 01359 01360 bool KDateTime::operator==(const KDateTime &other) const 01361 { 01362 if (d == other.d) 01363 return true; // the two instances share the same data 01364 if (d->dateOnly() != other.d->dateOnly()) 01365 return false; 01366 if (d->equalSpec(*other.d)) 01367 { 01368 // Both instances are in the same time zone, so compare directly 01369 if (d->dateOnly()) 01370 return d->date() == other.d->date(); 01371 else 01372 return d->secondOccurrence() == other.d->secondOccurrence() 01373 && d->dt() == other.d->dt(); 01374 } 01375 // Don't waste time converting to UTC if the dates aren't close enough. 01376 if (qAbs(d->date().daysTo(other.d->date())) > 2) 01377 return false; 01378 if (d->dateOnly()) 01379 { 01380 // Date-only values are equal if both the start and end of day times are equal. 01381 if (d->toUtc() != other.d->toUtc()) 01382 return false; // start-of-day times differ 01383 KDateTime end1(*this); 01384 end1.setTime(QTime(23,59,59,999)); 01385 KDateTime end2(other); 01386 end2.setTime(QTime(23,59,59,999)); 01387 return end1.d->toUtc() == end2.d->toUtc(); 01388 } 01389 return d->toUtc() == other.d->toUtc(); 01390 } 01391 01392 bool KDateTime::operator<(const KDateTime &other) const 01393 { 01394 if (d == other.d) 01395 return false; // the two instances share the same data 01396 if (d->equalSpec(*other.d)) 01397 { 01398 // Both instances are in the same time zone, so compare directly 01399 if (d->dateOnly() || other.d->dateOnly()) 01400 return d->date() < other.d->date(); 01401 if (d->secondOccurrence() == other.d->secondOccurrence()) 01402 return d->dt() < other.d->dt(); 01403 // One is the second occurrence of a date/time, during a change from 01404 // daylight saving to standard time, so only do a direct comparison 01405 // if the dates are more than 1 day apart. 01406 const int dayDiff = d->date().daysTo(other.d->date()); 01407 if (dayDiff > 1) 01408 return true; 01409 if (dayDiff < -1) 01410 return false; 01411 } 01412 else 01413 { 01414 // Don't waste time converting to UTC if the dates aren't close enough. 01415 const int dayDiff = d->date().daysTo(other.d->date()); 01416 if (dayDiff > 2) 01417 return true; 01418 if (dayDiff < -2) 01419 return false; 01420 } 01421 if (d->dateOnly()) 01422 { 01423 // This instance is date-only, so we need to compare the end of its 01424 // day with the other value. Note that if the other value is date-only, 01425 // we want to compare with the start of its day, which will happen 01426 // automatically. 01427 KDateTime kdt(*this); 01428 kdt.setTime(QTime(23,59,59,999)); 01429 return kdt.d->toUtc() < other.d->toUtc(); 01430 } 01431 return d->toUtc() < other.d->toUtc(); 01432 } 01433 01434 QString KDateTime::toString(const QString &format) const 01435 { 01436 if (!isValid()) 01437 return QString(); 01438 enum { TZNone, UTCOffsetShort, UTCOffset, UTCOffsetColon, TZAbbrev, TZName }; 01439 KLocale *locale = KGlobal::locale(); 01440 KCalendarSystemQDate calendar(locale); 01441 QString result; 01442 QString s; 01443 int num, numLength, zone; 01444 bool escape = false; 01445 ushort flag = 0; 01446 for (int i = 0, end = format.length(); i < end; ++i) 01447 { 01448 zone = TZNone; 01449 num = NO_NUMBER; 01450 numLength = 0; // no leading zeroes 01451 ushort ch = format[i].unicode(); 01452 if (!escape) 01453 { 01454 if (ch == '%') 01455 escape = true; 01456 else 01457 result += format[i]; 01458 continue; 01459 } 01460 if (!flag) 01461 { 01462 switch (ch) 01463 { 01464 case '%': 01465 result += QLatin1Char('%'); 01466 break; 01467 case ':': 01468 flag = ch; 01469 break; 01470 case 'Y': // year 01471 num = d->date().year(); 01472 numLength = 4; 01473 break; 01474 case 'y': // year, 2 digits 01475 num = d->date().year() % 100; 01476 numLength = 2; 01477 break; 01478 case 'm': // month, 01 - 12 01479 numLength = 2; 01480 num = d->date().month(); 01481 break; 01482 case 'B': // month name, translated 01483 result += calendar.monthName(d->date().month(), 2000, KCalendarSystem::LongName); 01484 break; 01485 case 'b': // month name, translated, short 01486 result += calendar.monthName(d->date().month(), 2000, KCalendarSystem::ShortName); 01487 break; 01488 case 'd': // day of month, 01 - 31 01489 numLength = 2; 01490 // fall through to 'e' 01491 case 'e': // day of month, 1 - 31 01492 num = d->date().day(); 01493 break; 01494 case 'A': // week day name, translated 01495 result += calendar.weekDayName(d->date().dayOfWeek(), KCalendarSystem::LongDayName); 01496 break; 01497 case 'a': // week day name, translated, short 01498 result += calendar.weekDayName(d->date().dayOfWeek(), KCalendarSystem::ShortDayName); 01499 break; 01500 case 'H': // hour, 00 - 23 01501 numLength = 2; 01502 // fall through to 'k' 01503 case 'k': // hour, 0 - 23 01504 num = d->dt().time().hour(); 01505 break; 01506 case 'I': // hour, 01 - 12 01507 numLength = 2; 01508 // fall through to 'l' 01509 case 'l': // hour, 1 - 12 01510 num = (d->dt().time().hour() + 11) % 12 + 1; 01511 break; 01512 case 'M': // minutes, 00 - 59 01513 num = d->dt().time().minute(); 01514 numLength = 2; 01515 break; 01516 case 'S': // seconds, 00 - 59 01517 num = d->dt().time().second(); 01518 numLength = 2; 01519 break; 01520 case 'P': // am/pm 01521 { 01522 bool am = (d->dt().time().hour() < 12); 01523 QString ap = ki18n(am ? "am" : "pm").toString(locale); 01524 if (ap.isEmpty()) 01525 result += am ? QLatin1String("am") : QLatin1String("pm"); 01526 else 01527 result += ap; 01528 break; 01529 } 01530 case 'p': // AM/PM 01531 { 01532 bool am = (d->dt().time().hour() < 12); 01533 QString ap = ki18n(am ? "am" : "pm").toString(locale).toUpper(); 01534 if (ap.isEmpty()) 01535 result += am ? QLatin1String("AM") : QLatin1String("PM"); 01536 else 01537 result += ap; 01538 break; 01539 } 01540 case 'z': // UTC offset in hours and minutes 01541 zone = UTCOffset; 01542 break; 01543 case 'Z': // time zone abbreviation 01544 zone = TZAbbrev; 01545 break; 01546 default: 01547 result += QLatin1Char('%'); 01548 result += format[i]; 01549 break; 01550 } 01551 } 01552 else if (flag == ':') 01553 { 01554 // It's a "%:" sequence 01555 switch (ch) 01556 { 01557 case 'A': // week day name in English 01558 result += QLatin1String(longDay[d->date().dayOfWeek() - 1]); 01559 break; 01560 case 'a': // week day name in English, short 01561 result += QLatin1String(shortDay[d->date().dayOfWeek() - 1]); 01562 break; 01563 case 'B': // month name in English 01564 result += QLatin1String(longMonth[d->date().month() - 1]); 01565 break; 01566 case 'b': // month name in English, short 01567 result += QLatin1String(shortMonth[d->date().month() - 1]); 01568 break; 01569 case 'm': // month, 1 - 12 01570 num = d->date().month(); 01571 break; 01572 case 'P': // am/pm 01573 result += (d->dt().time().hour() < 12) ? QLatin1String("am") : QLatin1String("pm"); 01574 break; 01575 case 'p': // AM/PM 01576 result += (d->dt().time().hour() < 12) ? QLatin1String("AM") : QLatin1String("PM"); 01577 break; 01578 case 'S': // seconds with ':' prefix, only if non-zero 01579 { 01580 int sec = d->dt().time().second(); 01581 if (sec || d->dt().time().msec()) 01582 { 01583 result += QLatin1Char(':'); 01584 num = sec; 01585 numLength = 2; 01586 } 01587 break; 01588 } 01589 case 's': // milliseconds 01590 result += s.sprintf("%03d", d->dt().time().msec()); 01591 break; 01592 case 'u': // UTC offset in hours 01593 zone = UTCOffsetShort; 01594 break; 01595 case 'z': // UTC offset in hours and minutes, with colon 01596 zone = UTCOffsetColon; 01597 break; 01598 case 'Z': // time zone name 01599 zone = TZName; 01600 break; 01601 default: 01602 result += QLatin1String("%:"); 01603 result += format[i]; 01604 break; 01605 } 01606 flag = 0; 01607 } 01608 if (!flag) 01609 escape = false; 01610 01611 // Append any required number or time zone information 01612 if (num != NO_NUMBER) 01613 { 01614 if (!numLength) 01615 result += QString::number(num); 01616 else if (numLength == 2 || numLength == 4) 01617 { 01618 if (num < 0) 01619 { 01620 num = -num; 01621 result += QLatin1Char('-'); 01622 } 01623 result += s.sprintf((numLength == 2 ? "%02d" : "%04d"), num); 01624 } 01625 } 01626 else if (zone != TZNone) 01627 { 01628 KTimeZone tz; 01629 int offset; 01630 switch (d->specType) 01631 { 01632 case UTC: 01633 case TimeZone: 01634 tz = (d->specType == TimeZone) ? d->specZone : KTimeZone::utc(); 01635 // fall through to OffsetFromUTC 01636 case OffsetFromUTC: 01637 offset = (d->specType == TimeZone) ? d->timeZoneOffset() 01638 : (d->specType == OffsetFromUTC) ? d->specUtcOffset : 0; 01639 offset /= 60; 01640 switch (zone) 01641 { 01642 case UTCOffsetShort: // UTC offset in hours 01643 case UTCOffset: // UTC offset in hours and minutes 01644 case UTCOffsetColon: // UTC offset in hours and minutes, with colon 01645 { 01646 if (offset >= 0) 01647 result += QLatin1Char('+'); 01648 else 01649 { 01650 result += QLatin1Char('-'); 01651 offset = -offset; 01652 } 01653 QString s; 01654 result += s.sprintf(((zone == UTCOffsetColon) ? "%02d:" : "%02d"), offset/60); 01655 if (ch != 'u' || offset % 60) 01656 result += s.sprintf("%02d", offset % 60); 01657 break; 01658 } 01659 case TZAbbrev: // time zone abbreviation 01660 if (tz.isValid() && d->specType != OffsetFromUTC) 01661 result += QString::fromLatin1(tz.abbreviation(d->toUtc())); 01662 break; 01663 case TZName: // time zone name 01664 if (tz.isValid() && d->specType != OffsetFromUTC) 01665 result += tz.name(); 01666 break; 01667 } 01668 break; 01669 default: 01670 break; 01671 } 01672 } 01673 } 01674 return result; 01675 } 01676 01677 QString KDateTime::toString(TimeFormat format) const 01678 { 01679 QString result; 01680 if (!isValid()) 01681 return result; 01682 01683 QString s; 01684 char tzsign = '+'; 01685 int offset = 0; 01686 const char *tzcolon = ""; 01687 KTimeZone tz; 01688 switch (format) 01689 { 01690 case RFCDateDay: 01691 result += QString::fromLatin1(shortDay[d->date().dayOfWeek() - 1]); 01692 result += QLatin1String(", "); 01693 // fall through to RFCDate 01694 case RFCDate: 01695 { 01696 char seconds[8] = { 0 }; 01697 if (d->dt().time().second()) 01698 sprintf(seconds, ":%02d", d->dt().time().second()); 01699 result += s.sprintf("%02d %s ", d->date().day(), shortMonth[d->date().month() - 1]); 01700 int year = d->date().year(); 01701 if (year < 0) 01702 { 01703 result += QLatin1Char('-'); 01704 year = -year; 01705 } 01706 result += s.sprintf("%04d %02d:%02d%s ", 01707 year, d->dt().time().hour(), d->dt().time().minute(), seconds); 01708 if (d->specType == ClockTime) 01709 tz = KSystemTimeZones::local(); 01710 break; 01711 } 01712 case RFC3339Date: 01713 { 01714 QString s; 01715 result += s.sprintf("%04d-%02d-%02dT%02d:%02d:%02d", 01716 d->date().year(), d->date().month(), d->date().day(), 01717 d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second()); 01718 int msec = d->dt().time().msec(); 01719 if (msec) 01720 { 01721 int digits = 3; 01722 if (!(msec % 10)) 01723 msec /= 10, --digits; 01724 if (!(msec % 10)) 01725 msec /= 10, --digits; 01726 result += s.sprintf(".%0*d", digits, d->dt().time().msec()); 01727 } 01728 if (d->specType == UTC) 01729 return result + QLatin1Char('Z'); 01730 if (d->specType == ClockTime) 01731 tz = KSystemTimeZones::local(); 01732 tzcolon = ":"; // krazy:exclude=doublequote_chars 01733 break; 01734 } 01735 case ISODate: 01736 { 01737 // QDateTime::toString(Qt::ISODate) doesn't output fractions of a second 01738 int year = d->date().year(); 01739 if (year < 0) 01740 { 01741 result += QLatin1Char('-'); 01742 year = -year; 01743 } 01744 QString s; 01745 result += s.sprintf("%04d-%02d-%02d", 01746 year, d->date().month(), d->date().day()); 01747 if (!d->dateOnly() || d->specType != ClockTime) 01748 { 01749 result += s.sprintf("T%02d:%02d:%02d", 01750 d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second()); 01751 if (d->dt().time().msec()) 01752 { 01753 // Comma is preferred by ISO8601 as the decimal point symbol, 01754 // so use it unless '.' is the symbol used in this locale or we don't have a locale. 01755 KLocale *locale = KGlobal::locale(); 01756 result += (locale && locale->decimalSymbol() == QLatin1String(".")) ? QLatin1Char('.') : QLatin1Char(','); 01757 result += s.sprintf("%03d", d->dt().time().msec()); 01758 } 01759 } 01760 if (d->specType == UTC) 01761 return result + QLatin1Char('Z'); 01762 if (d->specType == ClockTime) 01763 return result; 01764 tzcolon = ":"; // krazy:exclude=doublequote_chars 01765 break; 01766 } 01767 case QtTextDate: 01768 case LocalDate: 01769 { 01770 Qt::DateFormat qtfmt = (format == QtTextDate) ? Qt::TextDate : Qt::LocalDate; 01771 if (d->dateOnly()) 01772 result = d->date().toString(qtfmt); 01773 else 01774 result = d->dt().toString(qtfmt); 01775 if (result.isEmpty() || d->specType == ClockTime) 01776 return result; 01777 result += QLatin1Char(' '); 01778 break; 01779 } 01780 default: 01781 return result; 01782 } 01783 01784 // Return the string with UTC offset ±hhmm appended 01785 if (d->specType == OffsetFromUTC || d->specType == TimeZone || tz.isValid()) 01786 { 01787 if (d->specType == TimeZone) 01788 offset = d->timeZoneOffset(); // calculate offset and cache UTC value 01789 else 01790 offset = tz.isValid() ? tz.offsetAtZoneTime(d->dt()) : d->specUtcOffset; 01791 if (offset < 0) 01792 { 01793 offset = -offset; 01794 tzsign = '-'; 01795 } 01796 } 01797 offset /= 60; 01798 return result + s.sprintf("%c%02d%s%02d", tzsign, offset/60, tzcolon, offset%60); 01799 } 01800 01801 KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *negZero) 01802 { 01803 if (negZero) 01804 *negZero = false; 01805 QString str = string.trimmed(); 01806 if (str.isEmpty()) 01807 return KDateTime(); 01808 01809 switch (format) 01810 { 01811 case RFCDateDay: // format is Wdy, DD Mon YYYY hh:mm:ss ±hhmm 01812 case RFCDate: // format is [Wdy,] DD Mon YYYY hh:mm[:ss] ±hhmm 01813 { 01814 int nyear = 6; // indexes within string to values 01815 int nmonth = 4; 01816 int nday = 2; 01817 int nwday = 1; 01818 int nhour = 7; 01819 int nmin = 8; 01820 int nsec = 9; 01821 // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" 01822 QRegExp rx(QString::fromLatin1("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$")); 01823 QStringList parts; 01824 if (!str.indexOf(rx)) 01825 { 01826 // Check that if date has '-' separators, both separators are '-'. 01827 parts = rx.capturedTexts(); 01828 bool h1 = (parts[3] == QLatin1String("-")); 01829 bool h2 = (parts[5] == QLatin1String("-")); 01830 if (h1 != h2) 01831 break; 01832 } 01833 else 01834 { 01835 // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY" 01836 rx = QRegExp(QString::fromLatin1("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$")); 01837 if (str.indexOf(rx)) 01838 break; 01839 nyear = 7; 01840 nmonth = 2; 01841 nday = 3; 01842 nwday = 1; 01843 nhour = 4; 01844 nmin = 5; 01845 nsec = 6; 01846 parts = rx.capturedTexts(); 01847 } 01848 bool ok[4]; 01849 int day = parts[nday].toInt(&ok[0]); 01850 int year = parts[nyear].toInt(&ok[1]); 01851 int hour = parts[nhour].toInt(&ok[2]); 01852 int minute = parts[nmin].toInt(&ok[3]); 01853 if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) 01854 break; 01855 int second = 0; 01856 if (!parts[nsec].isEmpty()) 01857 { 01858 second = parts[nsec].toInt(&ok[0]); 01859 if (!ok[0]) 01860 break; 01861 } 01862 bool leapSecond = (second == 60); 01863 if (leapSecond) 01864 second = 59; // apparently a leap second - validate below, once time zone is known 01865 int month = 0; 01866 for ( ; month < 12 && parts[nmonth] != QLatin1String(shortMonth[month]); ++month) ; 01867 int dayOfWeek = -1; 01868 if (!parts[nwday].isEmpty()) 01869 { 01870 // Look up the weekday name 01871 while (++dayOfWeek < 7 && QLatin1String(shortDay[dayOfWeek]) != parts[nwday]) ; 01872 if (dayOfWeek >= 7) 01873 for (dayOfWeek = 0; dayOfWeek < 7 && QLatin1String(longDay[dayOfWeek]) != parts[nwday]; ++dayOfWeek) ; 01874 } 01875 if (month >= 12 || dayOfWeek >= 7 01876 || (dayOfWeek < 0 && format == RFCDateDay)) 01877 break; 01878 int i = parts[nyear].size(); 01879 if (i < 4) 01880 { 01881 // It's an obsolete year specification with less than 4 digits 01882 year += (i == 2 && year < 50) ? 2000 : 1900; 01883 } 01884 01885 // Parse the UTC offset part 01886 int offset = 0; // set default to '-0000' 01887 bool negOffset = false; 01888 if (parts.count() > 10) 01889 { 01890 rx = QRegExp(QString::fromLatin1("^([+-])(\\d\\d)(\\d\\d)$")); 01891 if (!parts[10].indexOf(rx)) 01892 { 01893 // It's a UTC offset ±hhmm 01894 parts = rx.capturedTexts(); 01895 offset = parts[2].toInt(&ok[0]) * 3600; 01896 int offsetMin = parts[3].toInt(&ok[1]); 01897 if (!ok[0] || !ok[1] || offsetMin > 59) 01898 break; 01899 offset += offsetMin * 60; 01900 negOffset = (parts[1] == QLatin1String("-")); 01901 if (negOffset) 01902 offset = -offset; 01903 } 01904 else 01905 { 01906 // Check for an obsolete time zone name 01907 QByteArray zone = parts[10].toLatin1(); 01908 if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') 01909 negOffset = true; // military zone: RFC 2822 treats as '-0000' 01910 else if (zone != "UT" && zone != "GMT") // treated as '+0000' 01911 { 01912 offset = (zone == "EDT") ? -4*3600 01913 : (zone == "EST" || zone == "CDT") ? -5*3600 01914 : (zone == "CST" || zone == "MDT") ? -6*3600 01915 : (zone == "MST" || zone == "PDT") ? -7*3600 01916 : (zone == "PST") ? -8*3600 01917 : 0; 01918 if (!offset) 01919 { 01920 // Check for any other alphabetic time zone 01921 bool nonalpha = false; 01922 for (int i = 0, end = zone.size(); i < end && !nonalpha; ++i) 01923 nonalpha = !isalpha(zone[i]); 01924 if (nonalpha) 01925 break; 01926 // TODO: Attempt to recognize the time zone abbreviation? 01927 negOffset = true; // unknown time zone: RFC 2822 treats as '-0000' 01928 } 01929 } 01930 } 01931 } 01932 Status invalid = stValid; 01933 QDate qdate = checkDate(year, month+1, day, invalid); // convert date, and check for out-of-range 01934 if (!qdate.isValid()) 01935 break; 01936 KDateTime result(qdate, QTime(hour, minute, second), Spec(OffsetFromUTC, offset)); 01937 if (!result.isValid() 01938 || (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek+1)) 01939 break; // invalid date/time, or weekday doesn't correspond with date 01940 if (!offset) 01941 { 01942 if (negOffset && negZero) 01943 *negZero = true; // UTC offset given as "-0000" 01944 result.setTimeSpec(UTC); 01945 } 01946 if (leapSecond) 01947 { 01948 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. 01949 // Convert the time to UTC and check that it is 00:00:00. 01950 if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) 01951 break; // the time isn't the last second of the day 01952 } 01953 if (invalid) 01954 { 01955 KDateTime dt; // date out of range - return invalid KDateTime ... 01956 dt.d->status = invalid; // ... with reason for error 01957 return dt; 01958 } 01959 return result; 01960 } 01961 case RFC3339Date: // format is YYYY-MM-DDThh:mm:ss[.s]TZ 01962 { 01963 QRegExp rx(QString::fromLatin1("^(\\d{4})-(\\d\\d)-(\\d\\d)[Tt](\\d\\d):(\\d\\d):(\\d\\d)(?:\\.(\\d+))?([Zz]|([+-])(\\d\\d):(\\d\\d))$")); 01964 if (str.indexOf(rx)) 01965 break; 01966 const QStringList parts = rx.capturedTexts(); 01967 bool ok, ok1, ok2; 01968 int msecs = 0; 01969 bool leapSecond = false; 01970 int year = parts[1].toInt(&ok); 01971 int month = parts[2].toInt(&ok1); 01972 int day = parts[3].toInt(&ok2); 01973 if (!ok || !ok1 || !ok2) 01974 break; 01975 QDate d(year, month, day); 01976 if (!d.isValid()) 01977 break; 01978 int hour = parts[4].toInt(&ok); 01979 int minute = parts[5].toInt(&ok1); 01980 int second = parts[6].toInt(&ok2); 01981 if (!ok || !ok1 || !ok2) 01982 break; 01983 leapSecond = (second == 60); 01984 if (leapSecond) 01985 second = 59; // apparently a leap second - validate below, once time zone is known 01986 if (!parts[7].isEmpty()) 01987 { 01988 QString ms = parts[7] + QLatin1String("00"); 01989 ms.truncate(3); 01990 msecs = ms.toInt(&ok); 01991 if (!ok) 01992 break; 01993 if (msecs && leapSecond) 01994 break; // leap second only valid if 23:59:60.000 01995 } 01996 QTime t(hour, minute, second, msecs); 01997 if (!t.isValid()) 01998 break; 01999 int offset = 0; 02000 SpecType spec = (parts[8].toUpper() == QLatin1String("Z")) ? UTC : OffsetFromUTC; 02001 if (spec == OffsetFromUTC) 02002 { 02003 offset = parts[10].toInt(&ok) * 3600; 02004 offset += parts[11].toInt(&ok1) * 60; 02005 if (!ok || !ok1) 02006 break; 02007 if (parts[9] == QLatin1String("-")) 02008 { 02009 if (!offset && leapSecond) 02010 break; // leap second only valid if known time zone 02011 offset = -offset; 02012 if (!offset && negZero) 02013 *negZero = true; 02014 } 02015 } 02016 if (leapSecond) 02017 { 02018 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. 02019 // Convert the time to UTC and check that it is 00:00:00. 02020 if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) 02021 break; // the time isn't the last second of the day 02022 } 02023 return KDateTime(d, t, Spec(spec, offset)); 02024 } 02025 case ISODate: 02026 { 02027 /* 02028 * Extended format: [±]YYYY-MM-DD[Thh[:mm[:ss.s]][TZ]] 02029 * Basic format: [±]YYYYMMDD[Thh[mm[ss.s]][TZ]] 02030 * Extended format: [±]YYYY-DDD[Thh[:mm[:ss.s]][TZ]] 02031 * Basic format: [±]YYYYDDD[Thh[mm[ss.s]][TZ]] 02032 * In the first three formats, the year may be expanded to more than 4 digits. 02033 * 02034 * QDateTime::fromString(Qt::ISODate) is a rather limited implementation 02035 * of parsing ISO 8601 format date/time strings, so it isn't used here. 02036 * This implementation isn't complete either, but it's better. 02037 * 02038 * ISO 8601 allows truncation, but for a combined date & time, the date part cannot 02039 * be truncated from the right, and the time part cannot be truncated from the left. 02040 * In other words, only the outer parts of the string can be omitted. 02041 * The standard does not actually define how to interpret omitted parts - it is up 02042 * to those interchanging the data to agree on a scheme. 02043 */ 02044 bool dateOnly = false; 02045 // Check first for the extended format of ISO 8601 02046 QRegExp rx(QString::fromLatin1("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)[T ](\\d\\d)(?::(\\d\\d)(?::(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(?::(\\d\\d))?)?$")); 02047 if (str.indexOf(rx)) 02048 { 02049 // It's not the extended format - check for the basic format 02050 rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$")); 02051 if (str.indexOf(rx)) 02052 { 02053 rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$")); 02054 if (str.indexOf(rx)) 02055 { 02056 // Check for date-only formats 02057 dateOnly = true; 02058 rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)$")); 02059 if (str.indexOf(rx)) 02060 { 02061 // It's not the extended format - check for the basic format 02062 rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})$")); 02063 if (str.indexOf(rx)) 02064 { 02065 rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})$")); 02066 if (str.indexOf(rx)) 02067 break; 02068 } 02069 } 02070 } 02071 } 02072 } 02073 const QStringList parts = rx.capturedTexts(); 02074 bool ok, ok1; 02075 QDate d; 02076 int hour = 0; 02077 int minute = 0; 02078 int second = 0; 02079 int msecs = 0; 02080 bool leapSecond = false; 02081 int year = parts[2].toInt(&ok); 02082 if (!ok) 02083 break; 02084 if (parts[1] == QLatin1String("-")) 02085 year = -year; 02086 if (!dateOnly) 02087 { 02088 hour = parts[4].toInt(&ok); 02089 if (!ok) 02090 break; 02091 if (!parts[5].isEmpty()) 02092 { 02093 minute = parts[5].toInt(&ok); 02094 if (!ok) 02095 break; 02096 } 02097 if (!parts[6].isEmpty()) 02098 { 02099 second = parts[6].toInt(&ok); 02100 if (!ok) 02101 break; 02102 } 02103 leapSecond = (second == 60); 02104 if (leapSecond) 02105 second = 59; // apparently a leap second - validate below, once time zone is known 02106 if (!parts[7].isEmpty()) 02107 { 02108 QString ms = parts[7] + QLatin1String("00"); 02109 ms.truncate(3); 02110 msecs = ms.toInt(&ok); 02111 if (!ok) 02112 break; 02113 } 02114 } 02115 int month, day; 02116 Status invalid = stValid; 02117 if (parts[3].length() == 3) 02118 { 02119 // A day of the year is specified 02120 day = parts[3].toInt(&ok); 02121 if (!ok || day < 1 || day > 366) 02122 break; 02123 d = checkDate(year, 1, 1, invalid).addDays(day - 1); // convert date, and check for out-of-range 02124 if (!d.isValid() || (!invalid && d.year() != year)) 02125 break; 02126 day = d.day(); 02127 month = d.month(); 02128 } 02129 else 02130 { 02131 // A month and day are specified 02132 month = parts[3].left(2).toInt(&ok); 02133 day = parts[3].right(2).toInt(&ok1); 02134 if (!ok || !ok1) 02135 break; 02136 d = checkDate(year, month, day, invalid); // convert date, and check for out-of-range 02137 if (!d.isValid()) 02138 break; 02139 } 02140 if (dateOnly) 02141 { 02142 if (invalid) 02143 { 02144 KDateTime dt; // date out of range - return invalid KDateTime ... 02145 dt.d->status = invalid; // ... with reason for error 02146 return dt; 02147 } 02148 return KDateTime(d, Spec(ClockTime)); 02149 } 02150 if (hour == 24 && !minute && !second && !msecs) 02151 { 02152 // A time of 24:00:00 is allowed by ISO 8601, and means midnight at the end of the day 02153 d = d.addDays(1); 02154 hour = 0; 02155 } 02156 02157 QTime t(hour, minute, second, msecs); 02158 if (!t.isValid()) 02159 break; 02160 if (parts[8].isEmpty()) 02161 { 02162 // No UTC offset is specified. Don't try to validate leap seconds. 02163 if (invalid) 02164 { 02165 KDateTime dt; // date out of range - return invalid KDateTime ... 02166 dt.d->status = invalid; // ... with reason for error 02167 return dt; 02168 } 02169 return KDateTime(d, t, KDateTimePrivate::fromStringDefault()); 02170 } 02171 int offset = 0; 02172 SpecType spec = (parts[8] == QLatin1String("Z")) ? UTC : OffsetFromUTC; 02173 if (spec == OffsetFromUTC) 02174 { 02175 offset = parts[10].toInt(&ok) * 3600; 02176 if (!ok) 02177 break; 02178 if (!parts[11].isEmpty()) 02179 { 02180 offset += parts[11].toInt(&ok) * 60; 02181 if (!ok) 02182 break; 02183 } 02184 if (parts[9] == QLatin1String("-")) 02185 { 02186 offset = -offset; 02187 if (!offset && negZero) 02188 *negZero = true; 02189 } 02190 } 02191 if (leapSecond) 02192 { 02193 // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. 02194 // Convert the time to UTC and check that it is 00:00:00. 02195 if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) 02196 break; // the time isn't the last second of the day 02197 } 02198 if (invalid) 02199 { 02200 KDateTime dt; // date out of range - return invalid KDateTime ... 02201 dt.d->status = invalid; // ... with reason for error 02202 return dt; 02203 } 02204 return KDateTime(d, t, Spec(spec, offset)); 02205 } 02206 case QtTextDate: // format is Wdy Mth DD [hh:mm:ss] YYYY [±hhmm] 02207 { 02208 int offset = 0; 02209 QRegExp rx(QString::fromLatin1("^(\\S+\\s+\\S+\\s+\\d\\d\\s+(\\d\\d:\\d\\d:\\d\\d\\s+)?\\d\\d\\d\\d)\\s*(.*)$")); 02210 if (str.indexOf(rx) < 0) 02211 break; 02212 QStringList parts = rx.capturedTexts(); 02213 QDate qd; 02214 QDateTime qdt; 02215 bool dateOnly = parts[2].isEmpty(); 02216 if (dateOnly) 02217 { 02218 qd = QDate::fromString(parts[1], Qt::TextDate); 02219 if (!qd.isValid()) 02220 break; 02221 } 02222 else 02223 { 02224 qdt = QDateTime::fromString(parts[1], Qt::TextDate); 02225 if (!qdt.isValid()) 02226 break; 02227 } 02228 if (parts[3].isEmpty()) 02229 { 02230 // No time zone offset specified, so return a local clock time 02231 if (dateOnly) 02232 return KDateTime(qd, KDateTimePrivate::fromStringDefault()); 02233 else 02234 { 02235 // Do it this way to prevent UTC conversions changing the time 02236 return KDateTime(qdt.date(), qdt.time(), KDateTimePrivate::fromStringDefault()); 02237 } 02238 } 02239 rx = QRegExp(QString::fromLatin1("([+-])([\\d][\\d])(?::?([\\d][\\d]))?$")); 02240 if (parts[3].indexOf(rx) < 0) 02241 break; 02242 02243 // Extract the UTC offset at the end of the string 02244 bool ok; 02245 parts = rx.capturedTexts(); 02246 offset = parts[2].toInt(&ok) * 3600; 02247 if (!ok) 02248 break; 02249 if (parts.count() > 3) 02250 { 02251 offset += parts[3].toInt(&ok) * 60; 02252 if (!ok) 02253 break; 02254 } 02255 if (parts[1] == QLatin1String("-")) 02256 { 02257 offset = -offset; 02258 if (!offset && negZero) 02259 *negZero = true; 02260 } 02261 if (dateOnly) 02262 return KDateTime(qd, Spec((offset ? OffsetFromUTC : UTC), offset)); 02263 qdt.setTimeSpec(offset ? Qt::LocalTime : Qt::UTC); 02264 return KDateTime(qdt, Spec((offset ? OffsetFromUTC : UTC), offset)); 02265 } 02266 case LocalDate: 02267 default: 02268 break; 02269 } 02270 return KDateTime(); 02271 } 02272 02273 KDateTime KDateTime::fromString(const QString &string, const QString &format, 02274 const KTimeZones *zones, bool offsetIfAmbiguous) 02275 { 02276 int utcOffset = 0; // UTC offset in seconds 02277 bool dateOnly = false; 02278 Status invalid = stValid; 02279 QString zoneName; 02280 QByteArray zoneAbbrev; 02281 QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly, invalid); 02282 if (!qdt.isValid()) 02283 return KDateTime(); 02284 if (zones) 02285 { 02286 // Try to find a time zone match 02287 bool zname = false; 02288 KTimeZone zone; 02289 if (!zoneName.isEmpty()) 02290 { 02291 // A time zone name has been found. 02292 // Use the time zone with that name. 02293 zone = zones->zone(zoneName); 02294 zname = true; 02295 } 02296 else if (!invalid) 02297 { 02298 if (!zoneAbbrev.isEmpty()) 02299 { 02300 // A time zone abbreviation has been found. 02301 // Use the time zone which contains it, if any, provided that the 02302 // abbreviation applies at the specified date/time. 02303 bool useUtcOffset = false; 02304 const KTimeZones::ZoneMap z = zones->zones(); 02305 for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) 02306 { 02307 if (it.value().abbreviations().contains(zoneAbbrev)) 02308 { 02309 int offset2; 02310 int offset = it.value().offsetAtZoneTime(qdt, &offset2); 02311 QDateTime ut(qdt); 02312 ut.setTimeSpec(Qt::UTC); 02313 ut.addSecs(-offset); 02314 if (it.value().abbreviation(ut) != zoneAbbrev) 02315 { 02316 if (offset == offset2) 02317 continue; // abbreviation doesn't apply at specified time 02318 ut.addSecs(offset - offset2); 02319 if (it.value().abbreviation(ut) != zoneAbbrev) 02320 continue; // abbreviation doesn't apply at specified time 02321 offset = offset2; 02322 } 02323 // Found a time zone which uses this abbreviation at the specified date/time 02324 if (zone.isValid()) 02325 { 02326 // Abbreviation is used by more than one time zone 02327 if (!offsetIfAmbiguous || offset != utcOffset) 02328 return KDateTime(); 02329 useUtcOffset = true; 02330 } 02331 else 02332 { 02333 zone = it.value(); 02334 utcOffset = offset; 02335 } 02336 } 02337 } 02338 if (useUtcOffset) 02339 { 02340 zone = KTimeZone(); 02341 if (!utcOffset) 02342 qdt.setTimeSpec(Qt::UTC); 02343 } 02344 else 02345 zname = true; 02346 } 02347 else if (utcOffset || qdt.timeSpec() == Qt::UTC) 02348 { 02349 // A UTC offset has been found. 02350 // Use the time zone which contains it, if any. 02351 // For a date-only value, use the start of the day. 02352 QDateTime dtUTC = qdt; 02353 dtUTC.setTimeSpec(Qt::UTC); 02354 dtUTC.addSecs(-utcOffset); 02355 const KTimeZones::ZoneMap z = zones->zones(); 02356 for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) 02357 { 02358 QList<int> offsets = it.value().utcOffsets(); 02359 if ((offsets.isEmpty() || offsets.contains(utcOffset)) 02360 && it.value().offsetAtUtc(dtUTC) == utcOffset) 02361 { 02362 // Found a time zone which uses this offset at the specified time 02363 if (zone.isValid() || !utcOffset) 02364 { 02365 // UTC offset is used by more than one time zone 02366 if (!offsetIfAmbiguous) 02367 return KDateTime(); 02368 if (invalid) 02369 { 02370 KDateTime dt; // date out of range - return invalid KDateTime ... 02371 dt.d->status = invalid; // ... with reason for error 02372 return dt; 02373 } 02374 if (dateOnly) 02375 return KDateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset)); 02376 qdt.setTimeSpec(Qt::LocalTime); 02377 return KDateTime(qdt, Spec(OffsetFromUTC, utcOffset)); 02378 } 02379 zone = it.value(); 02380 } 02381 } 02382 } 02383 } 02384 if (!zone.isValid() && zname) 02385 return KDateTime(); // an unknown zone name or abbreviation was found 02386 if (zone.isValid() && !invalid) 02387 { 02388 if (dateOnly) 02389 return KDateTime(qdt.date(), Spec(zone)); 02390 return KDateTime(qdt, Spec(zone)); 02391 } 02392 } 02393 02394 // No time zone match was found 02395 if (invalid) 02396 { 02397 KDateTime dt; // date out of range - return invalid KDateTime ... 02398 dt.d->status = invalid; // ... with reason for error 02399 return dt; 02400 } 02401 KDateTime result; 02402 if (utcOffset) 02403 { 02404 qdt.setTimeSpec(Qt::LocalTime); 02405 result = KDateTime(qdt, Spec(OffsetFromUTC, utcOffset)); 02406 } 02407 else if (qdt.timeSpec() == Qt::UTC) 02408 result = KDateTime(qdt, UTC); 02409 else 02410 { 02411 result = KDateTime(qdt, Spec(ClockTime)); 02412 result.setTimeSpec(KDateTimePrivate::fromStringDefault()); 02413 } 02414 if (dateOnly) 02415 result.setDateOnly(true); 02416 return result; 02417 } 02418 02419 void KDateTime::setFromStringDefault(const Spec &spec) 02420 { 02421 KDateTimePrivate::fromStringDefault() = spec; 02422 } 02423 02424 void KDateTime::setSimulatedSystemTime(const KDateTime& newTime) 02425 { 02426 Q_UNUSED(newTime); 02427 #ifndef NDEBUG 02428 if (newTime.isValid()) 02429 { 02430 KDateTimePrivate::currentDateTimeOffset = realCurrentLocalDateTime().secsTo_long(newTime); 02431 KSystemTimeZones::setLocalZone(newTime.timeZone()); 02432 } 02433 else 02434 { 02435 KDateTimePrivate::currentDateTimeOffset = 0; 02436 KSystemTimeZones::setLocalZone(KTimeZone()); 02437 } 02438 #endif 02439 } 02440 02441 KDateTime KDateTime::realCurrentLocalDateTime() 02442 { 02443 #ifndef NDEBUG 02444 return KDateTime(QDateTime::currentDateTime(), KSystemTimeZones::realLocalZone()); 02445 #else 02446 return KDateTime(QDateTime::currentDateTime(), Spec(KSystemTimeZones::local())); 02447 #endif 02448 } 02449 02450 QDataStream & operator<<(QDataStream &s, const KDateTime &dt) 02451 { 02452 s << dt.date() << dt.time() << dt.timeSpec() << quint8(dt.isDateOnly() ? 0x01 : 0x00); 02453 return s; 02454 } 02455 02456 QDataStream & operator>>(QDataStream &s, KDateTime &kdt) 02457 { 02458 QDate d; 02459 QTime t; 02460 KDateTime::Spec spec; 02461 quint8 flags; 02462 s >> d >> t >> spec >> flags; 02463 if (flags & 0x01) 02464 kdt = KDateTime(d, spec); 02465 else 02466 kdt = KDateTime(d, t, spec); 02467 return s; 02468 } 02469 02470 02471 /* 02472 * Extracts a QDateTime from a string, given a format string. 02473 * The date/time is set to Qt::UTC if a zero UTC offset is found, 02474 * otherwise it is Qt::LocalTime. If Qt::LocalTime is returned and 02475 * utcOffset == 0, that indicates that no UTC offset was found. 02476 */ 02477 QDateTime fromStr(const QString& string, const QString& format, int& utcOffset, 02478 QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status &status) 02479 { 02480 status = stValid; 02481 QString str = string.simplified(); 02482 int year = NO_NUMBER; 02483 int month = NO_NUMBER; 02484 int day = NO_NUMBER; 02485 int dayOfWeek = NO_NUMBER; 02486 int hour = NO_NUMBER; 02487 int minute = NO_NUMBER; 02488 int second = NO_NUMBER; 02489 int millisec = NO_NUMBER; 02490 int ampm = NO_NUMBER; 02491 int tzoffset = NO_NUMBER; 02492 zoneName.clear(); 02493 zoneAbbrev.clear(); 02494 02495 enum { TZNone, UTCOffset, UTCOffsetColon, TZAbbrev, TZName }; 02496 KLocale *locale = KGlobal::locale(); 02497 KCalendarSystemQDate calendar(locale); 02498 int zone; 02499 int s = 0; 02500 int send = str.length(); 02501 bool escape = false; 02502 ushort flag = 0; 02503 for (int f = 0, fend = format.length(); f < fend && s < send; ++f) 02504 { 02505 zone = TZNone; 02506 ushort ch = format[f].unicode(); 02507 if (!escape) 02508 { 02509 if (ch == '%') 02510 escape = true; 02511 else if (format[f].isSpace()) 02512 { 02513 if (str[s].isSpace()) 02514 ++s; 02515 } 02516 else if (format[f] == str[s]) 02517 ++s; 02518 else 02519 return QDateTime(); 02520 continue; 02521 } 02522 if (!flag) 02523 { 02524 switch (ch) 02525 { 02526 case '%': 02527 if (str[s++] != QLatin1Char('%')) 02528 return QDateTime(); 02529 break; 02530 case ':': 02531 flag = ch; 02532 break; 02533 case 'Y': // full year, 4 digits 02534 if (!getNumber(str, s, 4, 4, NO_NUMBER, -1, year)) 02535 return QDateTime(); 02536 break; 02537 case 'y': // year, 2 digits 02538 if (!getNumber(str, s, 2, 2, 0, 99, year)) 02539 return QDateTime(); 02540 year += (year <= 50) ? 2000 : 1999; 02541 break; 02542 case 'm': // month, 2 digits, 01 - 12 02543 if (!getNumber(str, s, 2, 2, 1, 12, month)) 02544 return QDateTime(); 02545 break; 02546 case 'B': 02547 case 'b': // month name, translated or English 02548 { 02549 int m = matchMonth(str, s, &calendar); 02550 if (m <= 0 || (month != NO_NUMBER && month != m)) 02551 return QDateTime(); 02552 month = m; 02553 break; 02554 } 02555 case 'd': // day of month, 2 digits, 01 - 31 02556 if (!getNumber(str, s, 2, 2, 1, 31, day)) 02557 return QDateTime(); 02558 break; 02559 case 'e': // day of month, 1 - 31 02560 if (!getNumber(str, s, 1, 2, 1, 31, day)) 02561 return QDateTime(); 02562 break; 02563 case 'A': 02564 case 'a': // week day name, translated or English 02565 { 02566 int dow = matchDay(str, s, &calendar); 02567 if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) 02568 return QDateTime(); 02569 dayOfWeek = dow; 02570 break; 02571 } 02572 case 'H': // hour, 2 digits, 00 - 23 02573 if (!getNumber(str, s, 2, 2, 0, 23, hour)) 02574 return QDateTime(); 02575 break; 02576 case 'k': // hour, 0 - 23 02577 if (!getNumber(str, s, 1, 2, 0, 23, hour)) 02578 return QDateTime(); 02579 break; 02580 case 'I': // hour, 2 digits, 01 - 12 02581 if (!getNumber(str, s, 2, 2, 1, 12, hour)) 02582 return QDateTime(); 02583 break; 02584 case 'l': // hour, 1 - 12 02585 if (!getNumber(str, s, 1, 2, 1, 12, hour)) 02586 return QDateTime(); 02587 break; 02588 case 'M': // minutes, 2 digits, 00 - 59 02589 if (!getNumber(str, s, 2, 2, 0, 59, minute)) 02590 return QDateTime(); 02591 break; 02592 case 'S': // seconds, 2 digits, 00 - 59 02593 if (!getNumber(str, s, 2, 2, 0, 59, second)) 02594 return QDateTime(); 02595 break; 02596 case 's': // seconds, 0 - 59 02597 if (!getNumber(str, s, 1, 2, 0, 59, second)) 02598 return QDateTime(); 02599 break; 02600 case 'P': 02601 case 'p': // am/pm 02602 { 02603 int ap = getAmPm(str, s, locale); 02604 if (!ap || (ampm != NO_NUMBER && ampm != ap)) 02605 return QDateTime(); 02606 ampm = ap; 02607 break; 02608 } 02609 case 'z': // UTC offset in hours and optionally minutes 02610 zone = UTCOffset; 02611 break; 02612 case 'Z': // time zone abbreviation 02613 zone = TZAbbrev; 02614 break; 02615 case 't': // whitespace 02616 if (str[s++] != QLatin1Char(' ')) 02617 return QDateTime(); 02618 break; 02619 default: 02620 if (s + 2 > send 02621 || str[s++] != QLatin1Char('%') 02622 || str[s++] != format[f]) 02623 return QDateTime(); 02624 break; 02625 } 02626 } 02627 else if (flag == ':') 02628 { 02629 // It's a "%:" sequence 02630 switch (ch) 02631 { 02632 case 'Y': // full year, >= 4 digits 02633 if (!getNumber(str, s, 4, 100, NO_NUMBER, -1, year)) 02634 return QDateTime(); 02635 break; 02636 case 'A': 02637 case 'a': // week day name in English 02638 { 02639 int dow = matchDay(str, s, 0); 02640 if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) 02641 return QDateTime(); 02642 dayOfWeek = dow; 02643 break; 02644 } 02645 case 'B': 02646 case 'b': // month name in English 02647 { 02648 int m = matchMonth(str, s, 0); 02649 if (m <= 0 || (month != NO_NUMBER && month != m)) 02650 return QDateTime(); 02651 month = m; 02652 break; 02653 } 02654 case 'm': // month, 1 - 12 02655 if (!getNumber(str, s, 1, 2, 1, 12, month)) 02656 return QDateTime(); 02657 break; 02658 case 'P': 02659 case 'p': // am/pm in English 02660 { 02661 int ap = getAmPm(str, s, 0); 02662 if (!ap || (ampm != NO_NUMBER && ampm != ap)) 02663 return QDateTime(); 02664 ampm = ap; 02665 break; 02666 } 02667 case 'M': // minutes, 0 - 59 02668 if (!getNumber(str, s, 1, 2, 0, 59, minute)) 02669 return QDateTime(); 02670 break; 02671 case 'S': // seconds with ':' prefix, defaults to zero 02672 if (str[s] != QLatin1Char(':')) 02673 { 02674 second = 0; 02675 break; 02676 } 02677 ++s; 02678 if (!getNumber(str, s, 1, 2, 0, 59, second)) 02679 return QDateTime(); 02680 break; 02681 case 's': // milliseconds, with decimal point prefix 02682 { 02683 if (str[s] != QLatin1Char('.')) 02684 { 02685 // If no locale, try comma, it is preferred by ISO8601 as the decimal point symbol 02686 QString dpt = locale == 0 ? QString::fromLatin1(",") : locale->decimalSymbol(); 02687 if (!str.mid(s).startsWith(dpt)) 02688 return QDateTime(); 02689 s += dpt.length() - 1; 02690 } 02691 ++s; 02692 if (s >= send) 02693 return QDateTime(); 02694 QString val = str.mid(s); 02695 int i = 0; 02696 for (int end = val.length(); i < end && val[i].isDigit(); ++i) ; 02697 if (!i) 02698 return QDateTime(); 02699 val.truncate(i); 02700 val += QLatin1String("00"); 02701 val.truncate(3); 02702 int ms = val.toInt(); 02703 if (millisec != NO_NUMBER && millisec != ms) 02704 return QDateTime(); 02705 millisec = ms; 02706 s += i; 02707 break; 02708 } 02709 case 'u': // UTC offset in hours and optionally minutes 02710 zone = UTCOffset; 02711 break; 02712 case 'z': // UTC offset in hours and minutes, with colon 02713 zone = UTCOffsetColon; 02714 break; 02715 case 'Z': // time zone name 02716 zone = TZName; 02717 break; 02718 default: 02719 if (s + 3 > send 02720 || str[s++] != QLatin1Char('%') 02721 || str[s++] != QLatin1Char(':') 02722 || str[s++] != format[f]) 02723 return QDateTime(); 02724 break; 02725 } 02726 flag = 0; 02727 } 02728 if (!flag) 02729 escape = false; 02730 02731 if (zone != TZNone) 02732 { 02733 // Read time zone or UTC offset 02734 switch (zone) 02735 { 02736 case UTCOffset: 02737 case UTCOffsetColon: 02738 if (!zoneAbbrev.isEmpty() || !zoneName.isEmpty()) 02739 return QDateTime(); 02740 if (!getUTCOffset(str, s, (zone == UTCOffsetColon), tzoffset)) 02741 return QDateTime(); 02742 break; 02743 case TZAbbrev: // time zone abbreviation 02744 { 02745 if (tzoffset != NO_NUMBER || !zoneName.isEmpty()) 02746 return QDateTime(); 02747 int start = s; 02748 while (s < send && str[s].isLetterOrNumber()) 02749 ++s; 02750 if (s == start) 02751 return QDateTime(); 02752 QString z = str.mid(start, s - start); 02753 if (!zoneAbbrev.isEmpty() && z.toLatin1() != zoneAbbrev) 02754 return QDateTime(); 02755 zoneAbbrev = z.toLatin1(); 02756 break; 02757 } 02758 case TZName: // time zone name 02759 { 02760 if (tzoffset != NO_NUMBER || !zoneAbbrev.isEmpty()) 02761 return QDateTime(); 02762 QString z; 02763 if (f + 1 >= fend) 02764 { 02765 z = str.mid(s); 02766 s = send; 02767 } 02768 else 02769 { 02770 // Get the terminating character for the zone name 02771 QChar endchar = format[f + 1]; 02772 if (endchar == QLatin1Char('%') && f + 2 < fend) 02773 { 02774 QChar endchar2 = format[f + 2]; 02775 if (endchar2 == QLatin1Char('n') || endchar2 == QLatin1Char('t')) 02776 endchar = QLatin1Char(' '); 02777 } 02778 // Extract from the input string up to the terminating character 02779 int start = s; 02780 for ( ; s < send && str[s] != endchar; ++s) ; 02781 if (s == start) 02782 return QDateTime(); 02783 z = str.mid(start, s - start); 02784 } 02785 if (!zoneName.isEmpty() && z != zoneName) 02786 return QDateTime(); 02787 zoneName = z; 02788 break; 02789 } 02790 default: 02791 break; 02792 } 02793 } 02794 } 02795 02796 if (year == NO_NUMBER) 02797 year = KDateTime::currentLocalDate().year(); 02798 if (month == NO_NUMBER) 02799 month = 1; 02800 QDate d = checkDate(year, month, (day > 0 ? day : 1), status); // convert date, and check for out-of-range 02801 if (!d.isValid()) 02802 return QDateTime(); 02803 if (dayOfWeek != NO_NUMBER && !status) 02804 { 02805 if (day == NO_NUMBER) 02806 { 02807 day = 1 + dayOfWeek - QDate(year, month, 1).dayOfWeek(); 02808 if (day <= 0) 02809 day += 7; 02810 } 02811 else 02812 { 02813 if (QDate(year, month, day).dayOfWeek() != dayOfWeek) 02814 return QDateTime(); 02815 } 02816 } 02817 if (day == NO_NUMBER) 02818 day = 1; 02819 dateOnly = (hour == NO_NUMBER && minute == NO_NUMBER && second == NO_NUMBER && millisec == NO_NUMBER); 02820 if (hour == NO_NUMBER) 02821 hour = 0; 02822 if (minute == NO_NUMBER) 02823 minute = 0; 02824 if (second == NO_NUMBER) 02825 second = 0; 02826 if (millisec == NO_NUMBER) 02827 millisec = 0; 02828 if (ampm != NO_NUMBER) 02829 { 02830 if (!hour || hour > 12) 02831 return QDateTime(); 02832 if (ampm == 1 && hour == 12) 02833 hour = 0; 02834 else if (ampm == 2 && hour < 12) 02835 hour += 12; 02836 } 02837 02838 QDateTime dt(d, QTime(hour, minute, second, millisec), (tzoffset == 0 ? Qt::UTC : Qt::LocalTime)); 02839 02840 utcOffset = (tzoffset == NO_NUMBER) ? 0 : tzoffset*60; 02841 02842 return dt; 02843 } 02844 02845 02846 /* 02847 * Find which day name matches the specified part of a string. 02848 * 'offset' is incremented by the length of the match. 02849 * Reply = day number (1 - 7), or <= 0 if no match. 02850 */ 02851 int matchDay(const QString &string, int &offset, KCalendarSystem *calendar) 02852 { 02853 int dayOfWeek; 02854 QString part = string.mid(offset); 02855 if (part.isEmpty()) 02856 return -1; 02857 if (calendar) 02858 { 02859 // Check for localised day name first 02860 for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) 02861 { 02862 QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::LongDayName); 02863 if (part.startsWith(name, Qt::CaseInsensitive)) 02864 { 02865 offset += name.length(); 02866 return dayOfWeek; 02867 } 02868 } 02869 for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) 02870 { 02871 QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::ShortDayName); 02872 if (part.startsWith(name, Qt::CaseInsensitive)) 02873 { 02874 offset += name.length(); 02875 return dayOfWeek; 02876 } 02877 } 02878 } 02879 02880 // Check for English day name 02881 dayOfWeek = findString(part, longDay, 7, offset); 02882 if (dayOfWeek < 0) 02883 dayOfWeek = findString(part, shortDay, 7, offset); 02884 return dayOfWeek + 1; 02885 } 02886 02887 /* 02888 * Find which month name matches the specified part of a string. 02889 * 'offset' is incremented by the length of the match. 02890 * Reply = month number (1 - 12), or <= 0 if no match. 02891 */ 02892 int matchMonth(const QString &string, int &offset, KCalendarSystem *calendar) 02893 { 02894 int month; 02895 QString part = string.mid(offset); 02896 if (part.isEmpty()) 02897 return -1; 02898 if (calendar) 02899 { 02900 // Check for localised month name first 02901 for (month = 1; month <= 12; ++month) 02902 { 02903 QString name = calendar->monthName(month, 2000, KCalendarSystem::LongName); 02904 if (part.startsWith(name, Qt::CaseInsensitive)) 02905 { 02906 offset += name.length(); 02907 return month; 02908 } 02909 } 02910 for (month = 1; month <= 12; ++month) 02911 { 02912 QString name = calendar->monthName(month, 2000, KCalendarSystem::ShortName); 02913 if (part.startsWith(name, Qt::CaseInsensitive)) 02914 { 02915 offset += name.length(); 02916 return month; 02917 } 02918 } 02919 } 02920 // Check for English month name 02921 month = findString(part, longMonth, 12, offset); 02922 if (month < 0) 02923 month = findString(part, shortMonth, 12, offset); 02924 return month + 1; 02925 } 02926 02927 /* 02928 * Read a UTC offset from the input string. 02929 */ 02930 bool getUTCOffset(const QString &string, int &offset, bool colon, int &result) 02931 { 02932 int sign; 02933 int len = string.length(); 02934 if (offset >= len) 02935 return false; 02936 switch (string[offset++].unicode()) 02937 { 02938 case '+': 02939 sign = 1; 02940 break; 02941 case '-': 02942 sign = -1; 02943 break; 02944 default: 02945 return false; 02946 } 02947 int tzhour = NO_NUMBER; 02948 int tzmin = NO_NUMBER; 02949 if (!getNumber(string, offset, 2, 2, 0, 99, tzhour)) 02950 return false; 02951 if (colon) 02952 { 02953 if (offset >= len || string[offset++] != QLatin1Char(':')) 02954 return false; 02955 } 02956 if (offset >= len || !string[offset].isDigit()) 02957 tzmin = 0; 02958 else 02959 { 02960 if (!getNumber(string, offset, 2, 2, 0, 59, tzmin)) 02961 return false; 02962 } 02963 tzmin += tzhour * 60; 02964 if (result != NO_NUMBER && result != tzmin) 02965 return false; 02966 result = tzmin; 02967 return true; 02968 } 02969 02970 /* 02971 * Read an am/pm indicator from the input string. 02972 * 'offset' is incremented by the length of the match. 02973 * Reply = 1 (am), 2 (pm), or 0 if no match. 02974 */ 02975 int getAmPm(const QString &string, int &offset, KLocale *locale) 02976 { 02977 QString part = string.mid(offset); 02978 int ap = 0; 02979 int n = 2; 02980 if (locale) 02981 { 02982 // Check localised form first 02983 QString aps = ki18n("am").toString(locale); 02984 if (part.startsWith(aps, Qt::CaseInsensitive)) 02985 { 02986 ap = 1; 02987 n = aps.length(); 02988 } 02989 else 02990 { 02991 aps = ki18n("pm").toString(locale); 02992 if (part.startsWith(aps, Qt::CaseInsensitive)) 02993 { 02994 ap = 2; 02995 n = aps.length(); 02996 } 02997 } 02998 } 02999 if (!ap) 03000 { 03001 if (part.startsWith(QLatin1String("am"), Qt::CaseInsensitive)) 03002 ap = 1; 03003 else if (part.startsWith(QLatin1String("pm"), Qt::CaseInsensitive)) 03004 ap = 2; 03005 } 03006 if (ap) 03007 offset += n; 03008 return ap; 03009 } 03010 03011 /* Convert part of 'string' to a number. 03012 * If converted number differs from any current value in 'result', the function fails. 03013 * Reply = true if successful. 03014 */ 03015 bool getNumber(const QString& string, int& offset, int mindigits, int maxdigits, int minval, int maxval, int& result) 03016 { 03017 int end = string.size(); 03018 bool neg = false; 03019 if (minval == NO_NUMBER && offset < end && string[offset] == QLatin1Char('-')) 03020 { 03021 neg = true; 03022 ++offset; 03023 } 03024 if (offset + maxdigits > end) 03025 maxdigits = end - offset; 03026 int ndigits; 03027 for (ndigits = 0; ndigits < maxdigits && string[offset + ndigits].isDigit(); ++ndigits) ; 03028 if (ndigits < mindigits) 03029 return false; 03030 bool ok; 03031 int n = string.mid(offset, ndigits).toInt(&ok); 03032 if (neg) 03033 n = -n; 03034 if (!ok || (result != NO_NUMBER && n != result) || (minval != NO_NUMBER && n < minval) || (n > maxval && maxval >= 0)) 03035 return false; 03036 result = n; 03037 offset += ndigits; 03038 return true; 03039 } 03040 03041 int findString_internal(const QString &string, const char *array, int count, int &offset, int disp) 03042 { 03043 for (int i = 0; i < count; ++i) 03044 { 03045 if (string.startsWith(QLatin1String(array + i * disp), Qt::CaseInsensitive)) 03046 { 03047 offset += qstrlen(array + i * disp); 03048 return i; 03049 } 03050 } 03051 return -1; 03052 } 03053 03054 /* 03055 * Return the QDate for a given year, month and day. 03056 * If in error, check whether the reason is that the year is out of range. 03057 * If so, return a valid (but wrong) date but with 'status' set to the 03058 * appropriate error code. If no error, 'status' is set to stValid. 03059 */ 03060 QDate checkDate(int year, int month, int day, Status &status) 03061 { 03062 status = stValid; 03063 QDate qdate(year, month, day); 03064 if (qdate.isValid()) 03065 return qdate; 03066 03067 // Invalid date - check whether it's simply out of range 03068 if (year < MIN_YEAR) 03069 { 03070 bool leap = (year % 4 == 0) && (year % 100 || year % 400 == 0); 03071 qdate.setYMD((leap ? 2000 : 2001), month, day); 03072 if (qdate.isValid()) 03073 status = stTooEarly; 03074 } 03075 return qdate; 03076 } 03077
KDE 4.7 API Reference