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

KDE's Doxygen guidelines are available online.