KDELibs4Support

kdatetime.cpp
1 /*
2  This file is part of the KDE libraries
3  Copyright (c) 2005-2011 David Jarvie <[email protected]>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kdatetime.h"
22 
23 #include <config-date.h>
24 
25 #if HAVE_SYS_TIME_H
26 #include <sys/time.h>
27 #endif
28 #if HAVE_TIME_H
29 #include <time.h>
30 #endif
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <limits>
35 
36 #include <QDebug>
37 #include <QDateTime>
38 #include <QRegExp>
39 #include <QStringList>
40 #include <QSharedData>
41 #include <QSharedPointer>
42 #include <QDataStream>
43 
44 #include <klocale.h>
45 #include <klocalizedstring.h>
46 #include "kcalendarsystem.h"
47 #include <ksystemtimezone.h>
48 
49 #ifdef Q_OS_WIN
50 #include <windows.h> // SYSTEMTIME
51 #endif
52 
53 static const char shortDay[][4] = {
54  "Mon", "Tue", "Wed",
55  "Thu", "Fri", "Sat",
56  "Sun"
57 };
58 static const char longDay[][10] = {
59  "Monday", "Tuesday", "Wednesday",
60  "Thursday", "Friday", "Saturday",
61  "Sunday"
62 };
63 static const char shortMonth[][4] = {
64  "Jan", "Feb", "Mar", "Apr",
65  "May", "Jun", "Jul", "Aug",
66  "Sep", "Oct", "Nov", "Dec"
67 };
68 static const char longMonth[][10] = {
69  "January", "February", "March",
70  "April", "May", "June",
71  "July", "August", "September",
72  "October", "November", "December"
73 };
74 
75 static QDateTime fromStr(const QString &string, const QString &format, int &utcOffset,
76  QString &zoneName, QByteArray &zoneAbbrev, bool &dateOnly);
77 static int matchDay(const QString &string, int &offset, const KCalendarSystem *);
78 static int matchMonth(const QString &string, int &offset, const KCalendarSystem *);
79 static bool getUTCOffset(const QString &string, int &offset, bool colon, int &result);
80 static int getAmPm(const QString &string, int &offset, bool localized);
81 static bool getNumber(const QString &string, int &offset, int mindigits, int maxdigits, int minval, int maxval, int &result);
82 static int findString_internal(const QString &string, const char *ptr, int count, int &offset, int disp);
83 template<int disp> static inline
84 int findString(const QString &string, const char array[][disp], int count, int &offset)
85 {
86  return findString_internal(string, array[0], count, offset, disp);
87 }
88 
89 static const int NO_NUMBER = std::numeric_limits<int>::min(); // indicates that no number is present in string conversion functions
90 
91 #ifdef COMPILING_TESTS
92 KDELIBS4SUPPORT_EXPORT int KDateTime_utcCacheHit = 0;
93 KDELIBS4SUPPORT_EXPORT int KDateTime_zoneCacheHit = 0;
94 #endif
95 
96 /*----------------------------------------------------------------------------*/
97 
98 class KDateTimeSpecPrivate
99 {
100 public:
101  KDateTimeSpecPrivate() : utcOffset(0) {}
102  // *** NOTE: This structure is replicated in KDateTimePrivate. Any changes must be copied there.
103  KTimeZone tz; // if type == TimeZone, the instance's time zone.
104  int utcOffset; // if type == OffsetFromUTC, the offset from UTC
105  KDateTime::SpecType type; // time spec type
106 };
107 
109  : d(new KDateTimeSpecPrivate)
110 {
111  d->type = KDateTime::Invalid;
112 }
113 
115  : d(new KDateTimeSpecPrivate())
116 {
117  setType(tz);
118 }
119 
121  : d(new KDateTimeSpecPrivate())
122 {
123  setType(type, utcOffset);
124 }
125 
127  : d(new KDateTimeSpecPrivate())
128 {
129  operator=(spec);
130 }
131 
133 {
134  delete d;
135 }
136 
138 {
139  if (&spec != this) {
140  d->type = spec.d->type;
141  if (d->type == KDateTime::TimeZone) {
142  d->tz = spec.d->tz;
143  } else if (d->type == KDateTime::OffsetFromUTC) {
144  d->utcOffset = spec.d->utcOffset;
145  }
146  }
147  return *this;
148 }
149 
151 {
152  switch (type) {
154  d->utcOffset = utcOffset;
155  // fall through to UTC
156  case KDateTime::UTC:
158  d->type = type;
159  break;
161  d->tz = KSystemTimeZones::local();
162  d->type = KDateTime::TimeZone;
163  break;
164  case KDateTime::TimeZone:
165  default:
166  d->type = KDateTime::Invalid;
167  break;
168  }
169 }
170 
172 {
173  if (tz == KTimeZone::utc()) {
174  d->type = KDateTime::UTC;
175  } else if (tz.isValid() && tz.currentOffset() != KTimeZone::InvalidOffset) {
176  d->type = KDateTime::TimeZone;
177  d->tz = tz;
178  } else {
179  d->type = KDateTime::Invalid;
180  }
181 }
182 
184 {
185  if (d->type == KDateTime::TimeZone) {
186  return d->tz;
187  }
188  if (d->type == KDateTime::UTC) {
189  return KTimeZone::utc();
190  }
191  return KTimeZone();
192 }
193 
195 {
196  if (d->type == KDateTime::UTC
197  || (d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) {
198  return true;
199  }
200  return false;
201 }
202 
204 {
205  return Spec(KDateTime::UTC);
206 }
208 {
209  return Spec(KDateTime::ClockTime);
210 }
212 {
213  return Spec(KDateTime::LocalZone);
214 }
216 {
217  return Spec(KDateTime::OffsetFromUTC, utcOffset);
218 }
220 {
221  return d->type;
222 }
224 {
225  return d->type != KDateTime::Invalid;
226 }
228 {
229  return d->type == KDateTime::TimeZone && d->tz == KSystemTimeZones::local();
230 }
232 {
233  return d->type == KDateTime::ClockTime;
234 }
236 {
237  return d->type == KDateTime::OffsetFromUTC;
238 }
240 {
241  return d->type == KDateTime::OffsetFromUTC ? d->utcOffset : 0;
242 }
243 
244 bool KDateTime::Spec::operator==(const Spec &other) const
245 {
246  if (d->type != other.d->type
247  || (d->type == KDateTime::TimeZone && d->tz != other.d->tz)
248  || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) {
249  return false;
250  }
251  return true;
252 }
253 
254 bool KDateTime::Spec::equivalentTo(const Spec &other) const
255 {
256  if (d->type == other.d->type) {
257  if ((d->type == KDateTime::TimeZone && d->tz != other.d->tz)
258  || (d->type == KDateTime::OffsetFromUTC && d->utcOffset != other.d->utcOffset)) {
259  return false;
260  }
261  return true;
262  } else {
263  if ((d->type == KDateTime::UTC && other.d->type == KDateTime::OffsetFromUTC && other.d->utcOffset == 0)
264  || (other.d->type == KDateTime::UTC && d->type == KDateTime::OffsetFromUTC && d->utcOffset == 0)) {
265  return true;
266  }
267  return false;
268  }
269 }
270 
272 {
273  // The specification type is encoded in order to insulate from changes
274  // to the SpecType enum.
275  switch (spec.type()) {
276  case KDateTime::UTC:
277  s << static_cast<quint8>('u');
278  break;
280  s << static_cast<quint8>('o') << spec.utcOffset();
281  break;
282  case KDateTime::TimeZone:
283  s << static_cast<quint8>('z') << (spec.timeZone().isValid() ? spec.timeZone().name() : QString());
284  break;
286  s << static_cast<quint8>('c');
287  break;
288  case KDateTime::Invalid:
289  default:
290  s << static_cast<quint8>(' ');
291  break;
292  }
293  return s;
294 }
295 
297 {
298  // The specification type is encoded in order to insulate from changes
299  // to the SpecType enum.
300  quint8 t;
301  s >> t;
302  switch (static_cast<char>(t)) {
303  case 'u':
304  spec.setType(KDateTime::UTC);
305  break;
306  case 'o': {
307  int utcOffset;
308  s >> utcOffset;
309  spec.setType(KDateTime::OffsetFromUTC, utcOffset);
310  break;
311  }
312  case 'z': {
313  QString zone;
314  s >> zone;
316  spec.setType(tz);
317  break;
318  }
319  case 'c':
321  break;
322  default:
324  break;
325  }
326  return s;
327 }
328 
329 /*----------------------------------------------------------------------------*/
330 
331 Q_GLOBAL_STATIC_WITH_ARGS(KDateTime::Spec, s_fromStringDefault, (KDateTime::ClockTime))
332 
333 class KDateTimePrivate : public QSharedData
334 {
335 public:
336  KDateTimePrivate()
337  : QSharedData(),
338  specType(KDateTime::Invalid),
339  utcCached(true),
340  convertedCached(false),
341  m2ndOccurrence(false),
342  mDateOnly(false)
343  {
344  }
345 
346  KDateTimePrivate(const QDateTime &d, const KDateTime::Spec &s, bool donly = false)
347  : QSharedData(),
348  mDt(d),
349  specType(s.type()),
350  utcCached(false),
351  convertedCached(false),
352  m2ndOccurrence(false),
353  mDateOnly(donly)
354  {
355  switch (specType) {
356  case KDateTime::TimeZone:
357  specZone = s.timeZone();
358  break;
360  specUtcOffset = s.utcOffset();
361  break;
362  case KDateTime::Invalid:
363  utcCached = true;
364  // fall through to UTC
365  case KDateTime::UTC:
366  default:
367  break;
368  }
369  }
370 
371  KDateTimePrivate(const KDateTimePrivate &rhs)
372  : QSharedData(rhs),
373  mDt(rhs.mDt),
374  specZone(rhs.specZone),
375  specUtcOffset(rhs.specUtcOffset),
376  ut(rhs.ut),
377  converted(rhs.converted),
378  specType(rhs.specType),
379  utcCached(rhs.utcCached),
380  convertedCached(rhs.convertedCached),
381  m2ndOccurrence(rhs.m2ndOccurrence),
382  mDateOnly(rhs.mDateOnly),
383  converted2ndOccur(rhs.converted2ndOccur)
384  {}
385 
386  ~KDateTimePrivate() {}
387  const QDateTime &dt() const
388  {
389  return mDt;
390  }
391  const QDate date() const
392  {
393  return mDt.date();
394  }
395  KDateTime::Spec spec() const;
396  QDateTime utc() const
397  {
398  return QDateTime(ut.date, ut.time, Qt::UTC);
399  }
400  bool dateOnly() const
401  {
402  return mDateOnly;
403  }
404  bool secondOccurrence() const
405  {
406  return m2ndOccurrence;
407  }
408  void setDt(const QDateTime &dt)
409  {
410  mDt = dt;
411  utcCached = convertedCached = m2ndOccurrence = false;
412  }
413  void setDtFromUtc(const QDateTime &utcdt);
414  void setDate(const QDate &d)
415  {
416  mDt.setDate(d);
417  utcCached = convertedCached = m2ndOccurrence = false;
418  }
419  void setTime(const QTime &t)
420  {
421  mDt.setTime(t);
422  utcCached = convertedCached = mDateOnly = m2ndOccurrence = false;
423  }
424  void setDtTimeSpec(Qt::TimeSpec s)
425  {
426  mDt.setTimeSpec(s);
427  utcCached = convertedCached = m2ndOccurrence = false;
428  }
429  void setSpec(const KDateTime::Spec &);
430  void setDateOnly(bool d);
431  int timeZoneOffset() const;
432  QDateTime toUtc(const KTimeZone &local = KTimeZone()) const;
433  QDateTime toZone(const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const;
434  void newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local = KTimeZone()) const;
435  bool equalSpec(const KDateTimePrivate &) const;
436  void clearCache()
437  {
438  utcCached = convertedCached = false;
439  }
440  void setDt(const QDateTime &dt, const QDateTime &utcDt)
441  {
442  mDt = dt;
443  ut.date = utcDt.date();
444  ut.time = utcDt.time();
445  utcCached = true;
446  convertedCached = false;
447  m2ndOccurrence = false;
448  }
449  void setUtc(const QDateTime &dt) const
450  {
451  ut.date = dt.date();
452  ut.time = dt.time();
453  utcCached = true;
454  convertedCached = false;
455  }
456 
457  /* Initialise the date/time for specType = UTC, from a time zone time,
458  * and cache the time zone time.
459  */
460  void setUtcFromTz(const QDateTime &dt, const KTimeZone &tz)
461  {
462  if (specType == KDateTime::UTC) {
463  mDt = tz.toUtc(dt);
464  utcCached = false;
465  converted.date = dt.date();
466  converted.time = dt.time();
467  converted.tz = tz;
468  convertedCached = true;
469  converted2ndOccur = false; // KTimeZone::toUtc() returns the first occurrence
470  }
471  }
472 
473  // Default time spec used by fromString()
474  static KDateTime::Spec &fromStringDefault()
475  {
476  return *s_fromStringDefault();
477  }
478 
479  static QTime sod; // start of day (00:00:00)
480 #ifndef NDEBUG
481  static qint64 currentDateTimeOffset; // offset to apply to current system time
482 #endif
483 
484  /* Because some applications create thousands of instances of KDateTime, this
485  * data structure is designed to minimize memory usage. Ensure that all small
486  * members are kept together at the end!
487  */
488 private:
489  QDateTime mDt;
490 public:
491  KTimeZone specZone; // if specType == TimeZone, the instance's time zone
492  // if specType == ClockTime, the local time zone used to calculate the cached UTC time (mutable)
493  int specUtcOffset; // if specType == OffsetFromUTC, the offset from UTC
494  mutable struct ut { // cached UTC equivalent of 'mDt'. Saves space compared to storing QDateTime.
495  QDate date;
496  QTime time;
497  } ut;
498 private:
499  mutable struct converted { // cached conversion to another time zone (if 'tz' is valid)
500  QDate date;
501  QTime time;
502  KTimeZone tz;
503  } converted;
504 public:
505  KDateTime::SpecType specType : 4; // time spec type (N.B. need 3 bits + sign bit, since enums are signed on some platforms)
506  mutable bool utcCached : 1; // true if 'ut' is valid
507  mutable bool convertedCached : 1; // true if 'converted' is valid
508  mutable bool m2ndOccurrence : 1; // this is the second occurrence of a time zone time
509 private:
510  bool mDateOnly : 1; // true to ignore the time part
511  mutable bool converted2ndOccur : 1; // this is the second occurrence of 'converted' time
512 };
513 
514 QTime KDateTimePrivate::sod(0, 0, 0);
515 #ifndef NDEBUG
516 qint64 KDateTimePrivate::currentDateTimeOffset = 0;
517 #endif
518 
519 KDateTime::Spec KDateTimePrivate::spec() const
520 {
521  if (specType == KDateTime::TimeZone) {
522  return KDateTime::Spec(specZone);
523  } else {
524  return KDateTime::Spec(specType, specUtcOffset);
525  }
526 }
527 
528 void KDateTimePrivate::setSpec(const KDateTime::Spec &other)
529 {
530  if (specType == other.type()) {
531  switch (specType) {
532  case KDateTime::TimeZone: {
533  KTimeZone tz = other.timeZone();
534  if (specZone == tz) {
535  return;
536  }
537  specZone = tz;
538  break;
539  }
541  int offset = other.utcOffset();
542  if (specUtcOffset == offset) {
543  return;
544  }
545  specUtcOffset = offset;
546  break;
547  }
548  default:
549  return;
550  }
551  utcCached = false;
552  } else {
553  specType = other.type();
554  switch (specType) {
555  case KDateTime::TimeZone:
556  specZone = other.timeZone();
557  break;
559  specUtcOffset = other.utcOffset();
560  break;
561  case KDateTime::Invalid:
562  ut.date = QDate(); // cache an invalid UTC value
563  utcCached = true;
564  // fall through to UTC
565  case KDateTime::UTC:
566  default:
567  break;
568  }
569  }
570  convertedCached = false;
571  setDtTimeSpec((specType == KDateTime::UTC) ? Qt::UTC : Qt::LocalTime); // this clears cached UTC value
572 }
573 
574 bool KDateTimePrivate::equalSpec(const KDateTimePrivate &other) const
575 {
576  if (specType != other.specType
577  || (specType == KDateTime::TimeZone && specZone != other.specZone)
578  || (specType == KDateTime::OffsetFromUTC && specUtcOffset != other.specUtcOffset)) {
579  return false;
580  }
581  return true;
582 }
583 
584 void KDateTimePrivate::setDateOnly(bool dateOnly)
585 {
586  if (dateOnly != mDateOnly) {
587  mDateOnly = dateOnly;
588  if (dateOnly && mDt.time() != sod) {
589  mDt.setTime(sod);
590  utcCached = false;
591  convertedCached = false;
592  }
593  m2ndOccurrence = false;
594  }
595 }
596 
597 /* Sets the date/time to a given UTC date/time. The time spec is not changed. */
598 void KDateTimePrivate::setDtFromUtc(const QDateTime &utcdt)
599 {
600  switch (specType) {
601  case KDateTime::UTC:
602  setDt(utcdt);
603  break;
605  QDateTime local = utcdt.addSecs(specUtcOffset);
606  local.setTimeSpec(Qt::LocalTime);
607  setDt(local, utcdt);
608  break;
609  }
610  case KDateTime::TimeZone: {
611  bool second;
612  setDt(specZone.toZoneTime(utcdt, &second), utcdt);
613  m2ndOccurrence = second;
614  break;
615  }
617  specZone = KSystemTimeZones::local();
618  setDt(specZone.toZoneTime(utcdt), utcdt);
619  break;
620  default: // invalid
621  break;
622  }
623 }
624 
625 /*
626  * Returns the UTC offset for the date/time, provided that it is a time zone type.
627  */
628 int KDateTimePrivate::timeZoneOffset() const
629 {
630  if (specType != KDateTime::TimeZone) {
632  }
633  if (utcCached) {
634  QDateTime dt = mDt;
635  dt.setTimeSpec(Qt::UTC);
636  return utc().secsTo(dt);
637  }
638  int secondOffset;
639  if (!specZone.isValid()) {
641  }
642  int offset = specZone.offsetAtZoneTime(mDt, &secondOffset);
643  if (m2ndOccurrence) {
644  m2ndOccurrence = (secondOffset != offset); // cancel "second occurrence" flag if not applicable
645  offset = secondOffset;
646  }
647  if (offset == KTimeZone::InvalidOffset) {
648  ut.date = QDate();
649  utcCached = true;
650  convertedCached = false;
651  } else {
652  // Calculate the UTC time from the offset and cache it
653  QDateTime utcdt = mDt;
654  utcdt.setTimeSpec(Qt::UTC);
655  setUtc(utcdt.addSecs(-offset));
656  }
657  return offset;
658 }
659 
660 /*
661  * Returns the date/time converted to UTC.
662  * Depending on which KTimeZone class is involved, conversion to UTC may require
663  * significant calculation, so the calculated UTC value is cached.
664  */
665 QDateTime KDateTimePrivate::toUtc(const KTimeZone &local) const
666 {
667  KTimeZone loc(local);
668  if (utcCached) {
669  // Return cached UTC value
670  if (specType == KDateTime::ClockTime) {
671  // ClockTime uses the dynamic current local system time zone.
672  // Check for a time zone change before using the cached UTC value.
673  if (!local.isValid()) {
674  loc = KSystemTimeZones::local();
675  }
676  if (specZone == loc) {
677 // qDebug() << "toUtc(): cached -> " << utc() << endl,
678 #ifdef COMPILING_TESTS
679  ++KDateTime_utcCacheHit;
680 #endif
681  return utc();
682  }
683  } else {
684 // qDebug() << "toUtc(): cached -> " << utc() << endl,
685 #ifdef COMPILING_TESTS
686  ++KDateTime_utcCacheHit;
687 #endif
688  return utc();
689  }
690  }
691 
692  // No cached UTC value, so calculate it
693  switch (specType) {
694  case KDateTime::UTC:
695  return mDt;
697  if (!mDt.isValid()) {
698  break;
699  }
700  QDateTime dt = QDateTime(mDt.date(), mDt.time(), Qt::UTC).addSecs(-specUtcOffset);
701  setUtc(dt);
702 // qDebug() << "toUtc(): calculated -> " << dt << endl,
703  return dt;
704  }
705  case KDateTime::ClockTime: {
706  if (!mDt.isValid()) {
707  break;
708  }
709  if (!loc.isValid()) {
710  loc = KSystemTimeZones::local();
711  }
712  const_cast<KDateTimePrivate *>(this)->specZone = loc;
713  QDateTime dt(specZone.toUtc(mDt));
714  setUtc(dt);
715 // qDebug() << "toUtc(): calculated -> " << dt << endl,
716  return dt;
717  }
718  case KDateTime::TimeZone:
719  if (!mDt.isValid()) {
720  break;
721  }
722  timeZoneOffset(); // calculate offset and cache UTC value
723 // qDebug() << "toUtc(): calculated -> " << utc() << endl,
724  return utc();
725  default:
726  break;
727  }
728 
729  // Invalid - mark it cached to avoid having to process it again
730  ut.date = QDate(); // (invalid)
731  utcCached = true;
732  convertedCached = false;
733 // qDebug() << "toUtc(): invalid";
734  return mDt;
735 }
736 
737 /* Convert this value to another time zone.
738  * The value is cached to save having to repeatedly calculate it.
739  * The caller should check for an invalid date/time.
740  */
741 QDateTime KDateTimePrivate::toZone(const KTimeZone &zone, const KTimeZone &local) const
742 {
743  if (convertedCached && converted.tz == zone) {
744  // Converted value is already cached
745 #ifdef COMPILING_TESTS
746 // qDebug() << "KDateTimePrivate::toZone(" << zone->name() << "): " << mDt << " cached";
747  ++KDateTime_zoneCacheHit;
748 #endif
749  return QDateTime(converted.date, converted.time, Qt::LocalTime);
750  } else {
751  // Need to convert the value
752  bool second;
753  QDateTime result = zone.toZoneTime(toUtc(local), &second);
754  converted.date = result.date();
755  converted.time = result.time();
756  converted.tz = zone;
757  convertedCached = true;
758  converted2ndOccur = second;
759  return result;
760  }
761 }
762 
763 /* Convert this value to another time zone, and write it into the specified instance.
764  * The value is cached to save having to repeatedly calculate it.
765  * The caller should check for an invalid date/time.
766  */
767 void KDateTimePrivate::newToZone(KDateTimePrivate *newd, const KTimeZone &zone, const KTimeZone &local) const
768 {
769  newd->mDt = toZone(zone, local);
770  newd->specZone = zone;
771  newd->specType = KDateTime::TimeZone;
772  newd->utcCached = utcCached;
773  newd->mDateOnly = mDateOnly;
774  newd->m2ndOccurrence = converted2ndOccur;
775  switch (specType) {
776  case KDateTime::UTC:
777  newd->ut.date = mDt.date(); // cache the UTC value
778  newd->ut.time = mDt.time();
779  break;
780  case KDateTime::TimeZone:
781  // This instance is also type time zone, so cache its value in the new instance
782  newd->converted.date = mDt.date();
783  newd->converted.time = mDt.time();
784  newd->converted.tz = specZone;
785  newd->convertedCached = true;
786  newd->converted2ndOccur = m2ndOccurrence;
787  newd->ut = ut;
788  return;
789  default:
790  newd->ut = ut;
791  break;
792  }
793  newd->convertedCached = false;
794 }
795 
796 /*----------------------------------------------------------------------------*/
797 Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KDateTimePrivate>, emptyDateTimePrivate, (new KDateTimePrivate))
798 
800  : d(*emptyDateTimePrivate())
801 {
802 }
803 
804 KDateTime::KDateTime(const QDate &date, const Spec &spec)
805  : d(new KDateTimePrivate(QDateTime(date, KDateTimePrivate::sod, Qt::LocalTime), spec, true))
806 {
807  if (spec.type() == UTC) {
808  d->setDtTimeSpec(Qt::UTC);
809  }
810 }
811 
812 KDateTime::KDateTime(const QDate &date, const QTime &time, const Spec &spec)
813  : d(new KDateTimePrivate(QDateTime(date, time, Qt::LocalTime), spec))
814 {
815  if (spec.type() == UTC) {
816  d->setDtTimeSpec(Qt::UTC);
817  }
818 }
819 
820 KDateTime::KDateTime(const QDateTime &dt, const Spec &spec)
821  : d(new KDateTimePrivate(dt, spec))
822 {
823  // If the supplied date/time is UTC and we need local time, or vice versa, convert it.
824  if (spec.type() == UTC) {
825  if (dt.timeSpec() == Qt::LocalTime) {
826  d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time
827  }
828  } else if (dt.timeSpec() == Qt::UTC) {
829  d->setDtFromUtc(dt);
830  }
831 }
832 
834  : d(new KDateTimePrivate(dt, (dt.timeSpec() == Qt::LocalTime ? Spec(LocalZone) : Spec(UTC))))
835 {
836 }
837 
838 KDateTime::KDateTime(const KDateTime &other)
839  : d(other.d)
840 {
841 }
842 
843 KDateTime::~KDateTime()
844 {
845 }
846 
847 KDateTime &KDateTime::operator=(const KDateTime &other)
848 {
849  if (&other != this) {
850  d = other.d;
851  }
852  return *this;
853 }
854 
856 {
857  d.detach();
858 }
859 bool KDateTime::isNull() const
860 {
861  return d->dt().isNull();
862 }
863 
864 bool KDateTime::isValid() const
865 {
866  switch (d->specType)
867  {
868  case Invalid:
869  return false;
870  case ClockTime:
871  return d->dt().date().isValid() && d->dt().time().isValid();
872  default:
873  return d->dt().isValid();
874  }
875 }
876 
878 {
879  return d->dateOnly();
880 }
882 {
883  return d->specType == TimeZone && d->specZone == KSystemTimeZones::local();
884 }
886 {
887  return d->specType == ClockTime;
888 }
889 bool KDateTime::isUtc() const
890 {
891  return d->specType == UTC || (d->specType == OffsetFromUTC && d->specUtcOffset == 0);
892 }
894 {
895  return d->specType == OffsetFromUTC;
896 }
898 {
899  return d->specType == TimeZone && d->secondOccurrence();
900 }
902 {
903  return d->date();
904 }
906 {
907  return d->dt().time();
908 }
910 {
911  return d->dt();
912 }
913 
915 {
916  return d->spec();
917 }
919 {
920  return d->specType;
921 }
922 
924 {
925  switch (d->specType) {
926  case TimeZone:
927  return d->specZone;
928  case UTC:
929  return KTimeZone::utc();
930  default:
931  return KTimeZone();
932  }
933 }
934 
936 {
937  switch (d->specType) {
938  case TimeZone:
939  return d->timeZoneOffset(); // calculate offset and cache UTC value
940  case OffsetFromUTC:
941  return d->specUtcOffset;
942  default:
943  return 0;
944  }
945 }
946 
948 {
949  if (!isValid()) {
950  return KDateTime();
951  }
952  if (d->specType == UTC) {
953  return *this;
954  }
955  if (d->dateOnly()) {
956  return KDateTime(d->date(), Spec(UTC));
957  }
958  QDateTime udt = d->toUtc();
959  if (!udt.isValid()) {
960  return KDateTime();
961  }
962  return KDateTime(udt, UTC);
963 }
964 
966 {
967  if (!isValid()) {
968  return KDateTime();
969  }
970  int offset = 0;
971  switch (d->specType) {
972  case OffsetFromUTC:
973  return *this;
974  case UTC: {
975  if (d->dateOnly()) {
976  return KDateTime(d->date(), Spec(OffsetFromUTC, 0));
977  }
978  QDateTime qdt = d->dt();
979  qdt.setTimeSpec(Qt::LocalTime);
980  return KDateTime(qdt, Spec(OffsetFromUTC, 0));
981  }
982  case TimeZone:
983  offset = d->timeZoneOffset(); // calculate offset and cache UTC value
984  break;
985  case ClockTime:
986  offset = KSystemTimeZones::local().offsetAtZoneTime(d->dt());
987  break;
988  default:
989  return KDateTime();
990  }
991  if (d->dateOnly()) {
992  return KDateTime(d->date(), Spec(OffsetFromUTC, offset));
993  }
994  return KDateTime(d->dt(), Spec(OffsetFromUTC, offset));
995 }
996 
998 {
999  if (!isValid()) {
1000  return KDateTime();
1001  }
1002  if (d->specType == OffsetFromUTC && d->specUtcOffset == utcOffset) {
1003  return *this;
1004  }
1005  if (d->dateOnly()) {
1006  return KDateTime(d->date(), Spec(OffsetFromUTC, utcOffset));
1007  }
1008  return KDateTime(d->toUtc(), Spec(OffsetFromUTC, utcOffset));
1009 }
1010 
1012 {
1013  if (!isValid()) {
1014  return KDateTime();
1015  }
1017  if (d->specType == TimeZone && d->specZone == local) {
1018  return *this; // it's already local zone. Preserve UTC cache, if any
1019  }
1020  if (d->dateOnly()) {
1021  return KDateTime(d->date(), Spec(local));
1022  }
1023  switch (d->specType) {
1024  case TimeZone:
1025  case OffsetFromUTC:
1026  case UTC: {
1027  KDateTime result;
1028  d->newToZone(result.d, local, local); // cache the time zone conversion
1029  return result;
1030  }
1031  case ClockTime:
1032  return KDateTime(d->dt(), Spec(local));
1033  default:
1034  return KDateTime();
1035  }
1036 }
1037 
1039 {
1040  if (!isValid()) {
1041  return KDateTime();
1042  }
1043  if (d->specType == ClockTime) {
1044  return *this;
1045  }
1046  if (d->dateOnly()) {
1047  return KDateTime(d->date(), Spec(ClockTime));
1048  }
1049  KDateTime result = toLocalZone();
1050  result.d->specType = ClockTime; // cached value (if any) is unaffected
1051  return result;
1052 }
1053 
1055 {
1056  if (!zone.isValid() || !isValid()) {
1057  return KDateTime();
1058  }
1059  if (d->specType == TimeZone && d->specZone == zone) {
1060  return *this; // preserve UTC cache, if any
1061  }
1062  if (d->dateOnly()) {
1063  return KDateTime(d->date(), Spec(zone));
1064  }
1065  KDateTime result;
1066  d->newToZone(result.d, zone); // cache the time zone conversion
1067  return result;
1068 }
1069 
1071 {
1072  return toTimeSpec(dt.timeSpec());
1073 }
1074 
1076 {
1077  if (spec == d->spec()) {
1078  return *this;
1079  }
1080  if (!isValid()) {
1081  return KDateTime();
1082  }
1083  if (d->dateOnly()) {
1084  return KDateTime(d->date(), spec);
1085  }
1086  if (spec.type() == TimeZone) {
1087  KDateTime result;
1088  d->newToZone(result.d, spec.timeZone()); // cache the time zone conversion
1089  return result;
1090  }
1091  return KDateTime(d->toUtc(), spec);
1092 }
1093 
1095 {
1096  QDateTime qdt = d->toUtc();
1097  if (!qdt.isValid()) {
1098  return uint(-1);
1099  }
1100  return qdt.toTime_t();
1101 }
1102 
1103 void KDateTime::setTime_t(qint64 seconds)
1104 {
1105  d->setSpec(UTC);
1106  QDateTime dt;
1107  dt.setTimeSpec(Qt::UTC); // prevent QDateTime::setMSecsSinceEpoch() converting to local time
1108  dt.setMSecsSinceEpoch(seconds * 1000);
1109  d->setDt(dt);
1110 }
1111 
1112 void KDateTime::setDateOnly(bool dateOnly)
1113 {
1114  d->setDateOnly(dateOnly);
1115 }
1116 
1118 {
1119  d->setDate(date);
1120 }
1121 
1123 {
1124  d->setTime(time);
1125 }
1126 
1128 {
1129  d->clearCache();
1130  d->setDateOnly(false);
1131  if (dt.timeSpec() == Qt::LocalTime) {
1132  if (d->specType == UTC) {
1133  d->setUtcFromTz(dt, KSystemTimeZones::local()); // set time & cache local time
1134  } else {
1135  d->setDt(dt);
1136  }
1137  } else {
1138  d->setDtFromUtc(dt); // a UTC time has been supplied
1139  }
1140 }
1141 
1142 void KDateTime::setTimeSpec(const Spec &other)
1143 {
1144  d->setSpec(other);
1145 }
1146 
1148 {
1149  if (d->specType == KDateTime::TimeZone && second != d->m2ndOccurrence) {
1150  d->m2ndOccurrence = second;
1151  d->clearCache();
1152  if (second) {
1153  // Check whether a second occurrence is actually possible, and
1154  // if not, reset m2ndOccurrence.
1155  d->timeZoneOffset(); // check, and cache UTC value
1156  }
1157  }
1158 }
1159 
1160 KDateTime KDateTime::addMSecs(qint64 msecs) const
1161 {
1162  if (!msecs) {
1163  return *this; // retain cache - don't create another instance
1164  }
1165  if (!isValid()) {
1166  return KDateTime();
1167  }
1168  if (d->dateOnly()) {
1169  KDateTime result(*this);
1170  result.d->setDate(d->date().addDays(msecs / 86400000));
1171  return result;
1172  }
1173  if (d->specType == ClockTime) {
1174  QDateTime qdt = d->dt();
1175  qdt.setTimeSpec(Qt::UTC); // set time as UTC to avoid daylight savings adjustments in addSecs()
1176  qdt = qdt.addMSecs(msecs);
1178  return KDateTime(qdt, Spec(ClockTime));
1179  }
1180  return KDateTime(d->toUtc().addMSecs(msecs), d->spec());
1181 }
1182 
1183 KDateTime KDateTime::addSecs(qint64 secs) const
1184 {
1185  return addMSecs(secs * 1000);
1186 }
1187 
1188 KDateTime KDateTime::addDays(qint64 days) const
1189 {
1190  if (!days) {
1191  return *this; // retain cache - don't create another instance
1192  }
1193  KDateTime result(*this);
1194  result.d->setDate(d->date().addDays(days));
1195  return result;
1196 }
1197 
1199 {
1200  if (!months) {
1201  return *this; // retain cache - don't create another instance
1202  }
1203  KDateTime result(*this);
1204  result.d->setDate(d->date().addMonths(months));
1205  return result;
1206 }
1207 
1209 {
1210  if (!years) {
1211  return *this; // retain cache - don't create another instance
1212  }
1213  KDateTime result(*this);
1214  result.d->setDate(d->date().addYears(years));
1215  return result;
1216 }
1217 
1218 qint64 KDateTime::secsTo(const KDateTime &t2) const
1219 {
1220  if (!isValid() || !t2.isValid()) {
1221  return 0;
1222  }
1223  if (d->dateOnly()) {
1224  QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date();
1225  return d->date().daysTo(dat) * 86400;
1226  }
1227  if (t2.d->dateOnly()) {
1228  return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date()) * 86400;
1229  }
1230 
1231  QDateTime dt1, dt2;
1232  if (d->specType == ClockTime && t2.d->specType == ClockTime) {
1233  // Set both times as UTC to avoid daylight savings adjustments in secsTo()
1234  dt1 = d->dt();
1235  dt1.setTimeSpec(Qt::UTC);
1236  dt2 = t2.d->dt();
1237  dt2.setTimeSpec(Qt::UTC);
1238  } else {
1239  dt1 = d->toUtc();
1240  dt2 = t2.d->toUtc();
1241  }
1242  return dt1.secsTo(dt2);
1243 }
1244 
1245 qint64 KDateTime::daysTo(const KDateTime &t2) const
1246 {
1247  if (!isValid() || !t2.isValid()) {
1248  return 0;
1249  }
1250  if (d->dateOnly()) {
1251  QDate dat = t2.d->dateOnly() ? t2.d->date() : t2.toTimeSpec(d->spec()).d->date();
1252  return d->date().daysTo(dat);
1253  }
1254  if (t2.d->dateOnly()) {
1255  return toTimeSpec(t2.d->spec()).d->date().daysTo(t2.d->date());
1256  }
1257 
1258  QDate dat;
1259  switch (d->specType) {
1260  case UTC:
1261  dat = t2.d->toUtc().date();
1262  break;
1263  case OffsetFromUTC:
1264  dat = t2.d->toUtc().addSecs(d->specUtcOffset).date();
1265  break;
1266  case TimeZone:
1267  dat = t2.d->toZone(d->specZone).date(); // this caches the converted time in t2
1268  break;
1269  case ClockTime: {
1271  dat = t2.d->toZone(local, local).date(); // this caches the converted time in t2
1272  break;
1273  }
1274  default: // invalid
1275  return 0;
1276  }
1277  return d->date().daysTo(dat);
1278 }
1279 
1281 {
1282 #ifndef NDEBUG
1285  }
1286 #endif
1288 }
1289 
1291 {
1292  KDateTime result;
1293 #ifdef Q_OS_WIN
1294  SYSTEMTIME st;
1295  memset(&st, 0, sizeof(SYSTEMTIME));
1296  GetSystemTime(&st);
1297  result = KDateTime(QDate(st.wYear, st.wMonth, st.wDay),
1298  QTime(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds),
1299  UTC);
1300 #else
1301  time_t t;
1302  ::time(&t);
1303  result.setTime_t(static_cast<qint64>(t));
1304 #endif
1305 #ifndef NDEBUG
1306  return result.addSecs(KDateTimePrivate::currentDateTimeOffset);
1307 #else
1308  return result;
1309 #endif
1310 }
1311 
1313 {
1314  switch (spec.type()) {
1315  case UTC:
1316  return currentUtcDateTime();
1317  case TimeZone:
1318  if (spec.timeZone() != KSystemTimeZones::local()) {
1319  break;
1320  }
1321  // fall through to LocalZone
1322  case LocalZone:
1323  return currentLocalDateTime();
1324  default:
1325  break;
1326  }
1327  return currentUtcDateTime().toTimeSpec(spec);
1328 }
1329 
1331 {
1332  return currentLocalDateTime().date();
1333 }
1334 
1336 {
1337  return currentLocalDateTime().time();
1338 }
1339 
1341 {
1342  QDateTime start1, start2;
1343  const bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != other.d->secondOccurrence());
1344  if (conv) {
1345  // Different time specs or one is a time which occurs twice,
1346  // so convert to UTC before comparing
1347  start1 = d->toUtc();
1348  start2 = other.d->toUtc();
1349  } else {
1350  // Same time specs, so no need to convert to UTC
1351  start1 = d->dt();
1352  start2 = other.d->dt();
1353  }
1354  if (d->dateOnly() || other.d->dateOnly()) {
1355  // At least one of the instances is date-only, so we need to compare
1356  // time periods rather than just times.
1357  QDateTime end1, end2;
1358  if (conv) {
1359  if (d->dateOnly()) {
1360  KDateTime kdt(*this);
1361  kdt.setTime(QTime(23, 59, 59, 999));
1362  end1 = kdt.d->toUtc();
1363  } else {
1364  end1 = start1;
1365  }
1366  if (other.d->dateOnly()) {
1367  KDateTime kdt(other);
1368  kdt.setTime(QTime(23, 59, 59, 999));
1369  end2 = kdt.d->toUtc();
1370  } else {
1371  end2 = start2;
1372  }
1373  } else {
1374  if (d->dateOnly()) {
1375  end1 = QDateTime(d->date(), QTime(23, 59, 59, 999), Qt::LocalTime);
1376  } else {
1377  end1 = d->dt();
1378  }
1379  if (other.d->dateOnly()) {
1380  end2 = QDateTime(other.d->date(), QTime(23, 59, 59, 999), Qt::LocalTime);
1381  } else {
1382  end2 = other.d->dt();
1383  }
1384  }
1385  if (start1 == start2)
1386  return !d->dateOnly() ? AtStart : (end1 == end2) ? Equal
1387  : (end1 < end2) ? static_cast<Comparison>(AtStart | Inside)
1388  : static_cast<Comparison>(AtStart | Inside | AtEnd | After);
1389  if (start1 < start2)
1390  return (end1 < start2) ? Before
1391  : (end1 == end2) ? static_cast<Comparison>(Before | AtStart | Inside | AtEnd)
1392  : (end1 == start2) ? static_cast<Comparison>(Before | AtStart)
1393  : (end1 < end2) ? static_cast<Comparison>(Before | AtStart | Inside) : Outside;
1394  else
1395  return (start1 > end2) ? After
1396  : (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast<Comparison>(AtEnd | After))
1397  : (end1 == end2) ? static_cast<Comparison>(Inside | AtEnd)
1398  : (end1 < end2) ? Inside : static_cast<Comparison>(Inside | AtEnd | After);
1399  }
1400  return (start1 == start2) ? Equal : (start1 < start2) ? Before : After;
1401 }
1402 
1403 bool KDateTime::operator==(const KDateTime &other) const
1404 {
1405  if (d == other.d) {
1406  return true; // the two instances share the same data
1407  }
1408  if (d->dateOnly() != other.d->dateOnly()) {
1409  return false;
1410  }
1411  if (d->equalSpec(*other.d)) {
1412  // Both instances are in the same time zone, so compare directly
1413  if (d->dateOnly()) {
1414  return d->date() == other.d->date();
1415  } else
1416  return d->secondOccurrence() == other.d->secondOccurrence()
1417  && d->dt() == other.d->dt();
1418  }
1419  // Don't waste time converting to UTC if the dates aren't close enough.
1420  if (qAbs(d->date().daysTo(other.d->date())) > 2) {
1421  return false;
1422  }
1423  if (d->dateOnly()) {
1424  // Date-only values are equal if both the start and end of day times are equal.
1425  if (d->toUtc() != other.d->toUtc()) {
1426  return false; // start-of-day times differ
1427  }
1428  KDateTime end1(*this);
1429  end1.setTime(QTime(23, 59, 59, 999));
1430  KDateTime end2(other);
1431  end2.setTime(QTime(23, 59, 59, 999));
1432  return end1.d->toUtc() == end2.d->toUtc();
1433  }
1434  return d->toUtc() == other.d->toUtc();
1435 }
1436 
1437 bool KDateTime::operator<(const KDateTime &other) const
1438 {
1439  if (d == other.d) {
1440  return false; // the two instances share the same data
1441  }
1442  if (d->equalSpec(*other.d)) {
1443  // Both instances are in the same time zone, so compare directly
1444  if (d->dateOnly() || other.d->dateOnly()) {
1445  return d->date() < other.d->date();
1446  }
1447  if (d->secondOccurrence() == other.d->secondOccurrence()) {
1448  return d->dt() < other.d->dt();
1449  }
1450  // One is the second occurrence of a date/time, during a change from
1451  // daylight saving to standard time, so only do a direct comparison
1452  // if the dates are more than 1 day apart.
1453  const int dayDiff = d->date().daysTo(other.d->date());
1454  if (dayDiff > 1) {
1455  return true;
1456  }
1457  if (dayDiff < -1) {
1458  return false;
1459  }
1460  } else {
1461  // Don't waste time converting to UTC if the dates aren't close enough.
1462  const int dayDiff = d->date().daysTo(other.d->date());
1463  if (dayDiff > 2) {
1464  return true;
1465  }
1466  if (dayDiff < -2) {
1467  return false;
1468  }
1469  }
1470  if (d->dateOnly()) {
1471  // This instance is date-only, so we need to compare the end of its
1472  // day with the other value. Note that if the other value is date-only,
1473  // we want to compare with the start of its day, which will happen
1474  // automatically.
1475  KDateTime kdt(*this);
1476  kdt.setTime(QTime(23, 59, 59, 999));
1477  return kdt.d->toUtc() < other.d->toUtc();
1478  }
1479  return d->toUtc() < other.d->toUtc();
1480 }
1481 
1483 {
1484  if (!isValid()) {
1485  return QString();
1486  }
1487  enum { TZNone, UTCOffsetShort, UTCOffset, UTCOffsetColon, TZAbbrev, TZName };
1489  QString result;
1490  QString s;
1491  int num, numLength, zone;
1492  bool escape = false;
1493  ushort flag = 0;
1494  for (int i = 0, end = format.length(); i < end; ++i) {
1495  zone = TZNone;
1496  num = NO_NUMBER;
1497  numLength = 0; // no leading zeroes
1498  ushort ch = format[i].unicode();
1499  if (!escape) {
1500  if (ch == '%') {
1501  escape = true;
1502  } else {
1503  result += format[i];
1504  }
1505  continue;
1506  }
1507  if (!flag) {
1508  switch (ch) {
1509  case '%':
1510  result += QLatin1Char('%');
1511  break;
1512  case ':':
1513  flag = ch;
1514  break;
1515  case 'Y': // year
1516  num = d->date().year();
1517  numLength = 4;
1518  break;
1519  case 'y': // year, 2 digits
1520  num = d->date().year() % 100;
1521  numLength = 2;
1522  break;
1523  case 'm': // month, 01 - 12
1524  numLength = 2;
1525  num = d->date().month();
1526  break;
1527  case 'B': // month name, translated
1528  result += calendar->monthName(d->date().month(), 2000, KCalendarSystem::LongName);
1529  break;
1530  case 'b': // month name, translated, short
1531  result += calendar->monthName(d->date().month(), 2000, KCalendarSystem::ShortName);
1532  break;
1533  case 'd': // day of month, 01 - 31
1534  numLength = 2;
1535  // fall through to 'e'
1536  case 'e': // day of month, 1 - 31
1537  num = d->date().day();
1538  break;
1539  case 'A': // week day name, translated
1540  result += calendar->weekDayName(d->date().dayOfWeek(), KCalendarSystem::LongDayName);
1541  break;
1542  case 'a': // week day name, translated, short
1543  result += calendar->weekDayName(d->date().dayOfWeek(), KCalendarSystem::ShortDayName);
1544  break;
1545  case 'H': // hour, 00 - 23
1546  numLength = 2;
1547  // fall through to 'k'
1548  case 'k': // hour, 0 - 23
1549  num = d->dt().time().hour();
1550  break;
1551  case 'I': // hour, 01 - 12
1552  numLength = 2;
1553  // fall through to 'l'
1554  case 'l': // hour, 1 - 12
1555  num = (d->dt().time().hour() + 11) % 12 + 1;
1556  break;
1557  case 'M': // minutes, 00 - 59
1558  num = d->dt().time().minute();
1559  numLength = 2;
1560  break;
1561  case 'S': // seconds, 00 - 59
1562  num = d->dt().time().second();
1563  numLength = 2;
1564  break;
1565  case 'P': { // am/pm
1566  bool am = (d->dt().time().hour() < 12);
1567  QString ap = am ? i18n("am") : i18n("pm");
1568  if (ap.isEmpty()) {
1569  result += am ? QLatin1String("am") : QLatin1String("pm");
1570  } else {
1571  result += ap;
1572  }
1573  break;
1574  }
1575  case 'p': { // AM/PM
1576  bool am = (d->dt().time().hour() < 12);
1577  QString ap = (am ? i18n("am") : i18n("pm")).toUpper();
1578  if (ap.isEmpty()) {
1579  result += am ? QLatin1String("AM") : QLatin1String("PM");
1580  } else {
1581  result += ap;
1582  }
1583  break;
1584  }
1585  case 'z': // UTC offset in hours and minutes
1586  zone = UTCOffset;
1587  break;
1588  case 'Z': // time zone abbreviation
1589  zone = TZAbbrev;
1590  break;
1591  default:
1592  result += QLatin1Char('%');
1593  result += format[i];
1594  break;
1595  }
1596  } else if (flag == ':') {
1597  // It's a "%:" sequence
1598  switch (ch) {
1599  case 'A': // week day name in English
1600  result += QLatin1String(longDay[d->date().dayOfWeek() - 1]);
1601  break;
1602  case 'a': // week day name in English, short
1603  result += QLatin1String(shortDay[d->date().dayOfWeek() - 1]);
1604  break;
1605  case 'B': // month name in English
1606  result += QLatin1String(longMonth[d->date().month() - 1]);
1607  break;
1608  case 'b': // month name in English, short
1609  result += QLatin1String(shortMonth[d->date().month() - 1]);
1610  break;
1611  case 'm': // month, 1 - 12
1612  num = d->date().month();
1613  break;
1614  case 'P': // am/pm
1615  result += (d->dt().time().hour() < 12) ? QLatin1String("am") : QLatin1String("pm");
1616  break;
1617  case 'p': // AM/PM
1618  result += (d->dt().time().hour() < 12) ? QLatin1String("AM") : QLatin1String("PM");
1619  break;
1620  case 'S': { // seconds with ':' prefix, only if non-zero
1621  int sec = d->dt().time().second();
1622  if (sec || d->dt().time().msec()) {
1623  result += QLatin1Char(':');
1624  num = sec;
1625  numLength = 2;
1626  }
1627  break;
1628  }
1629  case 's': // milliseconds
1630  result += s.sprintf("%03d", d->dt().time().msec());
1631  break;
1632  case 'u': // UTC offset in hours
1633  zone = UTCOffsetShort;
1634  break;
1635  case 'z': // UTC offset in hours and minutes, with colon
1636  zone = UTCOffsetColon;
1637  break;
1638  case 'Z': // time zone name
1639  zone = TZName;
1640  break;
1641  default:
1642  result += QLatin1String("%:");
1643  result += format[i];
1644  break;
1645  }
1646  flag = 0;
1647  }
1648  if (!flag) {
1649  escape = false;
1650  }
1651 
1652  // Append any required number or time zone information
1653  if (num != NO_NUMBER) {
1654  if (!numLength) {
1655  result += QString::number(num);
1656  } else if (numLength == 2 || numLength == 4) {
1657  if (num < 0) {
1658  num = -num;
1659  result += QLatin1Char('-');
1660  }
1661  result += s.sprintf((numLength == 2 ? "%02d" : "%04d"), num);
1662  }
1663  } else if (zone != TZNone) {
1664  KTimeZone tz;
1665  int offset;
1666  switch (d->specType) {
1667  case UTC:
1668  case TimeZone:
1669  tz = (d->specType == TimeZone) ? d->specZone : KTimeZone::utc();
1670  // fall through to OffsetFromUTC
1671  case OffsetFromUTC:
1672  offset = (d->specType == TimeZone) ? d->timeZoneOffset()
1673  : (d->specType == OffsetFromUTC) ? d->specUtcOffset : 0;
1674  offset /= 60;
1675  switch (zone) {
1676  case UTCOffsetShort: // UTC offset in hours
1677  case UTCOffset: // UTC offset in hours and minutes
1678  case UTCOffsetColon: { // UTC offset in hours and minutes, with colon
1679  if (offset >= 0) {
1680  result += QLatin1Char('+');
1681  } else {
1682  result += QLatin1Char('-');
1683  offset = -offset;
1684  }
1685  QString s;
1686  result += s.sprintf(((zone == UTCOffsetColon) ? "%02d:" : "%02d"), offset / 60);
1687  if (ch != 'u' || offset % 60) {
1688  result += s.sprintf("%02d", offset % 60);
1689  }
1690  break;
1691  }
1692  case TZAbbrev: // time zone abbreviation
1693  if (tz.isValid() && d->specType != OffsetFromUTC) {
1694  result += QString::fromLatin1(tz.abbreviation(d->toUtc()));
1695  }
1696  break;
1697  case TZName: // time zone name
1698  if (tz.isValid() && d->specType != OffsetFromUTC) {
1699  result += tz.name();
1700  }
1701  break;
1702  }
1703  break;
1704  default:
1705  break;
1706  }
1707  }
1708  }
1709  return result;
1710 }
1711 
1713 {
1714  QString result;
1715  if (!d->dt().isValid()) {
1716  return result;
1717  }
1718 
1719  QString s;
1720  char tzsign = '+';
1721  int offset = 0;
1722  const char *tzcolon = "";
1723  KTimeZone tz;
1724  switch (format) {
1725  case RFCDateDay:
1726  result += QString::fromLatin1(shortDay[d->date().dayOfWeek() - 1]);
1727  result += QLatin1String(", ");
1728  // fall through to RFCDate
1729  case RFCDate: {
1730  char seconds[8] = { 0 };
1731  if (d->dt().time().second()) {
1732  sprintf(seconds, ":%02d", d->dt().time().second());
1733  }
1734  result += s.sprintf("%02d %s ", d->date().day(), shortMonth[d->date().month() - 1]);
1735  int year = d->date().year();
1736  if (year < 0) {
1737  result += QLatin1Char('-');
1738  year = -year;
1739  }
1740  result += s.sprintf("%04d %02d:%02d%s ",
1741  year, d->dt().time().hour(), d->dt().time().minute(), seconds);
1742  if (d->specType == ClockTime) {
1743  tz = KSystemTimeZones::local();
1744  }
1745  break;
1746  }
1747  case RFC3339Date: {
1748  QString s;
1749  result += s.sprintf("%04d-%02d-%02dT%02d:%02d:%02d",
1750  d->date().year(), d->date().month(), d->date().day(),
1751  d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second());
1752  int msec = d->dt().time().msec();
1753  if (msec) {
1754  int digits = 3;
1755  if (!(msec % 10)) {
1756  msec /= 10, --digits;
1757  }
1758  if (!(msec % 10)) {
1759  msec /= 10, --digits;
1760  }
1761  result += s.sprintf(".%0*d", digits, d->dt().time().msec());
1762  }
1763  if (d->specType == UTC) {
1764  return result + QLatin1Char('Z');
1765  }
1766  if (d->specType == ClockTime) {
1767  tz = KSystemTimeZones::local();
1768  }
1769  tzcolon = ":"; // krazy:exclude=doublequote_chars
1770  break;
1771  }
1772  case ISODate: {
1773  // QDateTime::toString(Qt::ISODate) doesn't output fractions of a second
1774  int year = d->date().year();
1775  if (year < 0) {
1776  result += QLatin1Char('-');
1777  year = -year;
1778  }
1779  QString s;
1780  result += s.sprintf("%04d-%02d-%02d",
1781  year, d->date().month(), d->date().day());
1782  if (!d->dateOnly() || d->specType != ClockTime) {
1783  result += s.sprintf("T%02d:%02d:%02d",
1784  d->dt().time().hour(), d->dt().time().minute(), d->dt().time().second());
1785  if (d->dt().time().msec()) {
1786  // Comma is preferred by ISO8601 as the decimal point symbol,
1787  // so use it unless '.' is the symbol used in this locale.
1788  result += (KLocale::global()->decimalSymbol() == QLatin1String(".")) ? QLatin1Char('.') : QLatin1Char(',');
1789  result += s.sprintf("%03d", d->dt().time().msec());
1790  }
1791  }
1792  if (d->specType == UTC) {
1793  return result + QLatin1Char('Z');
1794  }
1795  if (d->specType == ClockTime) {
1796  return result;
1797  }
1798  tzcolon = ":"; // krazy:exclude=doublequote_chars
1799  break;
1800  }
1801  case QtTextDate:
1802  case LocalDate: {
1803  Qt::DateFormat qtfmt = (format == QtTextDate) ? Qt::TextDate : Qt::LocalDate;
1804  if (d->dateOnly()) {
1805  result = d->date().toString(qtfmt);
1806  } else {
1807  result = d->dt().toString(qtfmt);
1808  }
1809  if (result.isEmpty() || d->specType == ClockTime) {
1810  return result;
1811  }
1812  result += QLatin1Char(' ');
1813  break;
1814  }
1815  default:
1816  return result;
1817  }
1818 
1819  // Return the string with UTC offset ±hhmm appended
1820  if (d->specType == OffsetFromUTC) {
1821  offset = d->specUtcOffset;
1822  } else if (d->specType == TimeZone) {
1823  offset = d->timeZoneOffset(); // calculate offset and cache UTC value
1824  } else if (tz.isValid()) {
1825  offset = tz.offsetAtZoneTime(d->dt());
1826  }
1827  if (d->specType == Invalid || offset == KTimeZone::InvalidOffset) {
1828  return result + QLatin1String("+EINVAL");
1829  }
1830  if (offset < 0) {
1831  offset = -offset;
1832  tzsign = '-';
1833  }
1834  offset /= 60;
1835  return result + s.sprintf("%c%02d%s%02d", tzsign, offset / 60, tzcolon, offset % 60);
1836 }
1837 
1838 KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *negZero)
1839 {
1840  if (negZero) {
1841  *negZero = false;
1842  }
1843  QString str = string.trimmed();
1844  if (str.isEmpty()) {
1845  return KDateTime();
1846  }
1847 
1848  switch (format) {
1849  case RFCDateDay: // format is Wdy, DD Mon YYYY hh:mm:ss ±hhmm
1850  case RFCDate: { // format is [Wdy,] DD Mon YYYY hh:mm[:ss] ±hhmm
1851  int nyear = 6; // indexes within string to values
1852  int nmonth = 4;
1853  int nday = 2;
1854  int nwday = 1;
1855  int nhour = 7;
1856  int nmin = 8;
1857  int nsec = 9;
1858  // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
1859  QRegExp rx(QString::fromLatin1("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"));
1860  QStringList parts;
1861  if (!str.indexOf(rx)) {
1862  // Check that if date has '-' separators, both separators are '-'.
1863  parts = rx.capturedTexts();
1864  bool h1 = (parts[3] == QLatin1String("-"));
1865  bool h2 = (parts[5] == QLatin1String("-"));
1866  if (h1 != h2) {
1867  break;
1868  }
1869  } else {
1870  // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
1871  rx = QRegExp(QString::fromLatin1("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"));
1872  if (str.indexOf(rx)) {
1873  break;
1874  }
1875  nyear = 7;
1876  nmonth = 2;
1877  nday = 3;
1878  nwday = 1;
1879  nhour = 4;
1880  nmin = 5;
1881  nsec = 6;
1882  parts = rx.capturedTexts();
1883  }
1884  bool ok[4];
1885  int day = parts[nday].toInt(&ok[0]);
1886  int year = parts[nyear].toInt(&ok[1]);
1887  int hour = parts[nhour].toInt(&ok[2]);
1888  int minute = parts[nmin].toInt(&ok[3]);
1889  if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) {
1890  break;
1891  }
1892  int second = 0;
1893  if (!parts[nsec].isEmpty()) {
1894  second = parts[nsec].toInt(&ok[0]);
1895  if (!ok[0]) {
1896  break;
1897  }
1898  }
1899  bool leapSecond = (second == 60);
1900  if (leapSecond) {
1901  second = 59; // apparently a leap second - validate below, once time zone is known
1902  }
1903  int month = 0;
1904  for (; month < 12 && parts[nmonth] != QLatin1String(shortMonth[month]); ++month);
1905  int dayOfWeek = -1;
1906  if (!parts[nwday].isEmpty()) {
1907  // Look up the weekday name
1908  while (++dayOfWeek < 7 && QLatin1String(shortDay[dayOfWeek]) != parts[nwday]);
1909  if (dayOfWeek >= 7)
1910  for (dayOfWeek = 0; dayOfWeek < 7 && QLatin1String(longDay[dayOfWeek]) != parts[nwday]; ++dayOfWeek);
1911  }
1912  if (month >= 12 || dayOfWeek >= 7
1913  || (dayOfWeek < 0 && format == RFCDateDay)) {
1914  break;
1915  }
1916  int i = parts[nyear].size();
1917  if (i < 4) {
1918  // It's an obsolete year specification with less than 4 digits
1919  year += (i == 2 && year < 50) ? 2000 : 1900;
1920  }
1921 
1922  // Parse the UTC offset part
1923  int offset = 0; // set default to '-0000'
1924  bool negOffset = false;
1925  if (parts.count() > 10) {
1926  rx = QRegExp(QString::fromLatin1("^([+-])(\\d\\d)(\\d\\d)$"));
1927  if (!parts[10].indexOf(rx)) {
1928  // It's a UTC offset ±hhmm
1929  parts = rx.capturedTexts();
1930  offset = parts[2].toInt(&ok[0]) * 3600;
1931  int offsetMin = parts[3].toInt(&ok[1]);
1932  if (!ok[0] || !ok[1] || offsetMin > 59) {
1933  break;
1934  }
1935  offset += offsetMin * 60;
1936  negOffset = (parts[1] == QLatin1String("-"));
1937  if (negOffset) {
1938  offset = -offset;
1939  }
1940  } else {
1941  // Check for an obsolete time zone name
1942  QByteArray zone = parts[10].toLatin1();
1943  if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') {
1944  negOffset = true; // military zone: RFC 2822 treats as '-0000'
1945  } else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
1946  offset = (zone == "EDT") ? -4 * 3600
1947  : (zone == "EST" || zone == "CDT") ? -5 * 3600
1948  : (zone == "CST" || zone == "MDT") ? -6 * 3600
1949  : (zone == "MST" || zone == "PDT") ? -7 * 3600
1950  : (zone == "PST") ? -8 * 3600
1951  : 0;
1952  if (!offset) {
1953  // Check for any other alphabetic time zone
1954  bool nonalpha = false;
1955  for (int i = 0, end = zone.size(); i < end && !nonalpha; ++i) {
1956  nonalpha = !isalpha(zone[i]);
1957  }
1958  if (nonalpha) {
1959  break;
1960  }
1961  // TODO: Attempt to recognize the time zone abbreviation?
1962  negOffset = true; // unknown time zone: RFC 2822 treats as '-0000'
1963  }
1964  }
1965  }
1966  }
1967  QDate qdate(year, month + 1, day);
1968  if (!qdate.isValid()) {
1969  break;
1970  }
1971  KDateTime result(qdate, QTime(hour, minute, second), Spec(OffsetFromUTC, offset));
1972  if (!result.isValid()
1973  || (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek + 1)) {
1974  break; // invalid date/time, or weekday doesn't correspond with date
1975  }
1976  if (!offset) {
1977  if (negOffset && negZero) {
1978  *negZero = true; // UTC offset given as "-0000"
1979  }
1980  result.setTimeSpec(UTC);
1981  }
1982  if (leapSecond) {
1983  // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
1984  // Convert the time to UTC and check that it is 00:00:00.
1985  if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400) { // (max abs(offset) is 100 hours)
1986  break; // the time isn't the last second of the day
1987  }
1988  }
1989  return result;
1990  }
1991  case RFC3339Date: { // format is YYYY-MM-DDThh:mm:ss[.s]TZ
1992  QRegExp rx(QString::fromLatin1("^(\\d{4})-(\\d\\d)-(\\d\\d)[Tt](\\d\\d):(\\d\\d):(\\d\\d)(?:\\.(\\d+))?([Zz]|([+-])(\\d\\d):(\\d\\d))$"));
1993  if (str.indexOf(rx)) {
1994  break;
1995  }
1996  const QStringList parts = rx.capturedTexts();
1997  bool ok, ok1, ok2;
1998  int msecs = 0;
1999  bool leapSecond = false;
2000  int year = parts[1].toInt(&ok);
2001  int month = parts[2].toInt(&ok1);
2002  int day = parts[3].toInt(&ok2);
2003  if (!ok || !ok1 || !ok2) {
2004  break;
2005  }
2006  QDate d(year, month, day);
2007  if (!d.isValid()) {
2008  break;
2009  }
2010  int hour = parts[4].toInt(&ok);
2011  int minute = parts[5].toInt(&ok1);
2012  int second = parts[6].toInt(&ok2);
2013  if (!ok || !ok1 || !ok2) {
2014  break;
2015  }
2016  leapSecond = (second == 60);
2017  if (leapSecond) {
2018  second = 59; // apparently a leap second - validate below, once time zone is known
2019  }
2020  if (!parts[7].isEmpty()) {
2021  QString ms = parts[7] + QLatin1String("00");
2022  ms.truncate(3);
2023  msecs = ms.toInt(&ok);
2024  if (!ok) {
2025  break;
2026  }
2027  if (msecs && leapSecond) {
2028  break; // leap second only valid if 23:59:60.000
2029  }
2030  }
2031  QTime t(hour, minute, second, msecs);
2032  if (!t.isValid()) {
2033  break;
2034  }
2035  int offset = 0;
2036  SpecType spec = (parts[8].toUpper() == QLatin1String("Z")) ? UTC : OffsetFromUTC;
2037  if (spec == OffsetFromUTC) {
2038  offset = parts[10].toInt(&ok) * 3600;
2039  offset += parts[11].toInt(&ok1) * 60;
2040  if (!ok || !ok1) {
2041  break;
2042  }
2043  if (parts[9] == QLatin1String("-")) {
2044  if (!offset && leapSecond) {
2045  break; // leap second only valid if known time zone
2046  }
2047  offset = -offset;
2048  if (!offset && negZero) {
2049  *negZero = true;
2050  }
2051  }
2052  }
2053  if (leapSecond) {
2054  // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
2055  // Convert the time to UTC and check that it is 00:00:00.
2056  if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400) { // (max abs(offset) is 100 hours)
2057  break; // the time isn't the last second of the day
2058  }
2059  }
2060  return KDateTime(d, t, Spec(spec, offset));
2061  }
2062  case ISODate: {
2063  /*
2064  * Extended format: [±]YYYY-MM-DD[Thh[:mm[:ss.s]][TZ]]
2065  * Basic format: [±]YYYYMMDD[Thh[mm[ss.s]][TZ]]
2066  * Extended format: [±]YYYY-DDD[Thh[:mm[:ss.s]][TZ]]
2067  * Basic format: [±]YYYYDDD[Thh[mm[ss.s]][TZ]]
2068  * In the first three formats, the year may be expanded to more than 4 digits.
2069  *
2070  * QDateTime::fromString(Qt::ISODate) is a rather limited implementation
2071  * of parsing ISO 8601 format date/time strings, so it isn't used here.
2072  * This implementation isn't complete either, but it's better.
2073  *
2074  * ISO 8601 allows truncation, but for a combined date & time, the date part cannot
2075  * be truncated from the right, and the time part cannot be truncated from the left.
2076  * In other words, only the outer parts of the string can be omitted.
2077  * The standard does not actually define how to interpret omitted parts - it is up
2078  * to those interchanging the data to agree on a scheme.
2079  */
2080  bool dateOnly = false;
2081  // Check first for the extended format of ISO 8601
2082  QRegExp rx(QString::fromLatin1("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)[T ](\\d\\d)(?::(\\d\\d)(?::(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(?::(\\d\\d))?)?$"));
2083  if (str.indexOf(rx)) {
2084  // It's not the extended format - check for the basic format
2085  rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$"));
2086  if (str.indexOf(rx)) {
2087  rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})[T ](\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(\\d\\d)?)?$"));
2088  if (str.indexOf(rx)) {
2089  // Check for date-only formats
2090  dateOnly = true;
2091  rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)$"));
2092  if (str.indexOf(rx)) {
2093  // It's not the extended format - check for the basic format
2094  rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4,})(\\d{4})$"));
2095  if (str.indexOf(rx)) {
2096  rx = QRegExp(QString::fromLatin1("^([+-])?(\\d{4})(\\d{3})$"));
2097  if (str.indexOf(rx)) {
2098  break;
2099  }
2100  }
2101  }
2102  }
2103  }
2104  }
2105  const QStringList parts = rx.capturedTexts();
2106  bool ok, ok1;
2107  QDate d;
2108  int hour = 0;
2109  int minute = 0;
2110  int second = 0;
2111  int msecs = 0;
2112  bool leapSecond = false;
2113  int year = parts[2].toInt(&ok);
2114  if (!ok) {
2115  break;
2116  }
2117  if (parts[1] == QLatin1String("-")) {
2118  year = -year;
2119  }
2120  if (!dateOnly) {
2121  hour = parts[4].toInt(&ok);
2122  if (!ok) {
2123  break;
2124  }
2125  if (!parts[5].isEmpty()) {
2126  minute = parts[5].toInt(&ok);
2127  if (!ok) {
2128  break;
2129  }
2130  }
2131  if (!parts[6].isEmpty()) {
2132  second = parts[6].toInt(&ok);
2133  if (!ok) {
2134  break;
2135  }
2136  }
2137  leapSecond = (second == 60);
2138  if (leapSecond) {
2139  second = 59; // apparently a leap second - validate below, once time zone is known
2140  }
2141  if (!parts[7].isEmpty()) {
2142  QString ms = parts[7] + QLatin1String("00");
2143  ms.truncate(3);
2144  msecs = ms.toInt(&ok);
2145  if (!ok) {
2146  break;
2147  }
2148  }
2149  }
2150  int month, day;
2151  if (parts[3].length() == 3) {
2152  // A day of the year is specified
2153  day = parts[3].toInt(&ok);
2154  if (!ok || day < 1 || day > 366) {
2155  break;
2156  }
2157  d = QDate(year, 1, 1).addDays(day - 1);
2158  if (!d.isValid() || (d.year() != year)) {
2159  break;
2160  }
2161  day = d.day();
2162  month = d.month();
2163  } else {
2164  // A month and day are specified
2165  month = parts[3].left(2).toInt(&ok);
2166  day = parts[3].right(2).toInt(&ok1);
2167  if (!ok || !ok1) {
2168  break;
2169  }
2170  d = QDate(year, month, day);
2171  if (!d.isValid()) {
2172  break;
2173  }
2174  }
2175  if (dateOnly) {
2176  return KDateTime(d, Spec(ClockTime));
2177  }
2178  if (hour == 24 && !minute && !second && !msecs) {
2179  // A time of 24:00:00 is allowed by ISO 8601, and means midnight at the end of the day
2180  d = d.addDays(1);
2181  hour = 0;
2182  }
2183 
2184  QTime t(hour, minute, second, msecs);
2185  if (!t.isValid()) {
2186  break;
2187  }
2188  if (parts[8].isEmpty()) {
2189  // No UTC offset is specified. Don't try to validate leap seconds.
2190  return KDateTime(d, t, KDateTimePrivate::fromStringDefault());
2191  }
2192  int offset = 0;
2193  SpecType spec = (parts[8] == QLatin1String("Z")) ? UTC : OffsetFromUTC;
2194  if (spec == OffsetFromUTC) {
2195  offset = parts[10].toInt(&ok) * 3600;
2196  if (!ok) {
2197  break;
2198  }
2199  if (!parts[11].isEmpty()) {
2200  offset += parts[11].toInt(&ok) * 60;
2201  if (!ok) {
2202  break;
2203  }
2204  }
2205  if (parts[9] == QLatin1String("-")) {
2206  offset = -offset;
2207  if (!offset && negZero) {
2208  *negZero = true;
2209  }
2210  }
2211  }
2212  if (leapSecond) {
2213  // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
2214  // Convert the time to UTC and check that it is 00:00:00.
2215  if ((hour * 3600 + minute * 60 + 60 - offset + 86400 * 5) % 86400) { // (max abs(offset) is 100 hours)
2216  break; // the time isn't the last second of the day
2217  }
2218  }
2219  return KDateTime(d, t, Spec(spec, offset));
2220  }
2221  case QtTextDate: { // format is Wdy Mth DD [hh:mm:ss] YYYY [±hhmm]
2222  int offset = 0;
2223  QRegExp rx(QString::fromLatin1("^(\\S+\\s+\\S+\\s+\\d\\d\\s+(\\d\\d:\\d\\d:\\d\\d\\s+)?\\d\\d\\d\\d)\\s*(.*)$"));
2224  if (str.indexOf(rx) < 0) {
2225  break;
2226  }
2227  QStringList parts = rx.capturedTexts();
2228  QDate qd;
2229  QDateTime qdt;
2230  bool dateOnly = parts[2].isEmpty();
2231  if (dateOnly) {
2232  qd = QDate::fromString(parts[1], Qt::TextDate);
2233  if (!qd.isValid()) {
2234  break;
2235  }
2236  } else {
2237  qdt = QDateTime::fromString(parts[1], Qt::TextDate);
2238  if (!qdt.isValid()) {
2239  break;
2240  }
2241  }
2242  if (parts[3].isEmpty()) {
2243  // No time zone offset specified, so return a local clock time
2244  if (dateOnly) {
2245  return KDateTime(qd, KDateTimePrivate::fromStringDefault());
2246  } else {
2247  // Do it this way to prevent UTC conversions changing the time
2248  return KDateTime(qdt.date(), qdt.time(), KDateTimePrivate::fromStringDefault());
2249  }
2250  }
2251  rx = QRegExp(QString::fromLatin1("([+-])([\\d][\\d])(?::?([\\d][\\d]))?$"));
2252  if (parts[3].indexOf(rx) < 0) {
2253  break;
2254  }
2255 
2256  // Extract the UTC offset at the end of the string
2257  bool ok;
2258  parts = rx.capturedTexts();
2259  offset = parts[2].toInt(&ok) * 3600;
2260  if (!ok) {
2261  break;
2262  }
2263  if (parts.count() > 3) {
2264  offset += parts[3].toInt(&ok) * 60;
2265  if (!ok) {
2266  break;
2267  }
2268  }
2269  if (parts[1] == QLatin1String("-")) {
2270  offset = -offset;
2271  if (!offset && negZero) {
2272  *negZero = true;
2273  }
2274  }
2275  if (dateOnly) {
2276  return KDateTime(qd, Spec((offset ? OffsetFromUTC : UTC), offset));
2277  }
2278  qdt.setTimeSpec(offset ? Qt::LocalTime : Qt::UTC);
2279  return KDateTime(qdt, Spec((offset ? OffsetFromUTC : UTC), offset));
2280  }
2281  case LocalDate:
2282  default:
2283  break;
2284  }
2285  return KDateTime();
2286 }
2287 
2288 KDateTime KDateTime::fromString(const QString &string, const QString &format,
2289  const KTimeZones *zones, bool offsetIfAmbiguous)
2290 {
2291  int utcOffset = 0; // UTC offset in seconds
2292  bool dateOnly = false;
2293  QString zoneName;
2294  QByteArray zoneAbbrev;
2295  QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly);
2296  if (!qdt.isValid()) {
2297  return KDateTime();
2298  }
2299  if (zones) {
2300  // Try to find a time zone match
2301  bool zname = false;
2302  KTimeZone zone;
2303  if (!zoneName.isEmpty()) {
2304  // A time zone name has been found.
2305  // Use the time zone with that name.
2306  zone = zones->zone(zoneName);
2307  zname = true;
2308  } else if (!zoneAbbrev.isEmpty()) {
2309  // A time zone abbreviation has been found.
2310  // Use the time zone which contains it, if any, provided that the
2311  // abbreviation applies at the specified date/time.
2312  bool useUtcOffset = false;
2313  const KTimeZones::ZoneMap z = zones->zones();
2314  for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) {
2315  if (it.value().abbreviations().contains(zoneAbbrev)) {
2316  int offset2;
2317  int offset = it.value().offsetAtZoneTime(qdt, &offset2);
2318  QDateTime ut(qdt);
2319  ut.setTimeSpec(Qt::UTC);
2320  ut = ut.addSecs(-offset);
2321  if (it.value().abbreviation(ut) != zoneAbbrev) {
2322  if (offset == offset2) {
2323  continue; // abbreviation doesn't apply at specified time
2324  }
2325  ut = ut.addSecs(offset - offset2);
2326  if (it.value().abbreviation(ut) != zoneAbbrev) {
2327  continue; // abbreviation doesn't apply at specified time
2328  }
2329  offset = offset2;
2330  }
2331  // Found a time zone which uses this abbreviation at the specified date/time
2332  if (zone.isValid()) {
2333  // Abbreviation is used by more than one time zone
2334  if (!offsetIfAmbiguous || offset != utcOffset) {
2335  return KDateTime();
2336  }
2337  useUtcOffset = true;
2338  } else {
2339  zone = it.value();
2340  utcOffset = offset;
2341  }
2342  }
2343  }
2344  if (useUtcOffset) {
2345  zone = KTimeZone();
2346  if (!utcOffset) {
2347  qdt.setTimeSpec(Qt::UTC);
2348  }
2349  } else {
2350  zname = true;
2351  }
2352  } else if (utcOffset || qdt.timeSpec() == Qt::UTC) {
2353  // A UTC offset has been found.
2354  // Use the time zone which contains it, if any.
2355  // For a date-only value, use the start of the day.
2356  QDateTime dtUTC = qdt;
2357  dtUTC.setTimeSpec(Qt::UTC);
2358  dtUTC = dtUTC.addSecs(-utcOffset);
2359  const KTimeZones::ZoneMap z = zones->zones();
2360  for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it) {
2361  QList<int> offsets = it.value().utcOffsets();
2362  if ((offsets.isEmpty() || offsets.contains(utcOffset))
2363  && it.value().offsetAtUtc(dtUTC) == utcOffset) {
2364  // Found a time zone which uses this offset at the specified time
2365  if (zone.isValid() || !utcOffset) {
2366  // UTC offset is used by more than one time zone
2367  if (!offsetIfAmbiguous) {
2368  return KDateTime();
2369  }
2370  if (dateOnly) {
2371  return KDateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset));
2372  }
2374  return KDateTime(qdt, Spec(OffsetFromUTC, utcOffset));
2375  }
2376  zone = it.value();
2377  }
2378  }
2379  }
2380  if (!zone.isValid() && zname) {
2381  return KDateTime(); // an unknown zone name or abbreviation was found
2382  }
2383  if (zone.isValid()) {
2384  if (dateOnly) {
2385  return KDateTime(qdt.date(), Spec(zone));
2386  }
2387  return KDateTime(qdt, Spec(zone));
2388  }
2389  }
2390 
2391  // No time zone match was found
2392  KDateTime result;
2393  if (utcOffset) {
2395  result = KDateTime(qdt, Spec(OffsetFromUTC, utcOffset));
2396  } else if (qdt.timeSpec() == Qt::UTC) {
2397  result = KDateTime(qdt, UTC);
2398  } else {
2399  result = KDateTime(qdt, Spec(ClockTime));
2400  result.setTimeSpec(KDateTimePrivate::fromStringDefault());
2401  }
2402  if (dateOnly) {
2403  result.setDateOnly(true);
2404  }
2405  return result;
2406 }
2407 
2409 {
2410  KDateTimePrivate::fromStringDefault() = spec;
2411 }
2412 
2414 {
2415  Q_UNUSED(newTime);
2416 #ifndef NDEBUG
2417  if (newTime.isValid()) {
2418  KDateTimePrivate::currentDateTimeOffset = realCurrentLocalDateTime().secsTo_long(newTime);
2420  } else {
2421  KDateTimePrivate::currentDateTimeOffset = 0;
2423  }
2424 #endif
2425 }
2426 
2428 {
2429 #ifndef NDEBUG
2431 #else
2433 #endif
2434 }
2435 
2437 {
2438  s << dt.date() << dt.time() << dt.timeSpec() << quint8(dt.isDateOnly() ? 0x01 : 0x00);
2439  return s;
2440 }
2441 
2443 {
2444  QDate d;
2445  QTime t;
2446  KDateTime::Spec spec;
2447  quint8 flags;
2448  s >> d >> t >> spec >> flags;
2449  if (flags & 0x01) {
2450  kdt = KDateTime(d, spec);
2451  } else {
2452  kdt = KDateTime(d, t, spec);
2453  }
2454  return s;
2455 }
2456 
2457 /*
2458  * Extracts a QDateTime from a string, given a format string.
2459  * The date/time is set to Qt::UTC if a zero UTC offset is found,
2460  * otherwise it is Qt::LocalTime. If Qt::LocalTime is returned and
2461  * utcOffset == 0, that indicates that no UTC offset was found.
2462  */
2463 QDateTime fromStr(const QString &string, const QString &format, int &utcOffset,
2464  QString &zoneName, QByteArray &zoneAbbrev, bool &dateOnly)
2465 {
2466  QString str = string.simplified();
2467  int year = NO_NUMBER;
2468  int month = NO_NUMBER;
2469  int day = NO_NUMBER;
2470  int dayOfWeek = NO_NUMBER;
2471  int hour = NO_NUMBER;
2472  int minute = NO_NUMBER;
2473  int second = NO_NUMBER;
2474  int millisec = NO_NUMBER;
2475  int ampm = NO_NUMBER;
2476  int tzoffset = NO_NUMBER;
2477  zoneName.clear();
2478  zoneAbbrev.clear();
2479 
2480  enum { TZNone, UTCOffset, UTCOffsetColon, TZAbbrev, TZName };
2482  int zone;
2483  int s = 0;
2484  int send = str.length();
2485  bool escape = false;
2486  ushort flag = 0;
2487  for (int f = 0, fend = format.length(); f < fend && s < send; ++f) {
2488  zone = TZNone;
2489  ushort ch = format[f].unicode();
2490  if (!escape) {
2491  if (ch == '%') {
2492  escape = true;
2493  } else if (format[f].isSpace()) {
2494  if (str[s].isSpace()) {
2495  ++s;
2496  }
2497  } else if (format[f] == str[s]) {
2498  ++s;
2499  } else {
2500  return QDateTime();
2501  }
2502  continue;
2503  }
2504  if (!flag) {
2505  switch (ch) {
2506  case '%':
2507  if (str[s++] != QLatin1Char('%')) {
2508  return QDateTime();
2509  }
2510  break;
2511  case ':':
2512  flag = ch;
2513  break;
2514  case 'Y': // full year, 4 digits
2515  if (!getNumber(str, s, 4, 4, NO_NUMBER, -1, year)) {
2516  return QDateTime();
2517  }
2518  break;
2519  case 'y': // year, 2 digits
2520  if (!getNumber(str, s, 2, 2, 0, 99, year)) {
2521  return QDateTime();
2522  }
2523  year += (year <= 50) ? 2000 : 1999;
2524  break;
2525  case 'm': // month, 2 digits, 01 - 12
2526  if (!getNumber(str, s, 2, 2, 1, 12, month)) {
2527  return QDateTime();
2528  }
2529  break;
2530  case 'B':
2531  case 'b': { // month name, translated or English
2532  int m = matchMonth(str, s, calendar.data());
2533  if (m <= 0 || (month != NO_NUMBER && month != m)) {
2534  return QDateTime();
2535  }
2536  month = m;
2537  break;
2538  }
2539  case 'd': // day of month, 2 digits, 01 - 31
2540  if (!getNumber(str, s, 2, 2, 1, 31, day)) {
2541  return QDateTime();
2542  }
2543  break;
2544  case 'e': // day of month, 1 - 31
2545  if (!getNumber(str, s, 1, 2, 1, 31, day)) {
2546  return QDateTime();
2547  }
2548  break;
2549  case 'A':
2550  case 'a': { // week day name, translated or English
2551  int dow = matchDay(str, s, calendar.data());
2552  if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) {
2553  return QDateTime();
2554  }
2555  dayOfWeek = dow;
2556  break;
2557  }
2558  case 'H': // hour, 2 digits, 00 - 23
2559  if (!getNumber(str, s, 2, 2, 0, 23, hour)) {
2560  return QDateTime();
2561  }
2562  break;
2563  case 'k': // hour, 0 - 23
2564  if (!getNumber(str, s, 1, 2, 0, 23, hour)) {
2565  return QDateTime();
2566  }
2567  break;
2568  case 'I': // hour, 2 digits, 01 - 12
2569  if (!getNumber(str, s, 2, 2, 1, 12, hour)) {
2570  return QDateTime();
2571  }
2572  break;
2573  case 'l': // hour, 1 - 12
2574  if (!getNumber(str, s, 1, 2, 1, 12, hour)) {
2575  return QDateTime();
2576  }
2577  break;
2578  case 'M': // minutes, 2 digits, 00 - 59
2579  if (!getNumber(str, s, 2, 2, 0, 59, minute)) {
2580  return QDateTime();
2581  }
2582  break;
2583  case 'S': // seconds, 2 digits, 00 - 59
2584  if (!getNumber(str, s, 2, 2, 0, 59, second)) {
2585  return QDateTime();
2586  }
2587  break;
2588  case 's': // seconds, 0 - 59
2589  if (!getNumber(str, s, 1, 2, 0, 59, second)) {
2590  return QDateTime();
2591  }
2592  break;
2593  case 'P':
2594  case 'p': { // am/pm
2595  int ap = getAmPm(str, s, true);
2596  if (!ap || (ampm != NO_NUMBER && ampm != ap)) {
2597  return QDateTime();
2598  }
2599  ampm = ap;
2600  break;
2601  }
2602  case 'z': // UTC offset in hours and optionally minutes
2603  zone = UTCOffset;
2604  break;
2605  case 'Z': // time zone abbreviation
2606  zone = TZAbbrev;
2607  break;
2608  case 't': // whitespace
2609  if (str[s++] != QLatin1Char(' ')) {
2610  return QDateTime();
2611  }
2612  break;
2613  default:
2614  if (s + 2 > send
2615  || str[s++] != QLatin1Char('%')
2616  || str[s++] != format[f]) {
2617  return QDateTime();
2618  }
2619  break;
2620  }
2621  } else if (flag == ':') {
2622  // It's a "%:" sequence
2623  switch (ch) {
2624  case 'Y': // full year, >= 4 digits
2625  if (!getNumber(str, s, 4, 100, NO_NUMBER, -1, year)) {
2626  return QDateTime();
2627  }
2628  break;
2629  case 'A':
2630  case 'a': { // week day name in English
2631  int dow = matchDay(str, s, nullptr);
2632  if (dow <= 0 || (dayOfWeek != NO_NUMBER && dayOfWeek != dow)) {
2633  return QDateTime();
2634  }
2635  dayOfWeek = dow;
2636  break;
2637  }
2638  case 'B':
2639  case 'b': { // month name in English
2640  int m = matchMonth(str, s, nullptr);
2641  if (m <= 0 || (month != NO_NUMBER && month != m)) {
2642  return QDateTime();
2643  }
2644  month = m;
2645  break;
2646  }
2647  case 'm': // month, 1 - 12
2648  if (!getNumber(str, s, 1, 2, 1, 12, month)) {
2649  return QDateTime();
2650  }
2651  break;
2652  case 'P':
2653  case 'p': { // am/pm in English
2654  int ap = getAmPm(str, s, false);
2655  if (!ap || (ampm != NO_NUMBER && ampm != ap)) {
2656  return QDateTime();
2657  }
2658  ampm = ap;
2659  break;
2660  }
2661  case 'M': // minutes, 0 - 59
2662  if (!getNumber(str, s, 1, 2, 0, 59, minute)) {
2663  return QDateTime();
2664  }
2665  break;
2666  case 'S': // seconds with ':' prefix, defaults to zero
2667  if (str[s] != QLatin1Char(':')) {
2668  second = 0;
2669  break;
2670  }
2671  ++s;
2672  if (!getNumber(str, s, 1, 2, 0, 59, second)) {
2673  return QDateTime();
2674  }
2675  break;
2676  case 's': { // milliseconds, with decimal point prefix
2677  if (str[s] != QLatin1Char('.')) {
2678  // If no locale, try comma, it is preferred by ISO8601 as the decimal point symbol
2680  if (!str.mid(s).startsWith(dpt)) {
2681  return QDateTime();
2682  }
2683  s += dpt.length() - 1;
2684  }
2685  ++s;
2686  if (s >= send) {
2687  return QDateTime();
2688  }
2689  QString val = str.mid(s);
2690  int i = 0;
2691  for (int end = val.length(); i < end && val[i].isDigit(); ++i);
2692  if (!i) {
2693  return QDateTime();
2694  }
2695  val.truncate(i);
2696  val += QLatin1String("00");
2697  val.truncate(3);
2698  int ms = val.toInt();
2699  if (millisec != NO_NUMBER && millisec != ms) {
2700  return QDateTime();
2701  }
2702  millisec = ms;
2703  s += i;
2704  break;
2705  }
2706  case 'u': // UTC offset in hours and optionally minutes
2707  zone = UTCOffset;
2708  break;
2709  case 'z': // UTC offset in hours and minutes, with colon
2710  zone = UTCOffsetColon;
2711  break;
2712  case 'Z': // time zone name
2713  zone = TZName;
2714  break;
2715  default:
2716  if (s + 3 > send
2717  || str[s++] != QLatin1Char('%')
2718  || str[s++] != QLatin1Char(':')
2719  || str[s++] != format[f]) {
2720  return QDateTime();
2721  }
2722  break;
2723  }
2724  flag = 0;
2725  }
2726  if (!flag) {
2727  escape = false;
2728  }
2729 
2730  if (zone != TZNone) {
2731  // Read time zone or UTC offset
2732  switch (zone) {
2733  case UTCOffset:
2734  case UTCOffsetColon:
2735  if (!zoneAbbrev.isEmpty() || !zoneName.isEmpty()) {
2736  return QDateTime();
2737  }
2738  if (!getUTCOffset(str, s, (zone == UTCOffsetColon), tzoffset)) {
2739  return QDateTime();
2740  }
2741  break;
2742  case TZAbbrev: { // time zone abbreviation
2743  if (tzoffset != NO_NUMBER || !zoneName.isEmpty()) {
2744  return QDateTime();
2745  }
2746  int start = s;
2747  while (s < send && str[s].isLetterOrNumber()) {
2748  ++s;
2749  }
2750  if (s == start) {
2751  return QDateTime();
2752  }
2753  QString z = str.mid(start, s - start);
2754  if (!zoneAbbrev.isEmpty() && z.toLatin1() != zoneAbbrev) {
2755  return QDateTime();
2756  }
2757  zoneAbbrev = z.toLatin1();
2758  break;
2759  }
2760  case TZName: { // time zone name
2761  if (tzoffset != NO_NUMBER || !zoneAbbrev.isEmpty()) {
2762  return QDateTime();
2763  }
2764  QString z;
2765  if (f + 1 >= fend) {
2766  z = str.mid(s);
2767  s = send;
2768  } else {
2769  // Get the terminating character for the zone name
2770  QChar endchar = format[f + 1];
2771  if (endchar == QLatin1Char('%') && f + 2 < fend) {
2772  QChar endchar2 = format[f + 2];
2773  if (endchar2 == QLatin1Char('n') || endchar2 == QLatin1Char('t')) {
2774  endchar = QLatin1Char(' ');
2775  }
2776  }
2777  // Extract from the input string up to the terminating character
2778  int start = s;
2779  for (; s < send && str[s] != endchar; ++s);
2780  if (s == start) {
2781  return QDateTime();
2782  }
2783  z = str.mid(start, s - start);
2784  }
2785  if (!zoneName.isEmpty() && z != zoneName) {
2786  return QDateTime();
2787  }
2788  zoneName = z;
2789  break;
2790  }
2791  default:
2792  break;
2793  }
2794  }
2795  }
2796 
2797  if (year == NO_NUMBER) {
2798  year = KDateTime::currentLocalDate().year();
2799  }
2800  if (month == NO_NUMBER) {
2801  month = 1;
2802  }
2803  QDate d = QDate(year, month, (day > 0 ? day : 1));
2804  if (!d.isValid()) {
2805  return QDateTime();
2806  }
2807  if (dayOfWeek != NO_NUMBER) {
2808  if (day == NO_NUMBER) {
2809  day = 1 + dayOfWeek - QDate(year, month, 1).dayOfWeek();
2810  if (day <= 0) {
2811  day += 7;
2812  }
2813  } else {
2814  if (QDate(year, month, day).dayOfWeek() != dayOfWeek) {
2815  return QDateTime();
2816  }
2817  }
2818  }
2819  if (day == NO_NUMBER) {
2820  day = 1;
2821  }
2822  dateOnly = (hour == NO_NUMBER && minute == NO_NUMBER && second == NO_NUMBER && millisec == NO_NUMBER);
2823  if (hour == NO_NUMBER) {
2824  hour = 0;
2825  }
2826  if (minute == NO_NUMBER) {
2827  minute = 0;
2828  }
2829  if (second == NO_NUMBER) {
2830  second = 0;
2831  }
2832  if (millisec == NO_NUMBER) {
2833  millisec = 0;
2834  }
2835  if (ampm != NO_NUMBER) {
2836  if (!hour || hour > 12) {
2837  return QDateTime();
2838  }
2839  if (ampm == 1 && hour == 12) {
2840  hour = 0;
2841  } else if (ampm == 2 && hour < 12) {
2842  hour += 12;
2843  }
2844  }
2845 
2846  QDateTime dt(d, QTime(hour, minute, second, millisec), (tzoffset == 0 ? Qt::UTC : Qt::LocalTime));
2847 
2848  utcOffset = (tzoffset == NO_NUMBER) ? 0 : tzoffset * 60;
2849 
2850  return dt;
2851 }
2852 
2853 /*
2854  * Find which day name matches the specified part of a string.
2855  * 'offset' is incremented by the length of the match.
2856  * Reply = day number (1 - 7), or <= 0 if no match.
2857  */
2858 int matchDay(const QString &string, int &offset, const KCalendarSystem *calendar)
2859 {
2860  int dayOfWeek;
2861  QString part = string.mid(offset);
2862  if (part.isEmpty()) {
2863  return -1;
2864  }
2865  if (calendar) {
2866  // Check for localised day name first
2867  for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) {
2868  QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::LongDayName);
2869  if (part.startsWith(name, Qt::CaseInsensitive)) {
2870  offset += name.length();
2871  return dayOfWeek;
2872  }
2873  }
2874  for (dayOfWeek = 1; dayOfWeek <= 7; ++dayOfWeek) {
2875  QString name = calendar->weekDayName(dayOfWeek, KCalendarSystem::ShortDayName);
2876  if (part.startsWith(name, Qt::CaseInsensitive)) {
2877  offset += name.length();
2878  return dayOfWeek;
2879  }
2880  }
2881  }
2882 
2883  // Check for English day name
2884  dayOfWeek = findString(part, longDay, 7, offset);
2885  if (dayOfWeek < 0) {
2886  dayOfWeek = findString(part, shortDay, 7, offset);
2887  }
2888  return dayOfWeek + 1;
2889 }
2890 
2891 /*
2892  * Find which month name matches the specified part of a string.
2893  * 'offset' is incremented by the length of the match.
2894  * Reply = month number (1 - 12), or <= 0 if no match.
2895  */
2896 int matchMonth(const QString &string, int &offset, const KCalendarSystem *calendar)
2897 {
2898  int month;
2899  QString part = string.mid(offset);
2900  if (part.isEmpty()) {
2901  return -1;
2902  }
2903  if (calendar) {
2904  // Check for localised month name first
2905  for (month = 1; month <= 12; ++month) {
2906  QString name = calendar->monthName(month, 2000, KCalendarSystem::LongName);
2907  if (part.startsWith(name, Qt::CaseInsensitive)) {
2908  offset += name.length();
2909  return month;
2910  }
2911  }
2912  for (month = 1; month <= 12; ++month) {
2913  QString name = calendar->monthName(month, 2000, KCalendarSystem::ShortName);
2914  if (part.startsWith(name, Qt::CaseInsensitive)) {
2915  offset += name.length();
2916  return month;
2917  }
2918  }
2919  }
2920  // Check for English month name
2921  month = findString(part, longMonth, 12, offset);
2922  if (month < 0) {
2923  month = findString(part, shortMonth, 12, offset);
2924  }
2925  return month + 1;
2926 }
2927 
2928 /*
2929  * Read a UTC offset from the input string.
2930  */
2931 bool getUTCOffset(const QString &string, int &offset, bool colon, int &result)
2932 {
2933  int sign;
2934  int len = string.length();
2935  if (offset >= len) {
2936  return false;
2937  }
2938  switch (string[offset++].unicode()) {
2939  case '+':
2940  sign = 1;
2941  break;
2942  case '-':
2943  sign = -1;
2944  break;
2945  default:
2946  return false;
2947  }
2948  int tzhour = NO_NUMBER;
2949  int tzmin = NO_NUMBER;
2950  if (!getNumber(string, offset, 2, 2, 0, 99, tzhour)) {
2951  return false;
2952  }
2953  if (colon) {
2954  if (offset >= len || string[offset++] != QLatin1Char(':')) {
2955  return false;
2956  }
2957  }
2958  if (offset >= len || !string[offset].isDigit()) {
2959  tzmin = 0;
2960  } else {
2961  if (!getNumber(string, offset, 2, 2, 0, 59, tzmin)) {
2962  return false;
2963  }
2964  }
2965  tzmin += tzhour * 60;
2966  if (result != NO_NUMBER && result != tzmin) {
2967  return false;
2968  }
2969  result = sign * tzmin;
2970  return true;
2971 }
2972 
2973 /*
2974  * Read an am/pm indicator from the input string.
2975  * 'offset' is incremented by the length of the match.
2976  * Reply = 1 (am), 2 (pm), or 0 if no match.
2977  */
2978 int getAmPm(const QString &string, int &offset, bool localized)
2979 {
2980  QString part = string.mid(offset);
2981  int ap = 0;
2982  int n = 2;
2983  if (localized) {
2984  // Check localized form first
2985  QString aps = i18n("am");
2986  if (part.startsWith(aps, Qt::CaseInsensitive)) {
2987  ap = 1;
2988  n = aps.length();
2989  } else {
2990  aps = i18n("pm");
2991  if (part.startsWith(aps, Qt::CaseInsensitive)) {
2992  ap = 2;
2993  n = aps.length();
2994  }
2995  }
2996  }
2997  if (!ap) {
2998  if (part.startsWith(QLatin1String("am"), Qt::CaseInsensitive)) {
2999  ap = 1;
3000  } else if (part.startsWith(QLatin1String("pm"), Qt::CaseInsensitive)) {
3001  ap = 2;
3002  }
3003  }
3004  if (ap) {
3005  offset += n;
3006  }
3007  return ap;
3008 }
3009 
3010 /* Convert part of 'string' to a number.
3011  * If converted number differs from any current value in 'result', the function fails.
3012  * Reply = true if successful.
3013  */
3014 bool getNumber(const QString &string, int &offset, int mindigits, int maxdigits, int minval, int maxval, int &result)
3015 {
3016  int end = string.size();
3017  bool neg = false;
3018  if (minval == NO_NUMBER && offset < end && string[offset] == QLatin1Char('-')) {
3019  neg = true;
3020  ++offset;
3021  }
3022  if (offset + maxdigits > end) {
3023  maxdigits = end - offset;
3024  }
3025  int ndigits;
3026  for (ndigits = 0; ndigits < maxdigits && string[offset + ndigits].isDigit(); ++ndigits);
3027  if (ndigits < mindigits) {
3028  return false;
3029  }
3030  bool ok;
3031  int n = string.mid(offset, ndigits).toInt(&ok);
3032  if (neg) {
3033  n = -n;
3034  }
3035  if (!ok || (result != NO_NUMBER && n != result) || (minval != NO_NUMBER && n < minval) || (n > maxval && maxval >= 0)) {
3036  return false;
3037  }
3038  result = n;
3039  offset += ndigits;
3040  return true;
3041 }
3042 
3043 int findString_internal(const QString &string, const char *array, int count, int &offset, int disp)
3044 {
3045  for (int i = 0; i < count; ++i) {
3046  if (string.startsWith(QLatin1String(array + i * disp), Qt::CaseInsensitive)) {
3047  offset += qstrlen(array + i * disp);
3048  return i;
3049  }
3050  }
3051  return -1;
3052 }
void setMSecsSinceEpoch(qint64 msecs)
RFC 2822 format including day of the week, i.e.
Definition: kdatetime.h:420
static bool isSimulated()
Check whether there is a simulated local system time zone.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
a time in the current system time zone.
Definition: kdatetime.h:167
SpecType
The time specification type of a KDateTime instance.
Definition: kdatetime.h:158
KDateTime toTimeSpec(const Spec &spec) const
Returns the time converted to a new time specification.
Definition: kdatetime.cpp:1075
static Spec ClockTime()
The ClockTime time specification.
Definition: kdatetime.cpp:207
int currentOffset(Qt::TimeSpec basis=Qt::UTC) const
Returns the current offset of this time zone to UTC or the local system time zone.
Definition: ktimezone.cpp:885
KDateTime()
Constructs an invalid date/time.
Definition: kdatetime.cpp:799
KTimeZone timeZone() const
Returns the time zone for the date/time.
Definition: kdatetime.cpp:923
void clear()
Simultaneous, i.e.
Definition: kdatetime.h:475
void truncate(int position)
friend QDataStream & operator>>(QDataStream &in, KDateTime &dateTime)
Read a KDateTime object into dateTime from in, in binary format.
Definition: kdatetime.cpp:2442
bool isValid() const
Returns whether the date/time is valid.
Definition: kdatetime.cpp:864
This KDateTime starts after the start of the other, and ends before the end of the other...
Definition: kdatetime.h:464
Date/times with associated time zone.
static KDateTime currentLocalDateTime()
Returns the current date and time, as reported by the system clock, expressed in the local system tim...
Definition: kdatetime.cpp:1280
static Spec LocalZone()
Returns a local time zone time specification.
Definition: kdatetime.cpp:211
qint64 secsTo_long(const KDateTime &other) const
Definition: kdatetime.h:1076
static KTimeZone zone(const QString &name)
Returns the time zone with the given name.
DateFormat
Spec timeSpec() const
Returns the time specification of the date/time, i.e.
Definition: kdatetime.cpp:914
void setTimeSpec(const Spec &spec)
Changes the time specification of the instance.
Definition: kdatetime.cpp:1142
static void setSimulatedSystemTime(const KDateTime &newTime)
Set an adjustment to be applied when fetching the current system time.
Definition: kdatetime.cpp:2413
bool isValid() const
Checks whether the instance is valid.
Definition: ktimezone.cpp:637
This KDateTime is strictly later than the other, i.e.
Definition: kdatetime.h:472
Comparison compare(const KDateTime &other) const
Compare this instance with another to determine whether they are simultaneous, earlier or later...
Definition: kdatetime.cpp:1340
QMap::const_iterator constBegin() const const
Comparison
How this KDateTime compares with another.
Definition: kdatetime.h:456
KDateTime toOffsetFromUtc() const
Returns the time expressed as an offset from UTC, using the UTC offset associated with this instance&#39;...
Definition: kdatetime.cpp:965
bool isEmpty() const const
QString simplified() const const
bool isValid() const const
Same format as Qt::TextDate (i.e.
Definition: kdatetime.h:423
an invalid time specification.
Definition: kdatetime.h:159
virtual QString weekDayName(int weekDay, WeekDayNameFormat format=LongDayName) const =0
Gets specific calendar type week day name.
QString toString(const QString &format) const
Returns the date/time as a string.
Definition: kdatetime.cpp:1482
QTime time() const const
qint64 daysTo(const KDateTime &other) const
Calculates the number of days from this date/time to the other date/time.
Definition: kdatetime.cpp:1245
bool isOffsetFromUtc() const
Returns whether the time specification is a local time at a fixed offset from UTC.
Definition: kdatetime.cpp:235
int day() const const
void setSecondOccurrence(bool second)
Sets whether the date/time is the second occurrence of this time.
Definition: kdatetime.cpp:1147
bool isDateOnly() const
Returns whether the instance represents a date/time or a date-only value.
Definition: kdatetime.cpp:877
static KDateTime realCurrentLocalDateTime()
Return the real (not simulated) system time.
Definition: kdatetime.cpp:2427
static void setLocalZone(const KTimeZone &tz)
Set or clear the simulated local system time zone.
int length() const const
The KTimeZones class represents a time zone database which consists of a collection of individual tim...
Definition: ktimezone.h:308
bool isClockTime() const
Returns whether the time specification is a local clock time.
Definition: kdatetime.cpp:231
RFC 2822 format, i.e.
Definition: kdatetime.h:413
QString decimalSymbol() const
Returns what a decimal point should look like ("." or "," etc.) according to the current locale or us...
Definition: klocale.cpp:175
T * data() const const
void setTime(const QTime &time)
Sets the time part of the date/time.
Definition: kdatetime.cpp:1122
bool equivalentTo(const Spec &other) const
Checks whether this instance is equivalent to another.
Definition: kdatetime.cpp:254
KCalendarSystem abstract base class, provides support for local Calendar Systems in KDE...
void setDateOnly(bool dateOnly)
Sets the instance either to being a date and time value, or a date-only value.
Definition: kdatetime.cpp:1112
int size() const const
QString & sprintf(const char *cformat,...)
KTimeZone timeZone() const
Returns the time zone for the date/time, according to the time specification type as follows: ...
Definition: kdatetime.cpp:183
bool operator==(const Spec &other) const
Comparison operator.
Definition: kdatetime.cpp:244
T value(int i) const const
uint toTime_t() const
Converts the time to a UTC time, measured in seconds since 00:00:00 UTC 1st January 1970 (as returned...
Definition: kdatetime.cpp:1094
void clear()
bool isOffsetFromUtc() const
Returns whether the date/time is a local time at a fixed offset from UTC.
Definition: kdatetime.cpp:893
void setTime_t(qint64 seconds)
Sets the time to a UTC time, specified as seconds since 00:00:00 UTC 1st January 1970 (as returned by...
Definition: kdatetime.cpp:1103
bool operator==(const KDateTime &other) const
Check whether this date/time is simultaneous with another.
Definition: kdatetime.cpp:1403
int utcOffset() const
Returns the UTC offset associated with the time specification.
Definition: kdatetime.cpp:239
System time zone functions.
void setTimeSpec(Qt::TimeSpec spec)
QDate fromString(const QString &string, Qt::DateFormat format)
int dayOfWeek() const const
KDateTime addMSecs(qint64 msecs) const
Returns a date/time msecs milliseconds later than the stored date/time.
Definition: kdatetime.cpp:1160
QString number(int n, int base)
int count(const T &value) const const
int utcOffset() const
Returns the UTC offset associated with the date/time.
Definition: kdatetime.cpp:935
Same format as Qt::LocalDate (i.e.
Definition: kdatetime.h:427
TimeFormat
Format for strings representing date/time values.
Definition: kdatetime.h:398
qint64 secsTo(const KDateTime &other) const
Returns the number of seconds from this date/time to the other date/time.
Definition: kdatetime.cpp:1218
bool isClockTime() const
Returns whether the date/time is a local clock time.
Definition: kdatetime.cpp:885
QStringList capturedTexts() const const
Type type(const QSqlDatabase &db)
QByteArray abbreviation(const QDateTime &utcDateTime) const
Returns the time zone abbreviation current at a specified time.
Definition: ktimezone.cpp:675
SpecType type() const
Returns the time specification type, i.e.
Definition: kdatetime.cpp:219
CaseInsensitive
int toInt(bool *ok, int base) const const
bool isEmpty() const const
bool isEmpty() const const
QString trimmed() const const
QMap::const_iterator constEnd() const const
static void setFromStringDefault(const Spec &spec)
Sets the default time specification for use by fromString() when no time zone or UTC offset is found ...
Definition: kdatetime.cpp:2408
KDateTime toUtc() const
Returns the time converted to UTC.
Definition: kdatetime.cpp:947
QDate date() const
Returns the date part of the date/time.
Definition: kdatetime.cpp:901
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
KDateTime addDays(qint64 days) const
Returns a date/time days days later than the stored date/time.
Definition: kdatetime.cpp:1188
QDateTime dateTime() const
Returns the date/time component of the instance, ignoring the time zone.
Definition: kdatetime.cpp:909
bool isValid() const const
static KTimeZone local()
Returns the current local system time zone.
static Spec OffsetFromUTC(int utcOffset)
Returns a UTC offset time specification.
Definition: kdatetime.cpp:215
static KDateTime fromString(const QString &string, TimeFormat format=ISODate, bool *negZero=nullptr)
Returns the KDateTime represented by string, using the format given.
Definition: kdatetime.cpp:1838
a clock time which ignores time zones and simply uses whatever the local system clock says the time i...
Definition: kdatetime.h:179
KDateTime toClockTime() const
Returns the time converted to the local clock time.
Definition: kdatetime.cpp:1038
static const int InvalidOffset
Indicates an invalid UTC offset.
Definition: ktimezone.h:1075
KDateTime addYears(int years) const
Returns a date/time years years later than the stored date/time.
Definition: kdatetime.cpp:1208
QDateTime toUtc(const QDateTime &zoneDateTime) const
Converts a date/time, which is interpreted as local time in this time zone, into UTC.
Definition: ktimezone.cpp:803
static KLocale * global()
Return the global KLocale instance.
Definition: klocale.cpp:309
Spec()
Constructs an invalid time specification.
Definition: kdatetime.cpp:108
KDE Default, proleptic Gregorian Calendar as used by QDate.
Definition: klocale.h:647
static QTime currentLocalTime()
Returns the current time of day in the local time zone, as reported by the system clock...
Definition: kdatetime.cpp:1335
static KDateTime currentDateTime(const Spec &spec)
Returns the current date and time, as reported by the system clock, expressed in a given time specifi...
Definition: kdatetime.cpp:1312
KDateTime addSecs(qint64 secs) const
Returns a date/time secs seconds later than the stored date/time.
Definition: kdatetime.cpp:1183
static Spec UTC()
The UTC time specification.
Definition: kdatetime.cpp:203
A class representing a date and time with an associated time zone.
Definition: kdatetime.h:148
static KTimeZone realLocalZone()
Return the real (not simulated) local system time zone.
Qt::TimeSpec timeSpec() const const
QDateTime fromString(const QString &string, Qt::DateFormat format)
bool contains(const T &value) const const
Base class representing a time zone.
Definition: ktimezone.h:415
bool isValid() const const
virtual int offsetAtZoneTime(const QDateTime &zoneDateTime, int *secondOffset=nullptr) const
Returns the offset of this time zone to UTC at the given local date/time.
Definition: ktimezone.cpp:870
Spec & operator=(const Spec &spec)
Assignment operator.
Definition: kdatetime.cpp:137
QString i18n(const char *text, const TYPE &arg...)
const QChar * unicode() const const
static KCalendarSystem * create(KLocale::CalendarSystem calendarSystem, const KLocale *locale)
static QDate currentLocalDate()
Returns the current date in the local time zone, as reported by the system clock. ...
Definition: kdatetime.cpp:1330
a local time which has a fixed offset from UTC.
Definition: kdatetime.h:161
Short name format, e.g.
QDateTime currentDateTime()
KDateTime toLocalZone() const
Returns the time converted to the current local system time zone.
Definition: kdatetime.cpp:1011
QByteArray toLatin1() const const
QString mid(int position, int n) const const
virtual QString monthName(int month, int year, MonthNameFormat format=LongName) const =0
Gets specific calendar type month name for a given month number If an invalid month is specified...
bool isLocalZone() const
Returns whether the time specification is the current local system time zone.
Definition: kdatetime.cpp:227
QDate date() const const
a UTC time.
Definition: kdatetime.h:160
~Spec()
Destructor.
Definition: kdatetime.cpp:132
qint64 secsTo(const QDateTime &other) const const
friend QDataStream & operator<<(QDataStream &out, const KDateTime &dateTime)
Write dateTime to the datastream out, in binary format.
Definition: kdatetime.cpp:2436
KDateTime addMonths(int months) const
Returns a date/time months months later than the stored date/time.
Definition: kdatetime.cpp:1198
This KDateTime starts after the start of the other, and ends at the same time as the other...
Definition: kdatetime.h:468
void detach()
Create a separate copy of this instance&#39;s data if it is implicitly shared with another instance...
Definition: kdatetime.cpp:855
bool isLocalZone() const
Returns whether the time zone for the date/time is the current local system time zone.
Definition: kdatetime.cpp:881
ISO 8601 format, i.e.
Definition: kdatetime.h:399
This KDateTime is strictly earlier than the other, i.e.
Definition: kdatetime.h:457
void setType(SpecType type, int utcOffset=0)
Initialises the time specification.
Definition: kdatetime.cpp:150
This KDateTime starts before the start of the other, and ends after the end of the other...
Definition: kdatetime.h:478
int length() const const
KDateTime toZone(const KTimeZone &zone) const
Returns the time converted to a specified time zone.
Definition: kdatetime.cpp:1054
void setDateTime(const QDateTime &dt)
Sets the date/time part of the instance, leaving the time specification unaffected.
Definition: kdatetime.cpp:1127
This KDateTime starts at the same time as the other, and ends before the end of the other...
Definition: kdatetime.h:460
Short name format, e.g.
QString fromLatin1(const char *str, int size)
bool isUtc() const
Returns whether the date/time is a UTC time.
Definition: kdatetime.cpp:889
QDateTime toZoneTime(const QDateTime &utcDateTime, bool *secondOccurrence=nullptr) const
Converts a UTC date/time into local time in this time zone.
Definition: ktimezone.cpp:817
static KDateTime currentUtcDateTime()
Returns the current date and time, as reported by the system clock, expressed in UTC.
Definition: kdatetime.cpp:1290
QString name() const
Returns the name of the time zone.
Definition: ktimezone.cpp:662
QDate addDays(qint64 ndays) const const
const ZoneMap zones() const
Returns all the time zones defined in this collection.
Definition: ktimezone.cpp:61
RFC 3339 format, i.e.
Definition: kdatetime.h:431
bool isValid() const
Returns whether the time specification is valid.
Definition: kdatetime.cpp:223
QDateTime addSecs(qint64 s) const const
Long name format, e.g.
void setDate(const QDate &date)
Sets the date part of the date/time.
Definition: kdatetime.cpp:1117
int size() const const
int year() const const
The full time specification of a KDateTime instance.
Definition: kdatetime.h:199
int month() const const
SpecType timeType() const
Returns the time specification type of the date/time, i.e.
Definition: kdatetime.cpp:918
Long name format, e.g.
QTime time() const
Returns the time part of the date/time.
Definition: kdatetime.cpp:905
static KTimeZone utc()
Returns a standard UTC time zone, with name "UTC".
Definition: ktimezone.cpp:915
bool isSecondOccurrence() const
Returns whether the date/time is the second occurrence of this time.
Definition: kdatetime.cpp:897
bool operator<(const KDateTime &other) const
Check whether this date/time is earlier than another.
Definition: kdatetime.cpp:1437
bool isUtc() const
Returns whether the time specification is a UTC time.
Definition: kdatetime.cpp:194
KTimeZone zone(const QString &name) const
Returns the time zone with the given name.
Definition: ktimezone.cpp:109
a time in a specified time zone.
Definition: kdatetime.h:162
uint toTime_t() const const
const T value(const Key &key, const T &defaultValue) const const
bool isNull() const
Returns whether the date/time is null.
Definition: kdatetime.cpp:859
QDateTime addMSecs(qint64 msecs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Nov 26 2020 22:57:06 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.