KAlarmCal

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

KDE's Doxygen guidelines are available online.