diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-11 11:16:42 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-11 11:16:42 +0200 |
commit | ae97d11589dd03edeea0475163e6110869143b35 (patch) | |
tree | 15f79917b0e303445976028cb541668a1ebb67f1 /src/corelib/time | |
parent | 6e42979518aa0697ff31706616ddbc05486f1864 (diff) | |
parent | 4e875d988e99df57c439f481e258e2138fdb9410 (diff) |
Merge "Merge remote-tracking branch 'origin/dev' into wip/qt6"
Diffstat (limited to 'src/corelib/time')
-rw-r--r-- | src/corelib/time/qdatetime.cpp | 5685 | ||||
-rw-r--r-- | src/corelib/time/qdatetime.h | 426 | ||||
-rw-r--r-- | src/corelib/time/qdatetime_p.h | 151 | ||||
-rw-r--r-- | src/corelib/time/qdatetimeparser.cpp | 2047 | ||||
-rw-r--r-- | src/corelib/time/qdatetimeparser_p.h | 310 | ||||
-rw-r--r-- | src/corelib/time/qtimezone.cpp | 997 | ||||
-rw-r--r-- | src/corelib/time/qtimezone.h | 188 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate.cpp | 926 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_android.cpp | 257 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_data_p.h | 1257 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_icu.cpp | 508 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_mac.mm | 333 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_p.h | 493 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_tz.cpp | 1154 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_win.cpp | 927 | ||||
-rw-r--r-- | src/corelib/time/time.pri | 34 |
16 files changed, 15693 insertions, 0 deletions
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp new file mode 100644 index 0000000000..9220d210f1 --- /dev/null +++ b/src/corelib/time/qdatetime.cpp @@ -0,0 +1,5685 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "private/qdatetime_p.h" +#if QT_CONFIG(datetimeparser) +#include "private/qdatetimeparser_p.h" +#endif + +#include "qdatastream.h" +#include "qset.h" +#include "qlocale.h" +#include "qdatetime.h" +#if QT_CONFIG(timezone) +#include "qtimezoneprivate_p.h" +#endif +#include "qregexp.h" +#include "qdebug.h" +#ifndef Q_OS_WIN +#include <locale.h> +#endif + +#include <cmath> +#ifdef Q_CC_MINGW +# include <unistd.h> // Define _POSIX_THREAD_SAFE_FUNCTIONS to obtain localtime_r() +#endif +#include <time.h> +#ifdef Q_OS_WIN +# include <qt_windows.h> +# ifdef Q_OS_WINRT +# include "qfunctions_winrt.h" +# endif +#endif + +#if defined(Q_OS_MAC) +#include <private/qcore_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Date/Time Constants + *****************************************************************************/ + +enum { + SECS_PER_DAY = 86400, + MSECS_PER_DAY = 86400000, + SECS_PER_HOUR = 3600, + MSECS_PER_HOUR = 3600000, + SECS_PER_MIN = 60, + MSECS_PER_MIN = 60000, + TIME_T_MAX = 2145916799, // int maximum 2037-12-31T23:59:59 UTC + JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1) +}; + +/***************************************************************************** + QDate static helper functions + *****************************************************************************/ + +static inline QDate fixedDate(int y, int m, int d) +{ + QDate result(y, m, 1); + result.setDate(y, m, qMin(d, result.daysInMonth())); + return result; +} + +/* + Division, rounding down (rather than towards zero). + + From C++11 onwards, integer division is defined to round towards zero, so we + can rely on that when implementing this. This is only used with denominator b + > 0, so we only have to treat negative numerator, a, specially. + */ +static inline qint64 floordiv(qint64 a, int b) +{ + return (a - (a < 0 ? b - 1 : 0)) / b; +} + +static inline int floordiv(int a, int b) +{ + return (a - (a < 0 ? b - 1 : 0)) / b; +} + +static inline qint64 julianDayFromDate(int year, int month, int day) +{ + // Adjust for no year 0 + if (year < 0) + ++year; + +/* + * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php + * This formula is correct for all julian days, when using mathematical integer + * division (round to negative infinity), not c++11 integer division (round to zero) + */ + int a = floordiv(14 - month, 12); + qint64 y = (qint64)year + 4800 - a; + int m = month + 12 * a - 3; + return day + floordiv(153 * m + 2, 5) + 365 * y + floordiv(y, 4) - floordiv(y, 100) + floordiv(y, 400) - 32045; +} + +struct ParsedDate +{ + int year, month, day; +}; + +// prevent this function from being inlined into all 10 users +Q_NEVER_INLINE +static ParsedDate getDateFromJulianDay(qint64 julianDay) +{ +/* + * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php + * This formula is correct for all julian days, when using mathematical integer + * division (round to negative infinity), not c++11 integer division (round to zero) + */ + qint64 a = julianDay + 32044; + qint64 b = floordiv(4 * a + 3, 146097); + int c = a - floordiv(146097 * b, 4); + + int d = floordiv(4 * c + 3, 1461); + int e = c - floordiv(1461 * d, 4); + int m = floordiv(5 * e + 2, 153); + + int day = e - floordiv(153 * m + 2, 5) + 1; + int month = m + 3 - 12 * floordiv(m, 10); + int year = 100 * b + d - 4800 + floordiv(m, 10); + + // Adjust for no year 0 + if (year <= 0) + --year ; + + return { year, month, day }; +} + +/***************************************************************************** + Date/Time formatting helper functions + *****************************************************************************/ + +#if QT_CONFIG(textdate) +static const char qt_shortMonthNames[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static int qt_monthNumberFromShortName(QStringRef shortName) +{ + for (unsigned int i = 0; i < sizeof(qt_shortMonthNames) / sizeof(qt_shortMonthNames[0]); ++i) { + if (shortName == QLatin1String(qt_shortMonthNames[i], 3)) + return i + 1; + } + return -1; +} +static int qt_monthNumberFromShortName(const QString &shortName) +{ return qt_monthNumberFromShortName(QStringRef(&shortName)); } + +static int fromShortMonthName(const QStringRef &monthName) +{ + // Assume that English monthnames are the default + int month = qt_monthNumberFromShortName(monthName); + if (month != -1) + return month; + // If English names can't be found, search the localized ones + for (int i = 1; i <= 12; ++i) { + if (monthName == QLocale::system().monthName(i, QLocale::ShortFormat)) + return i; + } + return -1; +} +#endif // textdate + +#if QT_CONFIG(datestring) +struct ParsedRfcDateTime { + QDate date; + QTime time; + int utcOffset; +}; + +static ParsedRfcDateTime rfcDateImpl(const QString &s) +{ + ParsedRfcDateTime result; + + // Matches "Wdy, dd Mon yyyy HH:mm:ss ±hhmm" (Wdy, being optional) + QRegExp rex(QStringLiteral("^(?:[A-Z][a-z]+,)?[ \\t]*(\\d{1,2})[ \\t]+([A-Z][a-z]+)[ \\t]+(\\d\\d\\d\\d)(?:[ \\t]+(\\d\\d):(\\d\\d)(?::(\\d\\d))?)?[ \\t]*(?:([+-])(\\d\\d)(\\d\\d))?")); + if (s.indexOf(rex) == 0) { + const QStringList cap = rex.capturedTexts(); + result.date = QDate(cap[3].toInt(), qt_monthNumberFromShortName(cap[2]), cap[1].toInt()); + if (!cap[4].isEmpty()) + result.time = QTime(cap[4].toInt(), cap[5].toInt(), cap[6].toInt()); + const bool positiveOffset = (cap[7] == QLatin1String("+")); + const int hourOffset = cap[8].toInt(); + const int minOffset = cap[9].toInt(); + result.utcOffset = ((hourOffset * 60 + minOffset) * (positiveOffset ? 60 : -60)); + } else { + // Matches "Wdy Mon dd HH:mm:ss yyyy" + QRegExp rex(QStringLiteral("^[A-Z][a-z]+[ \\t]+([A-Z][a-z]+)[ \\t]+(\\d\\d)(?:[ \\t]+(\\d\\d):(\\d\\d):(\\d\\d))?[ \\t]+(\\d\\d\\d\\d)[ \\t]*(?:([+-])(\\d\\d)(\\d\\d))?")); + if (s.indexOf(rex) == 0) { + const QStringList cap = rex.capturedTexts(); + result.date = QDate(cap[6].toInt(), qt_monthNumberFromShortName(cap[1]), cap[2].toInt()); + if (!cap[3].isEmpty()) + result.time = QTime(cap[3].toInt(), cap[4].toInt(), cap[5].toInt()); + const bool positiveOffset = (cap[7] == QLatin1String("+")); + const int hourOffset = cap[8].toInt(); + const int minOffset = cap[9].toInt(); + result.utcOffset = ((hourOffset * 60 + minOffset) * (positiveOffset ? 60 : -60)); + } + } + + return result; +} +#endif // datestring + +// Return offset in [+-]HH:mm format +static QString toOffsetString(Qt::DateFormat format, int offset) +{ + return QString::asprintf("%c%02d%s%02d", + offset >= 0 ? '+' : '-', + qAbs(offset) / SECS_PER_HOUR, + // Qt::ISODate puts : between the hours and minutes, but Qt:TextDate does not: + format == Qt::TextDate ? "" : ":", + (qAbs(offset) / 60) % 60); +} + +#if QT_CONFIG(datestring) +// Parse offset in [+-]HH[[:]mm] format +static int fromOffsetString(const QStringRef &offsetString, bool *valid) noexcept +{ + *valid = false; + + const int size = offsetString.size(); + if (size < 2 || size > 6) + return 0; + + // sign will be +1 for a positive and -1 for a negative offset + int sign; + + // First char must be + or - + const QChar signChar = offsetString.at(0); + if (signChar == QLatin1Char('+')) + sign = 1; + else if (signChar == QLatin1Char('-')) + sign = -1; + else + return 0; + + // Split the hour and minute parts + const QStringRef time = offsetString.mid(1); + int hhLen = time.indexOf(QLatin1Char(':')); + int mmIndex; + if (hhLen == -1) + mmIndex = hhLen = 2; // [+-]HHmm or [+-]HH format + else + mmIndex = hhLen + 1; + + const QStringRef hhRef = time.left(hhLen); + bool ok = false; + const int hour = hhRef.toInt(&ok); + if (!ok) + return 0; + + const QStringRef mmRef = time.mid(mmIndex); + const int minute = mmRef.isEmpty() ? 0 : mmRef.toInt(&ok); + if (!ok || minute < 0 || minute > 59) + return 0; + + *valid = true; + return sign * ((hour * 60) + minute) * 60; +} +#endif // datestring + +static constexpr int daysInUsualMonth(int month) // (February isn't usual.) +{ + // Long if odd up to July = 7, or if even from 8 = August onwards: + return Q_ASSERT(month != 2 && month > 0 && month <= 12), 30 | ((month & 1) ^ (month >> 3)); +} + +/***************************************************************************** + QDate member functions + *****************************************************************************/ + +/*! + \since 4.5 + + \enum QDate::MonthNameType + + This enum describes the types of the string representation used + for the month name. + + \value DateFormat This type of name can be used for date-to-string formatting. + \value StandaloneFormat This type is used when you need to enumerate months or weekdays. + Usually standalone names are represented in singular forms with + capitalized first letter. +*/ + +/*! + \class QDate + \inmodule QtCore + \reentrant + \brief The QDate class provides date functions. + + + A QDate object encodes a calendar date, i.e. year, month, and day numbers, + in the proleptic Gregorian calendar by default. It can read the current date + from the system clock. It provides functions for comparing dates, and for + manipulating dates. For example, it is possible to add and subtract days, + months, and years to dates. + + A QDate object is typically created by giving the year, month, and day + numbers explicitly. Note that QDate interprets two digit years as presented, + i.e., as years 0 through 99, without adding any offset. A QDate can also be + constructed with the static function currentDate(), which creates a QDate + object containing the system clock's date. An explicit date can also be set + using setDate(). The fromString() function returns a QDate given a string + and a date format which is used to interpret the date within the string. + + The year(), month(), and day() functions provide access to the + year, month, and day numbers. Also, dayOfWeek() and dayOfYear() + functions are provided. The same information is provided in + textual format by the toString(), shortDayName(), longDayName(), + shortMonthName(), and longMonthName() functions. + + QDate provides a full set of operators to compare two QDate + objects where smaller means earlier, and larger means later. + + You can increment (or decrement) a date by a given number of days + using addDays(). Similarly you can use addMonths() and addYears(). + The daysTo() function returns the number of days between two + dates. + + The daysInMonth() and daysInYear() functions return how many days + there are in this date's month and year, respectively. The + isLeapYear() function indicates whether a date is in a leap year. + + \section1 Remarks + + \section2 No Year 0 + + There is no year 0. Dates in that year are considered invalid. The year -1 + is the year "1 before Christ" or "1 before current era." The day before 1 + January 1 CE, QDate(1, 1, 1), is 31 December 1 BCE, QDate(-1, 12, 31). + + \section2 Range of Valid Dates + + Dates are stored internally as a Julian Day number, an integer count of + every day in a contiguous range, with 24 November 4714 BCE in the Gregorian + calendar being Julian Day 0 (1 January 4713 BCE in the Julian calendar). + As well as being an efficient and accurate way of storing an absolute date, + it is suitable for converting a Date into other calendar systems such as + Hebrew, Islamic or Chinese. The Julian Day number can be obtained using + QDate::toJulianDay() and can be set using QDate::fromJulianDay(). + + The range of dates able to be stored by QDate as a Julian Day number is + for technical reasons limited to between -784350574879 and 784354017364, + which means from before 2 billion BCE to after 2 billion CE. + + \sa QTime, QDateTime, QDateEdit, QDateTimeEdit, QCalendarWidget +*/ + +/*! + \fn QDate::QDate() + + Constructs a null date. Null dates are invalid. + + \sa isNull(), isValid() +*/ + +/*! + Constructs a date with year \a y, month \a m and day \a d. + + If the specified date is invalid, the date is not set and + isValid() returns \c false. + + \warning Years 1 to 99 are interpreted as is. Year 0 is invalid. + + \sa isValid() +*/ + +QDate::QDate(int y, int m, int d) +{ + setDate(y, m, d); +} + + +/*! + \fn bool QDate::isNull() const + + Returns \c true if the date is null; otherwise returns \c false. A null + date is invalid. + + \note The behavior of this function is equivalent to isValid(). + + \sa isValid() +*/ + +/*! + \fn bool QDate::isValid() const + + Returns \c true if this date is valid; otherwise returns \c false. + + \sa isNull() +*/ + +/*! + Returns the year of this date. Negative numbers indicate years + before 1 CE, such that year -44 is 44 BCE. + + Returns 0 if the date is invalid. + + \sa month(), day() +*/ + +int QDate::year() const +{ + if (isNull()) + return 0; + + return getDateFromJulianDay(jd).year; +} + +/*! + Returns the number corresponding to the month of this date, using + the following convention: + + \list + \li 1 = "January" + \li 2 = "February" + \li 3 = "March" + \li 4 = "April" + \li 5 = "May" + \li 6 = "June" + \li 7 = "July" + \li 8 = "August" + \li 9 = "September" + \li 10 = "October" + \li 11 = "November" + \li 12 = "December" + \endlist + + Returns 0 if the date is invalid. + + \sa year(), day() +*/ + +int QDate::month() const +{ + if (isNull()) + return 0; + + return getDateFromJulianDay(jd).month; +} + +/*! + Returns the day of the month (1 to 31) of this date. + + Returns 0 if the date is invalid. + + \sa year(), month(), dayOfWeek() +*/ + +int QDate::day() const +{ + if (isNull()) + return 0; + + return getDateFromJulianDay(jd).day; +} + +/*! + Returns the weekday (1 = Monday to 7 = Sunday) for this date. + + Returns 0 if the date is invalid. + + \sa day(), dayOfYear(), Qt::DayOfWeek +*/ + +int QDate::dayOfWeek() const +{ + if (isNull()) + return 0; + + if (jd >= 0) + return (jd % 7) + 1; + else + return ((jd + 1) % 7) + 7; +} + +/*! + Returns the day of the year (1 to 365 or 366 on leap years) for + this date. + + Returns 0 if the date is invalid. + + \sa day(), dayOfWeek() +*/ + +int QDate::dayOfYear() const +{ + if (isNull()) + return 0; + + return jd - julianDayFromDate(year(), 1, 1) + 1; +} + +/*! + Returns the number of days in the month (28 to 31) for this date. + + Returns 0 if the date is invalid. + + \sa day(), daysInYear() +*/ + +int QDate::daysInMonth() const +{ + if (isNull()) + return 0; + + const ParsedDate pd = getDateFromJulianDay(jd); + if (pd.month == 2) + return isLeapYear(pd.year) ? 29 : 28; + + return daysInUsualMonth(pd.month); +} + +/*! + Returns the number of days in the year (365 or 366) for this date. + + Returns 0 if the date is invalid. + + \sa day(), daysInMonth() +*/ + +int QDate::daysInYear() const +{ + if (isNull()) + return 0; + + return isLeapYear(getDateFromJulianDay(jd).year) ? 366 : 365; +} + +/*! + Returns the week number (1 to 53), and stores the year in + *\a{yearNumber} unless \a yearNumber is null (the default). + + Returns 0 if the date is invalid. + + In accordance with ISO 8601, weeks start on Monday and the first + Thursday of a year is always in week 1 of that year. Most years + have 52 weeks, but some have 53. + + *\a{yearNumber} is not always the same as year(). For example, 1 + January 2000 has week number 52 in the year 1999, and 31 December + 2002 has week number 1 in the year 2003. + + \sa isValid() +*/ + +int QDate::weekNumber(int *yearNumber) const +{ + if (!isValid()) + return 0; + + int year = QDate::year(); + int yday = dayOfYear(); + int wday = dayOfWeek(); + + int week = (yday - wday + 10) / 7; + + if (week == 0) { + // last week of previous year + --year; + week = (yday + 365 + (QDate::isLeapYear(year) ? 1 : 0) - wday + 10) / 7; + Q_ASSERT(week == 52 || week == 53); + } else if (week == 53) { + // maybe first week of next year + int w = (yday - 365 - (QDate::isLeapYear(year) ? 1 : 0) - wday + 10) / 7; + if (w > 0) { + ++year; + week = w; + } + Q_ASSERT(week == 53 || week == 1); + } + + if (yearNumber != 0) + *yearNumber = year; + return week; +} + +static bool inDateTimeRange(qint64 jd, bool start) +{ + using Bounds = std::numeric_limits<qint64>; + if (jd < Bounds::min() + JULIAN_DAY_FOR_EPOCH) + return false; + jd -= JULIAN_DAY_FOR_EPOCH; + const qint64 maxDay = Bounds::max() / MSECS_PER_DAY; + const qint64 minDay = Bounds::min() / MSECS_PER_DAY - 1; + // (Divisions rounded towards zero, as MSECS_PER_DAY has factors other than two.) + // Range includes start of last day and end of first: + if (start) + return jd > minDay && jd <= maxDay; + return jd >= minDay && jd < maxDay; +} + +static QDateTime toEarliest(const QDate &day, const QDateTime &form) +{ + const Qt::TimeSpec spec = form.timeSpec(); + const int offset = (spec == Qt::OffsetFromUTC) ? form.offsetFromUtc() : 0; +#if QT_CONFIG(timezone) + QTimeZone zone; + if (spec == Qt::TimeZone) + zone = form.timeZone(); +#endif + auto moment = [=](QTime time) { + switch (spec) { + case Qt::OffsetFromUTC: return QDateTime(day, time, spec, offset); +#if QT_CONFIG(timezone) + case Qt::TimeZone: return QDateTime(day, time, zone); +#endif + default: return QDateTime(day, time, spec); + } + }; + // Longest routine time-zone transition is 2 hours: + QDateTime when = moment(QTime(2, 0)); + if (!when.isValid()) { + // Noon should be safe ... + when = moment(QTime(12, 0)); + if (!when.isValid()) { + // ... unless it's a 24-hour jump (moving the date-line) + when = moment(QTime(23, 59, 59, 999)); + if (!when.isValid()) + return QDateTime(); + } + } + int high = when.time().msecsSinceStartOfDay() / 60000; + int low = 0; + // Binary chop to the right minute + while (high > low + 1) { + int mid = (high + low) / 2; + QDateTime probe = moment(QTime(mid / 60, mid % 60)); + if (probe.isValid() && probe.date() == day) { + high = mid; + when = probe; + } else { + low = mid; + } + } + return when; +} + +/*! + \since 5.14 + \fn QDateTime QDate::startOfDay(Qt::TimeSpec spec, int offsetSeconds) const + \fn QDateTime QDate::startOfDay(const QTimeZone &zone) const + + Returns the start-moment of the day. Usually, this shall be midnight at the + start of the day: however, if a time-zone transition causes the given date + to skip over that midnight (e.g. a DST spring-forward skipping from the end + of the previous day to 01:00 of the new day), the actual earliest time in + the day is returned. This can only arise when the start-moment is specified + in terms of a time-zone (by passing its QTimeZone as \a zone) or in terms of + local time (by passing Qt::LocalTime as \a spec; this is its default). + + The \a offsetSeconds is ignored unless \a spec is Qt::OffsetFromUTC, when it + gives the implied zone's offset from UTC. As UTC and such zones have no + transitions, the start of the day is QTime(0, 0) in these cases. + + In the rare case of a date that was entirely skipped (this happens when a + zone east of the international date-line switches to being west of it), the + return shall be invalid. Passing Qt::TimeZone as \a spec (instead of + passing a QTimeZone) or passing an invalid time-zone as \a zone will also + produce an invalid result, as shall dates that start outside the range + representable by QDateTime. + + \sa endOfDay() +*/ +QDateTime QDate::startOfDay(Qt::TimeSpec spec, int offsetSeconds) const +{ + if (!inDateTimeRange(jd, true)) + return QDateTime(); + + switch (spec) { + case Qt::TimeZone: // should pass a QTimeZone instead of Qt::TimeZone + qWarning() << "Called QDate::startOfDay(Qt::TimeZone) on" << *this; + return QDateTime(); + case Qt::OffsetFromUTC: + case Qt::UTC: + return QDateTime(*this, QTime(0, 0), spec, offsetSeconds); + + case Qt::LocalTime: + if (offsetSeconds) + qWarning("Ignoring offset (%d seconds) passed with Qt::LocalTime", offsetSeconds); + break; + } + QDateTime when(*this, QTime(0, 0), spec); + if (!when.isValid()) + when = toEarliest(*this, when); + + return when.isValid() ? when : QDateTime(); +} + +#if QT_CONFIG(timezone) +/*! + \overload + \since 5.14 +*/ +QDateTime QDate::startOfDay(const QTimeZone &zone) const +{ + if (!inDateTimeRange(jd, true) || !zone.isValid()) + return QDateTime(); + + QDateTime when(*this, QTime(0, 0), zone); + if (when.isValid()) + return when; + + // The start of the day must have fallen in a spring-forward's gap; find the spring-forward: + if (zone.hasTransitions()) { + QTimeZone::OffsetData tran = zone.previousTransition(QDateTime(*this, QTime(23, 59, 59, 999), zone)); + const QDateTime &at = tran.atUtc.toTimeZone(zone); + if (at.isValid() && at.date() == *this) + return at; + } + + when = toEarliest(*this, when); + return when.isValid() ? when : QDateTime(); +} +#endif // timezone + +static QDateTime toLatest(const QDate &day, const QDateTime &form) +{ + const Qt::TimeSpec spec = form.timeSpec(); + const int offset = (spec == Qt::OffsetFromUTC) ? form.offsetFromUtc() : 0; +#if QT_CONFIG(timezone) + QTimeZone zone; + if (spec == Qt::TimeZone) + zone = form.timeZone(); +#endif + auto moment = [=](QTime time) { + switch (spec) { + case Qt::OffsetFromUTC: return QDateTime(day, time, spec, offset); +#if QT_CONFIG(timezone) + case Qt::TimeZone: return QDateTime(day, time, zone); +#endif + default: return QDateTime(day, time, spec); + } + }; + // Longest routine time-zone transition is 2 hours: + QDateTime when = moment(QTime(21, 59, 59, 999)); + if (!when.isValid()) { + // Noon should be safe ... + when = moment(QTime(12, 0)); + if (!when.isValid()) { + // ... unless it's a 24-hour jump (moving the date-line) + when = moment(QTime(0, 0)); + if (!when.isValid()) + return QDateTime(); + } + } + int high = 24 * 60; + int low = when.time().msecsSinceStartOfDay() / 60000; + // Binary chop to the right minute + while (high > low + 1) { + int mid = (high + low) / 2; + QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999)); + if (probe.isValid() && probe.date() == day) { + low = mid; + when = probe; + } else { + high = mid; + } + } + return when; +} + +/*! + \since 5.14 + \fn QDateTime QDate::endOfDay(Qt::TimeSpec spec, int offsetSeconds) const + \fn QDateTime QDate::endOfDay(const QTimeZone &zone) const + + Returns the end-moment of the day. Usually, this is one millisecond before + the midnight at the end of the day: however, if a time-zone transition + causes the given date to skip over that midnight (e.g. a DST spring-forward + skipping from just before 23:00 to the start of the next day), the actual + latest time in the day is returned. This can only arise when the + start-moment is specified in terms of a time-zone (by passing its QTimeZone + as \a zone) or in terms of local time (by passing Qt::LocalTime as \a spec; + this is its default). + + The \a offsetSeconds is ignored unless \a spec is Qt::OffsetFromUTC, when it + gives the implied zone's offset from UTC. As UTC and such zones have no + transitions, the end of the day is QTime(23, 59, 59, 999) in these cases. + + In the rare case of a date that was entirely skipped (this happens when a + zone east of the international date-line switches to being west of it), the + return shall be invalid. Passing Qt::TimeZone as \a spec (instead of + passing a QTimeZone) will also produce an invalid result, as shall dates + that end outside the range representable by QDateTime. + + \sa startOfDay() +*/ +QDateTime QDate::endOfDay(Qt::TimeSpec spec, int offsetSeconds) const +{ + if (!inDateTimeRange(jd, false)) + return QDateTime(); + + switch (spec) { + case Qt::TimeZone: // should pass a QTimeZone instead of Qt::TimeZone + qWarning() << "Called QDate::endOfDay(Qt::TimeZone) on" << *this; + return QDateTime(); + case Qt::UTC: + case Qt::OffsetFromUTC: + return QDateTime(*this, QTime(23, 59, 59, 999), spec, offsetSeconds); + + case Qt::LocalTime: + if (offsetSeconds) + qWarning("Ignoring offset (%d seconds) passed with Qt::LocalTime", offsetSeconds); + break; + } + QDateTime when(*this, QTime(23, 59, 59, 999), spec); + if (!when.isValid()) + when = toLatest(*this, when); + return when.isValid() ? when : QDateTime(); +} + +#if QT_CONFIG(timezone) +/*! + \overload + \since 5.14 +*/ +QDateTime QDate::endOfDay(const QTimeZone &zone) const +{ + if (!inDateTimeRange(jd, false) || !zone.isValid()) + return QDateTime(); + + QDateTime when(*this, QTime(23, 59, 59, 999), zone); + if (when.isValid()) + return when; + + // The end of the day must have fallen in a spring-forward's gap; find the spring-forward: + if (zone.hasTransitions()) { + QTimeZone::OffsetData tran = zone.nextTransition(QDateTime(*this, QTime(0, 0), zone)); + const QDateTime &at = tran.atUtc.toTimeZone(zone); + if (at.isValid() && at.date() == *this) + return at; + } + + when = toLatest(*this, when); + return when.isValid() ? when : QDateTime(); +} +#endif // timezone + +#if QT_DEPRECATED_SINCE(5, 11) && QT_CONFIG(textdate) + +/*! + \since 4.5 + \deprecated + + Returns the short name of the \a month for the representation specified + by \a type. + + The months are enumerated using the following convention: + + \list + \li 1 = "Jan" + \li 2 = "Feb" + \li 3 = "Mar" + \li 4 = "Apr" + \li 5 = "May" + \li 6 = "Jun" + \li 7 = "Jul" + \li 8 = "Aug" + \li 9 = "Sep" + \li 10 = "Oct" + \li 11 = "Nov" + \li 12 = "Dec" + \endlist + + The month names will be localized according to the system's + locale settings, i.e. using QLocale::system(). + + Returns an empty string if the date is invalid. + + \sa toString(), longMonthName(), shortDayName(), longDayName() +*/ + +QString QDate::shortMonthName(int month, QDate::MonthNameType type) +{ + switch (type) { + case QDate::DateFormat: + return QLocale::system().monthName(month, QLocale::ShortFormat); + case QDate::StandaloneFormat: + return QLocale::system().standaloneMonthName(month, QLocale::ShortFormat); + } + return QString(); +} + +/*! + \since 4.5 + \deprecated + + Returns the long name of the \a month for the representation specified + by \a type. + + The months are enumerated using the following convention: + + \list + \li 1 = "January" + \li 2 = "February" + \li 3 = "March" + \li 4 = "April" + \li 5 = "May" + \li 6 = "June" + \li 7 = "July" + \li 8 = "August" + \li 9 = "September" + \li 10 = "October" + \li 11 = "November" + \li 12 = "December" + \endlist + + The month names will be localized according to the system's + locale settings, i.e. using QLocale::system(). + + Returns an empty string if the date is invalid. + + \sa toString(), shortMonthName(), shortDayName(), longDayName() +*/ + +QString QDate::longMonthName(int month, MonthNameType type) +{ + switch (type) { + case QDate::DateFormat: + return QLocale::system().monthName(month, QLocale::LongFormat); + case QDate::StandaloneFormat: + return QLocale::system().standaloneMonthName(month, QLocale::LongFormat); + } + return QString(); +} + +/*! + \since 4.5 + \deprecated + + Returns the short name of the \a weekday for the representation specified + by \a type. + + The days are enumerated using the following convention: + + \list + \li 1 = "Mon" + \li 2 = "Tue" + \li 3 = "Wed" + \li 4 = "Thu" + \li 5 = "Fri" + \li 6 = "Sat" + \li 7 = "Sun" + \endlist + + The day names will be localized according to the system's + locale settings, i.e. using QLocale::system(). + + Returns an empty string if the date is invalid. + + \sa toString(), shortMonthName(), longMonthName(), longDayName() +*/ + +QString QDate::shortDayName(int weekday, MonthNameType type) +{ + switch (type) { + case QDate::DateFormat: + return QLocale::system().dayName(weekday, QLocale::ShortFormat); + case QDate::StandaloneFormat: + return QLocale::system().standaloneDayName(weekday, QLocale::ShortFormat); + } + return QString(); +} + +/*! + \since 4.5 + \deprecated + + Returns the long name of the \a weekday for the representation specified + by \a type. + + The days are enumerated using the following convention: + + \list + \li 1 = "Monday" + \li 2 = "Tuesday" + \li 3 = "Wednesday" + \li 4 = "Thursday" + \li 5 = "Friday" + \li 6 = "Saturday" + \li 7 = "Sunday" + \endlist + + The day names will be localized according to the system's + locale settings, i.e. using QLocale::system(). + + Returns an empty string if the date is invalid. + + \sa toString(), shortDayName(), shortMonthName(), longMonthName() +*/ + +QString QDate::longDayName(int weekday, MonthNameType type) +{ + switch (type) { + case QDate::DateFormat: + return QLocale::system().dayName(weekday, QLocale::LongFormat); + case QDate::StandaloneFormat: + return QLocale::system().standaloneDayName(weekday, QLocale::LongFormat); + } + return QString(); +} +#endif // textdate && deprecated + +#if QT_CONFIG(datestring) + +#if QT_CONFIG(textdate) +static QString toStringTextDate(QDate date) +{ + const ParsedDate pd = getDateFromJulianDay(date.toJulianDay()); + static const QLatin1Char sp(' '); + return QLocale::system().dayName(date.dayOfWeek(), QLocale::ShortFormat) + sp + + QLocale::system().monthName(pd.month, QLocale::ShortFormat) + sp + + QString::number(pd.day) + sp + + QString::number(pd.year); +} +#endif // textdate + +static QString toStringIsoDate(qint64 jd) +{ + const ParsedDate pd = getDateFromJulianDay(jd); + if (pd.year >= 0 && pd.year <= 9999) + return QString::asprintf("%04d-%02d-%02d", pd.year, pd.month, pd.day); + else + return QString(); +} + +/*! + \fn QString QDate::toString(Qt::DateFormat format) const + + \overload + + Returns the date as a string. The \a format parameter determines + the format of the string. + + If the \a format is Qt::TextDate, the string is formatted in + the default way. QDate::shortDayName() and QDate::shortMonthName() + are used to generate the string, so the day and month names will + be localized names using the system locale, i.e. QLocale::system(). An + example of this formatting is "Sat May 20 1995". + + If the \a format is Qt::ISODate, the string format corresponds + to the ISO 8601 extended specification for representations of + dates and times, taking the form yyyy-MM-dd, where yyyy is the + year, MM is the month of the year (between 01 and 12), and dd is + the day of the month between 01 and 31. + + If the \a format is Qt::SystemLocaleShortDate or + Qt::SystemLocaleLongDate, the string format depends on the locale + settings of the system. Identical to calling + QLocale::system().toString(date, QLocale::ShortFormat) or + QLocale::system().toString(date, QLocale::LongFormat). + + If the \a format is Qt::DefaultLocaleShortDate or + Qt::DefaultLocaleLongDate, the string format depends on the + default application locale. This is the locale set with + QLocale::setDefault(), or the system locale if no default locale + has been set. Identical to calling + \l {QLocale::toString()}{QLocale().toString(date, QLocale::ShortFormat) } or + \l {QLocale::toString()}{QLocale().toString(date, QLocale::LongFormat)}. + + If the \a format is Qt::RFC2822Date, the string is formatted in + an \l{RFC 2822} compatible way. An example of this formatting is + "20 May 1995". + + If the date is invalid, an empty string will be returned. + + \warning The Qt::ISODate format is only valid for years in the + range 0 to 9999. This restriction may apply to locale-aware + formats as well, depending on the locale settings. + + \sa fromString(), shortDayName(), shortMonthName(), QLocale::toString() +*/ +QString QDate::toString(Qt::DateFormat format) const +{ + if (!isValid()) + return QString(); + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toString(*this, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toString(*this, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toString(*this, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toString(*this, QLocale::LongFormat); + case Qt::RFC2822Date: + return QLocale::c().toString(*this, QStringViewLiteral("dd MMM yyyy")); + default: +#if QT_CONFIG(textdate) + case Qt::TextDate: + return toStringTextDate(*this); +#endif + case Qt::ISODate: + case Qt::ISODateWithMs: + return toStringIsoDate(jd); + } +} + +/*! + \fn QString QDate::toString(const QString &format) const + \fn QString QDate::toString(QStringView format) const + + Returns the date as a string. The \a format parameter determines + the format of the result string. + + These expressions may be used: + + \table + \header \li Expression \li Output + \row \li d \li the day as number without a leading zero (1 to 31) + \row \li dd \li the day as number with a leading zero (01 to 31) + \row \li ddd + \li the abbreviated localized day name (e.g. 'Mon' to 'Sun'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li dddd + \li the long localized day name (e.g. 'Monday' to 'Sunday'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li M \li the month as number without a leading zero (1 to 12) + \row \li MM \li the month as number with a leading zero (01 to 12) + \row \li MMM + \li the abbreviated localized month name (e.g. 'Jan' to 'Dec'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li MMMM + \li the long localized month name (e.g. 'January' to 'December'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li yy \li the year as two digit number (00 to 99) + \row \li yyyy \li the year as four digit number. If the year is negative, + a minus sign is prepended in addition. + \endtable + + Any sequence of characters enclosed in single quotes will be included + verbatim in the output string (stripped of the quotes), even if it contains + formatting characters. Two consecutive single quotes ("''") are replaced by + a single quote in the output. All other characters in the format string are + included verbatim in the output string. + + Formats without separators (e.g. "ddMM") are supported but must be used with + care, as the resulting strings aren't always reliably readable (e.g. if "dM" + produces "212" it could mean either the 2nd of December or the 21st of + February). + + Example format strings (assuming that the QDate is the 20 July + 1969): + + \table + \header \li Format \li Result + \row \li dd.MM.yyyy \li 20.07.1969 + \row \li ddd MMMM d yy \li Sun July 20 69 + \row \li 'The day is' dddd \li The day is Sunday + \endtable + + If the datetime is invalid, an empty string will be returned. + + \sa fromString(), QDateTime::toString(), QTime::toString(), QLocale::toString() + +*/ +QString QDate::toString(QStringView format) const +{ + return QLocale::system().toString(*this, format); // QLocale::c() ### Qt6 +} + +#if QT_STRINGVIEW_LEVEL < 2 +QString QDate::toString(const QString &format) const +{ + return toString(qToStringViewIgnoringNull(format)); +} +#endif + +#endif // datestring + +/*! + \fn bool QDate::setYMD(int y, int m, int d) + + \deprecated in 5.0, use setDate() instead. + + Sets the date's year \a y, month \a m, and day \a d. + + If \a y is in the range 0 to 99, it is interpreted as 1900 to + 1999. + Returns \c false if the date is invalid. + + Use setDate() instead. +*/ + +/*! + \since 4.2 + + Sets the date's \a year, \a month, and \a day. Returns \c true if + the date is valid; otherwise returns \c false. + + If the specified date is invalid, the QDate object is set to be + invalid. + + \sa isValid() +*/ +bool QDate::setDate(int year, int month, int day) +{ + if (isValid(year, month, day)) + jd = julianDayFromDate(year, month, day); + else + jd = nullJd(); + + return isValid(); +} + +/*! + \since 4.5 + + Extracts the date's year, month, and day, and assigns them to + *\a year, *\a month, and *\a day. The pointers may be null. + + Returns 0 if the date is invalid. + + \note In Qt versions prior to 5.7, this function is marked as non-\c{const}. + + \sa year(), month(), day(), isValid() +*/ +void QDate::getDate(int *year, int *month, int *day) const +{ + ParsedDate pd = { 0, 0, 0 }; + if (isValid()) + pd = getDateFromJulianDay(jd); + + if (year) + *year = pd.year; + if (month) + *month = pd.month; + if (day) + *day = pd.day; +} + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +/*! + \overload + \internal +*/ +void QDate::getDate(int *year, int *month, int *day) +{ + qAsConst(*this).getDate(year, month, day); +} +#endif // < Qt 6 + +/*! + Returns a QDate object containing a date \a ndays later than the + date of this object (or earlier if \a ndays is negative). + + Returns a null date if the current date is invalid or the new date is + out of range. + + \sa addMonths(), addYears(), daysTo() +*/ + +QDate QDate::addDays(qint64 ndays) const +{ + if (isNull()) + return QDate(); + + // Due to limits on minJd() and maxJd() we know that any overflow + // will be invalid and caught by fromJulianDay(). + return fromJulianDay(jd + ndays); +} + +/*! + Returns a QDate object containing a date \a nmonths later than the + date of this object (or earlier if \a nmonths is negative). + + \note If the ending day/month combination does not exist in the + resulting month/year, this function will return a date that is the + latest valid date. + + \sa addDays(), addYears() +*/ + +QDate QDate::addMonths(int nmonths) const +{ + if (!isValid()) + return QDate(); + if (!nmonths) + return *this; + + int old_y, y, m, d; + { + const ParsedDate pd = getDateFromJulianDay(jd); + y = pd.year; + m = pd.month; + d = pd.day; + } + old_y = y; + + bool increasing = nmonths > 0; + + while (nmonths != 0) { + if (nmonths < 0 && nmonths + 12 <= 0) { + y--; + nmonths+=12; + } else if (nmonths < 0) { + m+= nmonths; + nmonths = 0; + if (m <= 0) { + --y; + m += 12; + } + } else if (nmonths - 12 >= 0) { + y++; + nmonths -= 12; + } else if (m == 12) { + y++; + m = 0; + } else { + m += nmonths; + nmonths = 0; + if (m > 12) { + ++y; + m -= 12; + } + } + } + + // was there a sign change? + if ((old_y > 0 && y <= 0) || + (old_y < 0 && y >= 0)) + // yes, adjust the date by +1 or -1 years + y += increasing ? +1 : -1; + + return fixedDate(y, m, d); +} + +/*! + Returns a QDate object containing a date \a nyears later than the + date of this object (or earlier if \a nyears is negative). + + \note If the ending day/month combination does not exist in the + resulting year (i.e., if the date was Feb 29 and the final year is + not a leap year), this function will return a date that is the + latest valid date (that is, Feb 28). + + \sa addDays(), addMonths() +*/ + +QDate QDate::addYears(int nyears) const +{ + if (!isValid()) + return QDate(); + + ParsedDate pd = getDateFromJulianDay(jd); + + int old_y = pd.year; + pd.year += nyears; + + // was there a sign change? + if ((old_y > 0 && pd.year <= 0) || + (old_y < 0 && pd.year >= 0)) + // yes, adjust the date by +1 or -1 years + pd.year += nyears > 0 ? +1 : -1; + + return fixedDate(pd.year, pd.month, pd.day); +} + +/*! + Returns the number of days from this date to \a d (which is + negative if \a d is earlier than this date). + + Returns 0 if either date is invalid. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 0 + + \sa addDays() +*/ + +qint64 QDate::daysTo(const QDate &d) const +{ + if (isNull() || d.isNull()) + return 0; + + // Due to limits on minJd() and maxJd() we know this will never overflow + return d.jd - jd; +} + + +/*! + \fn bool QDate::operator==(const QDate &d) const + + Returns \c true if this date is equal to \a d; otherwise returns + false. + +*/ + +/*! + \fn bool QDate::operator!=(const QDate &d) const + + Returns \c true if this date is different from \a d; otherwise + returns \c false. +*/ + +/*! + \fn bool QDate::operator<(const QDate &d) const + + Returns \c true if this date is earlier than \a d; otherwise returns + false. +*/ + +/*! + \fn bool QDate::operator<=(const QDate &d) const + + Returns \c true if this date is earlier than or equal to \a d; + otherwise returns \c false. +*/ + +/*! + \fn bool QDate::operator>(const QDate &d) const + + Returns \c true if this date is later than \a d; otherwise returns + false. +*/ + +/*! + \fn bool QDate::operator>=(const QDate &d) const + + Returns \c true if this date is later than or equal to \a d; + otherwise returns \c false. +*/ + +/*! + \fn QDate::currentDate() + Returns the current date, as reported by the system clock. + + \sa QTime::currentTime(), QDateTime::currentDateTime() +*/ + +#if QT_CONFIG(datestring) +/*! + Returns the QDate represented by the \a string, using the + \a format given, or an invalid date if the string cannot be + parsed. + + Note for Qt::TextDate: It is recommended that you use the + English short month names (e.g. "Jan"). Although localized month + names can also be used, they depend on the user's locale settings. + + \sa toString(), QLocale::toDate() +*/ + +QDate QDate::fromString(const QString &string, Qt::DateFormat format) +{ + if (string.isEmpty()) + return QDate(); + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toDate(string, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toDate(string, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toDate(string, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toDate(string, QLocale::LongFormat); + case Qt::RFC2822Date: + return rfcDateImpl(string).date; + default: +#if QT_CONFIG(textdate) + case Qt::TextDate: { + QVector<QStringRef> parts = string.splitRef(QLatin1Char(' '), QString::SkipEmptyParts); + + if (parts.count() != 4) + return QDate(); + + QStringRef monthName = parts.at(1); + const int month = fromShortMonthName(monthName); + if (month == -1) { + // Month name matches neither English nor other localised name. + return QDate(); + } + + bool ok = false; + int year = parts.at(3).toInt(&ok); + if (!ok) + return QDate(); + + return QDate(year, month, parts.at(2).toInt()); + } +#endif // textdate + case Qt::ISODate: { + // Semi-strict parsing, must be long enough and have non-numeric separators + if (string.size() < 10 || string.at(4).isDigit() || string.at(7).isDigit() + || (string.size() > 10 && string.at(10).isDigit())) { + return QDate(); + } + const int year = string.midRef(0, 4).toInt(); + if (year <= 0 || year > 9999) + return QDate(); + return QDate(year, string.midRef(5, 2).toInt(), string.midRef(8, 2).toInt()); + } + } + return QDate(); +} + +/*! + Returns the QDate represented by the \a string, using the \a + format given, or an invalid date if the string cannot be parsed. + + These expressions may be used for the format: + + \table + \header \li Expression \li Output + \row \li d \li The day as a number without a leading zero (1 to 31) + \row \li dd \li The day as a number with a leading zero (01 to 31) + \row \li ddd + \li The abbreviated localized day name (e.g. 'Mon' to 'Sun'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li dddd + \li The long localized day name (e.g. 'Monday' to 'Sunday'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li M \li The month as a number without a leading zero (1 to 12) + \row \li MM \li The month as a number with a leading zero (01 to 12) + \row \li MMM + \li The abbreviated localized month name (e.g. 'Jan' to 'Dec'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li MMMM + \li The long localized month name (e.g. 'January' to 'December'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li yy \li The year as two digit number (00 to 99) + \row \li yyyy \li The year as four digit number. If the year is negative, + a minus sign is prepended in addition. + \endtable + + All other input characters will be treated as text. Any sequence + of characters that are enclosed in single quotes will also be + treated as text and will not be used as an expression. For example: + + \snippet code/src_corelib_tools_qdatetime.cpp 1 + + If the format is not satisfied, an invalid QDate is returned. The + expressions that don't expect leading zeroes (d, M) will be + greedy. This means that they will use two digits even if this + will put them outside the accepted range of values and leaves too + few digits for other sections. For example, the following format + string could have meant January 30 but the M will grab two + digits, resulting in an invalid date: + + \snippet code/src_corelib_tools_qdatetime.cpp 2 + + For any field that is not represented in the format the following + defaults are used: + + \table + \header \li Field \li Default value + \row \li Year \li 1900 + \row \li Month \li 1 + \row \li Day \li 1 + \endtable + + The following examples demonstrate the default values: + + \snippet code/src_corelib_tools_qdatetime.cpp 3 + + \sa toString(), QDateTime::fromString(), QTime::fromString(), + QLocale::toDate() +*/ + +QDate QDate::fromString(const QString &string, const QString &format) +{ + QDate date; +#if QT_CONFIG(datetimeparser) + QDateTimeParser dt(QVariant::Date, QDateTimeParser::FromString); + // dt.setDefaultLocale(QLocale::c()); ### Qt 6 + if (dt.parseFormat(format)) + dt.fromString(string, &date, 0); +#else + Q_UNUSED(string); + Q_UNUSED(format); +#endif + return date; +} +#endif // datestring + +/*! + \overload + + Returns \c true if the specified date (\a year, \a month, and \a + day) is valid; otherwise returns \c false. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 4 + + \sa isNull(), setDate() +*/ + +bool QDate::isValid(int year, int month, int day) +{ + // There is no year 0 in the Gregorian calendar. + return year && day > 0 && month > 0 && month <= 12 && + day <= (month == 2 ? isLeapYear(year) ? 29 : 28 : daysInUsualMonth(month)); +} + +/*! + \fn bool QDate::isLeapYear(int year) + + Returns \c true if the specified \a year is a leap year; otherwise + returns \c false. +*/ + +bool QDate::isLeapYear(int y) +{ + // No year 0 in Gregorian calendar, so -1, -5, -9 etc are leap years + if ( y < 1) + ++y; + + return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; +} + +/*! \fn static QDate QDate::fromJulianDay(qint64 jd) + + Converts the Julian day \a jd to a QDate. + + \sa toJulianDay() +*/ + +/*! \fn int QDate::toJulianDay() const + + Converts the date to a Julian day. + + \sa fromJulianDay() +*/ + +/***************************************************************************** + QTime member functions + *****************************************************************************/ + +/*! + \class QTime + \inmodule QtCore + \reentrant + + \brief The QTime class provides clock time functions. + + + A QTime object contains a clock time, which it can express as the numbers of + hours, minutes, seconds, and milliseconds since midnight. It provides + functions for comparing times and for manipulating a time by adding a number + of milliseconds. + + QTime uses the 24-hour clock format; it has no concept of AM/PM. + Unlike QDateTime, QTime knows nothing about time zones or + daylight-saving time (DST). + + A QTime object is typically created either by giving the number + of hours, minutes, seconds, and milliseconds explicitly, or by + using the static function currentTime(), which creates a QTime + object that contains the system's local time. Note that the + accuracy depends on the accuracy of the underlying operating + system; not all systems provide 1-millisecond accuracy. + + The hour(), minute(), second(), and msec() functions provide + access to the number of hours, minutes, seconds, and milliseconds + of the time. The same information is provided in textual format by + the toString() function. + + The addSecs() and addMSecs() functions provide the time a given + number of seconds or milliseconds later than a given time. + Correspondingly, the number of seconds or milliseconds + between two times can be found using secsTo() or msecsTo(). + + QTime provides a full set of operators to compare two QTime + objects; an earlier time is considered smaller than a later one; + if A.msecsTo(B) is positive, then A < B. + + \sa QDate, QDateTime +*/ + +/*! + \fn QTime::QTime() + + Constructs a null time object. For a null time, isNull() returns \c true and + isValid() returns \c false. If you need a zero time, use QTime(0, 0). For + the start of a day, see QDate::startOfDay(). + + \sa isNull(), isValid() +*/ + +/*! + Constructs a time with hour \a h, minute \a m, seconds \a s and + milliseconds \a ms. + + \a h must be in the range 0 to 23, \a m and \a s must be in the + range 0 to 59, and \a ms must be in the range 0 to 999. + + \sa isValid() +*/ + +QTime::QTime(int h, int m, int s, int ms) +{ + setHMS(h, m, s, ms); +} + + +/*! + \fn bool QTime::isNull() const + + Returns \c true if the time is null (i.e., the QTime object was + constructed using the default constructor); otherwise returns + false. A null time is also an invalid time. + + \sa isValid() +*/ + +/*! + Returns \c true if the time is valid; otherwise returns \c false. For example, + the time 23:30:55.746 is valid, but 24:12:30 is invalid. + + \sa isNull() +*/ + +bool QTime::isValid() const +{ + return mds > NullTime && mds < MSECS_PER_DAY; +} + + +/*! + Returns the hour part (0 to 23) of the time. + + Returns -1 if the time is invalid. + + \sa minute(), second(), msec() +*/ + +int QTime::hour() const +{ + if (!isValid()) + return -1; + + return ds() / MSECS_PER_HOUR; +} + +/*! + Returns the minute part (0 to 59) of the time. + + Returns -1 if the time is invalid. + + \sa hour(), second(), msec() +*/ + +int QTime::minute() const +{ + if (!isValid()) + return -1; + + return (ds() % MSECS_PER_HOUR) / MSECS_PER_MIN; +} + +/*! + Returns the second part (0 to 59) of the time. + + Returns -1 if the time is invalid. + + \sa hour(), minute(), msec() +*/ + +int QTime::second() const +{ + if (!isValid()) + return -1; + + return (ds() / 1000)%SECS_PER_MIN; +} + +/*! + Returns the millisecond part (0 to 999) of the time. + + Returns -1 if the time is invalid. + + \sa hour(), minute(), second() +*/ + +int QTime::msec() const +{ + if (!isValid()) + return -1; + + return ds() % 1000; +} + +#if QT_CONFIG(datestring) +/*! + \overload + + Returns the time as a string. The \a format parameter determines + the format of the string. + + If \a format is Qt::TextDate, the string format is HH:mm:ss; + e.g. 1 second before midnight would be "23:59:59". + + If \a format is Qt::ISODate, the string format corresponds to the + ISO 8601 extended specification for representations of dates, + represented by HH:mm:ss. To include milliseconds in the ISO 8601 + date, use the \a format Qt::ISODateWithMs, which corresponds to + HH:mm:ss.zzz. + + If the \a format is Qt::SystemLocaleShortDate or + Qt::SystemLocaleLongDate, the string format depends on the locale + settings of the system. Identical to calling + QLocale::system().toString(time, QLocale::ShortFormat) or + QLocale::system().toString(time, QLocale::LongFormat). + + If the \a format is Qt::DefaultLocaleShortDate or + Qt::DefaultLocaleLongDate, the string format depends on the + default application locale. This is the locale set with + QLocale::setDefault(), or the system locale if no default locale + has been set. Identical to calling + + \l {QLocale::toString()}{QLocale().toString(time, QLocale::ShortFormat)} or + \l {QLocale::toString()}{QLocale().toString(time, QLocale::LongFormat)}. + + If the \a format is Qt::RFC2822Date, the string is formatted in + an \l{RFC 2822} compatible way. An example of this formatting is + "23:59:20". + + If the time is invalid, an empty string will be returned. + + \sa fromString(), QDate::toString(), QDateTime::toString(), QLocale::toString() +*/ + +QString QTime::toString(Qt::DateFormat format) const +{ + if (!isValid()) + return QString(); + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toString(*this, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toString(*this, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toString(*this, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toString(*this, QLocale::LongFormat); + case Qt::ISODateWithMs: + return QString::asprintf("%02d:%02d:%02d.%03d", hour(), minute(), second(), msec()); + case Qt::RFC2822Date: + case Qt::ISODate: + case Qt::TextDate: + default: + return QString::asprintf("%02d:%02d:%02d", hour(), minute(), second()); + } +} + +/*! + \fn QString QTime::toString(const QString &format) const + \fn QString QTime::toString(QStringView format) const + + Returns the time as a string. The \a format parameter determines + the format of the result string. + + These expressions may be used: + + \table + \header \li Expression \li Output + \row \li h + \li the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display) + \row \li hh + \li the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display) + \row \li H + \li the hour without a leading zero (0 to 23, even with AM/PM display) + \row \li HH + \li the hour with a leading zero (00 to 23, even with AM/PM display) + \row \li m \li the minute without a leading zero (0 to 59) + \row \li mm \li the minute with a leading zero (00 to 59) + \row \li s \li the whole second, without any leading zero (0 to 59) + \row \li ss \li the whole second, with a leading zero where applicable (00 to 59) + \row \li z \li the fractional part of the second, to go after a decimal + point, without trailing zeroes (0 to 999). Thus "\c{s.z}" + reports the seconds to full available (millisecond) precision + without trailing zeroes. + \row \li zzz \li the fractional part of the second, to millisecond + precision, including trailing zeroes where applicable (000 to 999). + \row \li AP or A + \li use AM/PM display. \e A/AP will be replaced by either + QLocale::amText() or QLocale::pmText(). + \row \li ap or a + \li use am/pm display. \e a/ap will be replaced by a lower-case version of + QLocale::amText() or QLocale::pmText(). + \row \li t \li the timezone (for example "CEST") + \endtable + + Any sequence of characters enclosed in single quotes will be included + verbatim in the output string (stripped of the quotes), even if it contains + formatting characters. Two consecutive single quotes ("''") are replaced by + a single quote in the output. All other characters in the format string are + included verbatim in the output string. + + Formats without separators (e.g. "ddMM") are supported but must be used with + care, as the resulting strings aren't always reliably readable (e.g. if "dM" + produces "212" it could mean either the 2nd of December or the 21st of + February). + + Example format strings (assuming that the QTime is 14:13:09.042 and the system + locale is \c{en_US}) + + \table + \header \li Format \li Result + \row \li hh:mm:ss.zzz \li 14:13:09.042 + \row \li h:m:s ap \li 2:13:9 pm + \row \li H:m:s a \li 14:13:9 pm + \endtable + + If the time is invalid, an empty string will be returned. + If \a format is empty, the default format "hh:mm:ss" is used. + + \sa fromString(), QDate::toString(), QDateTime::toString(), QLocale::toString() +*/ +QString QTime::toString(QStringView format) const +{ + return QLocale::system().toString(*this, format); // QLocale::c() ### Qt6 +} + +#if QT_STRINGVIEW_VERSION < 2 +QString QTime::toString(const QString &format) const +{ + return toString(qToStringViewIgnoringNull(format)); +} +#endif + +#endif // datestring + +/*! + Sets the time to hour \a h, minute \a m, seconds \a s and + milliseconds \a ms. + + \a h must be in the range 0 to 23, \a m and \a s must be in the + range 0 to 59, and \a ms must be in the range 0 to 999. + Returns \c true if the set time is valid; otherwise returns \c false. + + \sa isValid() +*/ + +bool QTime::setHMS(int h, int m, int s, int ms) +{ + if (!isValid(h,m,s,ms)) { + mds = NullTime; // make this invalid + return false; + } + mds = (h*SECS_PER_HOUR + m*SECS_PER_MIN + s)*1000 + ms; + return true; +} + +/*! + Returns a QTime object containing a time \a s seconds later + than the time of this object (or earlier if \a s is negative). + + Note that the time will wrap if it passes midnight. + + Returns a null time if this time is invalid. + + Example: + + \snippet code/src_corelib_tools_qdatetime.cpp 5 + + \sa addMSecs(), secsTo(), QDateTime::addSecs() +*/ + +QTime QTime::addSecs(int s) const +{ + s %= SECS_PER_DAY; + return addMSecs(s * 1000); +} + +/*! + Returns the number of seconds from this time to \a t. + If \a t is earlier than this time, the number of seconds returned + is negative. + + Because QTime measures time within a day and there are 86400 + seconds in a day, the result is always between -86400 and 86400. + + secsTo() does not take into account any milliseconds. + + Returns 0 if either time is invalid. + + \sa addSecs(), QDateTime::secsTo() +*/ + +int QTime::secsTo(const QTime &t) const +{ + if (!isValid() || !t.isValid()) + return 0; + + // Truncate milliseconds as we do not want to consider them. + int ourSeconds = ds() / 1000; + int theirSeconds = t.ds() / 1000; + return theirSeconds - ourSeconds; +} + +/*! + Returns a QTime object containing a time \a ms milliseconds later + than the time of this object (or earlier if \a ms is negative). + + Note that the time will wrap if it passes midnight. See addSecs() + for an example. + + Returns a null time if this time is invalid. + + \sa addSecs(), msecsTo(), QDateTime::addMSecs() +*/ + +QTime QTime::addMSecs(int ms) const +{ + QTime t; + if (isValid()) { + if (ms < 0) { + // %,/ not well-defined for -ve, so always work with +ve. + int negdays = (MSECS_PER_DAY - ms) / MSECS_PER_DAY; + t.mds = (ds() + ms + negdays * MSECS_PER_DAY) % MSECS_PER_DAY; + } else { + t.mds = (ds() + ms) % MSECS_PER_DAY; + } + } + return t; +} + +/*! + Returns the number of milliseconds from this time to \a t. + If \a t is earlier than this time, the number of milliseconds returned + is negative. + + Because QTime measures time within a day and there are 86400 + seconds in a day, the result is always between -86400000 and + 86400000 ms. + + Returns 0 if either time is invalid. + + \sa secsTo(), addMSecs(), QDateTime::msecsTo() +*/ + +int QTime::msecsTo(const QTime &t) const +{ + if (!isValid() || !t.isValid()) + return 0; + return t.ds() - ds(); +} + + +/*! + \fn bool QTime::operator==(const QTime &t) const + + Returns \c true if this time is equal to \a t; otherwise returns \c false. +*/ + +/*! + \fn bool QTime::operator!=(const QTime &t) const + + Returns \c true if this time is different from \a t; otherwise returns \c false. +*/ + +/*! + \fn bool QTime::operator<(const QTime &t) const + + Returns \c true if this time is earlier than \a t; otherwise returns \c false. +*/ + +/*! + \fn bool QTime::operator<=(const QTime &t) const + + Returns \c true if this time is earlier than or equal to \a t; + otherwise returns \c false. +*/ + +/*! + \fn bool QTime::operator>(const QTime &t) const + + Returns \c true if this time is later than \a t; otherwise returns \c false. +*/ + +/*! + \fn bool QTime::operator>=(const QTime &t) const + + Returns \c true if this time is later than or equal to \a t; + otherwise returns \c false. +*/ + +/*! + \fn QTime QTime::fromMSecsSinceStartOfDay(int msecs) + + Returns a new QTime instance with the time set to the number of \a msecs + since the start of the day, i.e. since 00:00:00. + + If \a msecs falls outside the valid range an invalid QTime will be returned. + + \sa msecsSinceStartOfDay() +*/ + +/*! + \fn int QTime::msecsSinceStartOfDay() const + + Returns the number of msecs since the start of the day, i.e. since 00:00:00. + + \sa fromMSecsSinceStartOfDay() +*/ + +/*! + \fn QTime::currentTime() + + Returns the current time as reported by the system clock. + + Note that the accuracy depends on the accuracy of the underlying + operating system; not all systems provide 1-millisecond accuracy. +*/ + +#if QT_CONFIG(datestring) + +static QTime fromIsoTimeString(const QStringRef &string, Qt::DateFormat format, bool *isMidnight24) +{ + if (isMidnight24) + *isMidnight24 = false; + + const int size = string.size(); + if (size < 5) + return QTime(); + + bool ok = false; + int hour = string.mid(0, 2).toInt(&ok); + if (!ok) + return QTime(); + const int minute = string.mid(3, 2).toInt(&ok); + if (!ok) + return QTime(); + int second = 0; + int msec = 0; + + if (size == 5) { + // HH:mm format + second = 0; + msec = 0; + } else if (string.at(5) == QLatin1Char(',') || string.at(5) == QLatin1Char('.')) { + if (format == Qt::TextDate) + return QTime(); + // ISODate HH:mm.ssssss format + // We only want 5 digits worth of fraction of minute. This follows the existing + // behavior that determines how milliseconds are read; 4 millisecond digits are + // read and then rounded to 3. If we read at most 5 digits for fraction of minute, + // the maximum amount of millisecond digits it will expand to once converted to + // seconds is 4. E.g. 12:34,99999 will expand to 12:34:59.9994. The milliseconds + // will then be rounded up AND clamped to 999. + + const QStringRef minuteFractionStr = string.mid(6, 5); + const long minuteFractionInt = minuteFractionStr.toLong(&ok); + if (!ok) + return QTime(); + const float minuteFraction = double(minuteFractionInt) / (std::pow(double(10), minuteFractionStr.count())); + + const float secondWithMs = minuteFraction * 60; + const float secondNoMs = std::floor(secondWithMs); + const float secondFraction = secondWithMs - secondNoMs; + second = secondNoMs; + msec = qMin(qRound(secondFraction * 1000.0), 999); + } else { + // HH:mm:ss or HH:mm:ss.zzz + second = string.mid(6, 2).toInt(&ok); + if (!ok) + return QTime(); + if (size > 8 && (string.at(8) == QLatin1Char(',') || string.at(8) == QLatin1Char('.'))) { + const QStringRef msecStr(string.mid(9, 4)); + int msecInt = msecStr.isEmpty() ? 0 : msecStr.toInt(&ok); + if (!ok) + return QTime(); + const double secondFraction(msecInt / (std::pow(double(10), msecStr.count()))); + msec = qMin(qRound(secondFraction * 1000.0), 999); + } + } + + const bool isISODate = format == Qt::ISODate || format == Qt::ISODateWithMs; + if (isISODate && hour == 24 && minute == 0 && second == 0 && msec == 0) { + if (isMidnight24) + *isMidnight24 = true; + hour = 0; + } + + return QTime(hour, minute, second, msec); +} + +/*! + Returns the time represented in the \a string as a QTime using the + \a format given, or an invalid time if this is not possible. + + Note that fromString() uses a "C" locale encoded string to convert + milliseconds to a float value. If the default locale is not "C", + this may result in two conversion attempts (if the conversion + fails for the default locale). This should be considered an + implementation detail. + + \sa toString(), QLocale::toTime() +*/ +QTime QTime::fromString(const QString &string, Qt::DateFormat format) +{ + if (string.isEmpty()) + return QTime(); + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toTime(string, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toTime(string, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toTime(string, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toTime(string, QLocale::LongFormat); + case Qt::RFC2822Date: + return rfcDateImpl(string).time; + case Qt::ISODate: + case Qt::ISODateWithMs: + case Qt::TextDate: + default: + return fromIsoTimeString(QStringRef(&string), format, 0); + } +} + +/*! + Returns the QTime represented by the \a string, using the \a + format given, or an invalid time if the string cannot be parsed. + + These expressions may be used for the format: + + \table + \header \li Expression \li Output + \row \li h + \li the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display) + \row \li hh + \li the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display) + \row \li m \li the minute without a leading zero (0 to 59) + \row \li mm \li the minute with a leading zero (00 to 59) + \row \li s \li the whole second, without any leading zero (0 to 59) + \row \li ss \li the whole second, with a leading zero where applicable (00 to 59) + \row \li z \li the fractional part of the second, to go after a decimal + point, without trailing zeroes (0 to 999). Thus "\c{s.z}" + reports the seconds to full available (millisecond) precision + without trailing zeroes. + \row \li zzz \li the fractional part of the second, to millisecond + precision, including trailing zeroes where applicable (000 to 999). + \row \li AP + \li interpret as an AM/PM time. \e AP must be either "AM" or "PM". + \row \li ap + \li Interpret as an AM/PM time. \e ap must be either "am" or "pm". + \endtable + + All other input characters will be treated as text. Any sequence + of characters that are enclosed in single quotes will also be + treated as text and not be used as an expression. + + \snippet code/src_corelib_tools_qdatetime.cpp 6 + + If the format is not satisfied, an invalid QTime is returned. + Expressions that do not expect leading zeroes to be given (h, m, s + and z) are greedy. This means that they will use two digits even if + this puts them outside the range of accepted values and leaves too + few digits for other sections. For example, the following string + could have meant 00:07:10, but the m will grab two digits, resulting + in an invalid time: + + \snippet code/src_corelib_tools_qdatetime.cpp 7 + + Any field that is not represented in the format will be set to zero. + For example: + + \snippet code/src_corelib_tools_qdatetime.cpp 8 + + \sa toString(), QDateTime::fromString(), QDate::fromString(), + QLocale::toTime() +*/ + +QTime QTime::fromString(const QString &string, const QString &format) +{ + QTime time; +#if QT_CONFIG(datetimeparser) + QDateTimeParser dt(QVariant::Time, QDateTimeParser::FromString); + // dt.setDefaultLocale(QLocale::c()); ### Qt 6 + if (dt.parseFormat(format)) + dt.fromString(string, 0, &time); +#else + Q_UNUSED(string); + Q_UNUSED(format); +#endif + return time; +} + +#endif // datestring + + +/*! + \overload + + Returns \c true if the specified time is valid; otherwise returns + false. + + The time is valid if \a h is in the range 0 to 23, \a m and + \a s are in the range 0 to 59, and \a ms is in the range 0 to 999. + + Example: + + \snippet code/src_corelib_tools_qdatetime.cpp 9 +*/ + +bool QTime::isValid(int h, int m, int s, int ms) +{ + return (uint)h < 24 && (uint)m < 60 && (uint)s < 60 && (uint)ms < 1000; +} + +#if QT_DEPRECATED_SINCE(5, 14) // ### Qt 6: remove +/*! + Sets this time to the current time. This is practical for timing: + + \snippet code/src_corelib_tools_qdatetime.cpp 10 + + \sa restart(), elapsed(), currentTime() +*/ + +void QTime::start() +{ + *this = currentTime(); +} + +/*! + Sets this time to the current time and returns the number of + milliseconds that have elapsed since the last time start() or + restart() was called. + + This function is guaranteed to be atomic and is thus very handy + for repeated measurements. Call start() to start the first + measurement, and restart() for each later measurement. + + Note that the counter wraps to zero 24 hours after the last call + to start() or restart(). + + \warning If the system's clock setting has been changed since the + last time start() or restart() was called, the result is + undefined. This can happen when daylight-saving time is turned on + or off. + + \sa start(), elapsed(), currentTime() +*/ + +int QTime::restart() +{ + QTime t = currentTime(); + int n = msecsTo(t); + if (n < 0) // passed midnight + n += 86400*1000; + *this = t; + return n; +} + +/*! + Returns the number of milliseconds that have elapsed since the + last time start() or restart() was called. + + Note that the counter wraps to zero 24 hours after the last call + to start() or restart. + + Note that the accuracy depends on the accuracy of the underlying + operating system; not all systems provide 1-millisecond accuracy. + + \warning If the system's clock setting has been changed since the + last time start() or restart() was called, the result is + undefined. This can happen when daylight-saving time is turned on + or off. + + \sa start(), restart() +*/ + +int QTime::elapsed() const +{ + int n = msecsTo(currentTime()); + if (n < 0) // passed midnight + n += 86400 * 1000; + return n; +} +#endif // Use QElapsedTimer instead ! + +/***************************************************************************** + QDateTime static helper functions + *****************************************************************************/ + +// get the types from QDateTime (through QDateTimePrivate) +typedef QDateTimePrivate::QDateTimeShortData ShortData; +typedef QDateTimePrivate::QDateTimeData QDateTimeData; + +// Returns the platform variant of timezone, i.e. the standard time offset +// The timezone external variable is documented as always holding the +// Standard Time offset as seconds west of Greenwich, i.e. UTC+01:00 is -3600 +// Note this may not be historicaly accurate. +// Relies on tzset, mktime, or localtime having been called to populate timezone +static int qt_timezone() +{ +#if defined(_MSC_VER) + long offset; + _get_timezone(&offset); + return offset; +#elif defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN) + time_t clock = time(NULL); + struct tm t; + localtime_r(&clock, &t); + // QTBUG-36080 Workaround for systems without the POSIX timezone + // variable. This solution is not very efficient but fixing it is up to + // the libc implementations. + // + // tm_gmtoff has some important differences compared to the timezone + // variable: + // - It returns the number of seconds east of UTC, and we want the + // number of seconds west of UTC. + // - It also takes DST into account, so we need to adjust it to always + // get the Standard Time offset. + return -t.tm_gmtoff + (t.tm_isdst ? (long)SECS_PER_HOUR : 0L); +#elif defined(Q_OS_INTEGRITY) + return 0; +#else + return timezone; +#endif // Q_OS_WIN +} + +// Returns the tzname, assume tzset has been called already +static QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus) +{ + int isDst = (daylightStatus == QDateTimePrivate::DaylightTime) ? 1 : 0; +#if defined(Q_CC_MSVC) + size_t s = 0; + char name[512]; + if (_get_tzname(&s, name, 512, isDst)) + return QString(); + return QString::fromLocal8Bit(name); +#else + return QString::fromLocal8Bit(tzname[isDst]); +#endif // Q_OS_WIN +} + +#if QT_CONFIG(datetimeparser) && QT_CONFIG(timezone) +/* + \internal + Implemented here to share qt_tzname() +*/ +int QDateTimeParser::startsWithLocalTimeZone(const QStringRef name) +{ + QDateTimePrivate::DaylightStatus zones[2] = { + QDateTimePrivate::StandardTime, + QDateTimePrivate::DaylightTime + }; + for (const auto z : zones) { + QString zone(qt_tzname(z)); + if (name.startsWith(zone)) + return zone.size(); + } + return 0; +} +#endif // datetimeparser && timezone + +// Calls the platform variant of mktime for the given date, time and daylightStatus, +// and updates the date, time, daylightStatus and abbreviation with the returned values +// If the date falls outside the 1970 to 2037 range supported by mktime / time_t +// then null date/time will be returned, you should adjust the date first if +// you need a guaranteed result. +static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStatus *daylightStatus, + QString *abbreviation, bool *ok = 0) +{ + const qint64 msec = time->msec(); + int yy, mm, dd; + date->getDate(&yy, &mm, &dd); + + // All other platforms provide standard C library time functions + tm local; + memset(&local, 0, sizeof(local)); // tm_[wy]day plus any non-standard fields + local.tm_sec = time->second(); + local.tm_min = time->minute(); + local.tm_hour = time->hour(); + local.tm_mday = dd; + local.tm_mon = mm - 1; + local.tm_year = yy - 1900; + if (daylightStatus) + local.tm_isdst = int(*daylightStatus); + else + local.tm_isdst = -1; + +#if defined(Q_OS_WIN) + int hh = local.tm_hour; +#endif // Q_OS_WIN + time_t secsSinceEpoch = qMkTime(&local); + if (secsSinceEpoch != time_t(-1)) { + *date = QDate(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday); + *time = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec); +#if defined(Q_OS_WIN) + // Windows mktime for the missing hour subtracts 1 hour from the time + // instead of adding 1 hour. If time differs and is standard time then + // this has happened, so add 2 hours to the time and 1 hour to the msecs + if (local.tm_isdst == 0 && local.tm_hour != hh) { + if (time->hour() >= 22) + *date = date->addDays(1); + *time = time->addSecs(2 * SECS_PER_HOUR); + secsSinceEpoch += SECS_PER_HOUR; + local.tm_isdst = 1; + } +#endif // Q_OS_WIN + if (local.tm_isdst >= 1) { + if (daylightStatus) + *daylightStatus = QDateTimePrivate::DaylightTime; + if (abbreviation) + *abbreviation = qt_tzname(QDateTimePrivate::DaylightTime); + } else if (local.tm_isdst == 0) { + if (daylightStatus) + *daylightStatus = QDateTimePrivate::StandardTime; + if (abbreviation) + *abbreviation = qt_tzname(QDateTimePrivate::StandardTime); + } else { + if (daylightStatus) + *daylightStatus = QDateTimePrivate::UnknownDaylightTime; + if (abbreviation) + *abbreviation = qt_tzname(QDateTimePrivate::StandardTime); + } + if (ok) + *ok = true; + } else { + *date = QDate(); + *time = QTime(); + if (daylightStatus) + *daylightStatus = QDateTimePrivate::UnknownDaylightTime; + if (abbreviation) + *abbreviation = QString(); + if (ok) + *ok = false; + } + + return ((qint64)secsSinceEpoch * 1000) + msec; +} + +// Calls the platform variant of localtime for the given msecs, and updates +// the date, time, and DST status with the returned values. +static bool qt_localtime(qint64 msecsSinceEpoch, QDate *localDate, QTime *localTime, + QDateTimePrivate::DaylightStatus *daylightStatus) +{ + const time_t secsSinceEpoch = msecsSinceEpoch / 1000; + const int msec = msecsSinceEpoch % 1000; + + tm local; + bool valid = false; + + // localtime() is specified to work as if it called tzset(). + // localtime_r() does not have this constraint, so make an explicit call. + // The explicit call should also request the timezone info be re-parsed. + qTzSet(); +#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // Use the reentrant version of localtime() where available + // as is thread-safe and doesn't use a shared static data area + tm *res = 0; + res = localtime_r(&secsSinceEpoch, &local); + if (res) + valid = true; +#elif defined(Q_CC_MSVC) + if (!_localtime64_s(&local, &secsSinceEpoch)) + valid = true; +#else + // Returns shared static data which may be overwritten at any time + // So copy the result asap + tm *res = 0; + res = localtime(&secsSinceEpoch); + if (res) { + local = *res; + valid = true; + } +#endif + if (valid) { + *localDate = QDate(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday); + *localTime = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec); + if (daylightStatus) { + if (local.tm_isdst > 0) + *daylightStatus = QDateTimePrivate::DaylightTime; + else if (local.tm_isdst < 0) + *daylightStatus = QDateTimePrivate::UnknownDaylightTime; + else + *daylightStatus = QDateTimePrivate::StandardTime; + } + return true; + } else { + *localDate = QDate(); + *localTime = QTime(); + if (daylightStatus) + *daylightStatus = QDateTimePrivate::UnknownDaylightTime; + return false; + } +} + +// Converts an msecs value into a date and time +static void msecsToTime(qint64 msecs, QDate *date, QTime *time) +{ + qint64 jd = JULIAN_DAY_FOR_EPOCH; + qint64 ds = 0; + + if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) { + jd += msecs / MSECS_PER_DAY; + msecs %= MSECS_PER_DAY; + } + + if (msecs < 0) { + ds = MSECS_PER_DAY - msecs - 1; + jd -= ds / MSECS_PER_DAY; + ds = ds % MSECS_PER_DAY; + ds = MSECS_PER_DAY - ds - 1; + } else { + ds = msecs; + } + + if (date) + *date = QDate::fromJulianDay(jd); + if (time) + *time = QTime::fromMSecsSinceStartOfDay(ds); +} + +// Converts a date/time value into msecs +static qint64 timeToMSecs(const QDate &date, const QTime &time) +{ + return ((date.toJulianDay() - JULIAN_DAY_FOR_EPOCH) * MSECS_PER_DAY) + + time.msecsSinceStartOfDay(); +} + +// Convert an MSecs Since Epoch into Local Time +static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, + QDateTimePrivate::DaylightStatus *daylightStatus = 0) +{ + if (msecs < 0) { + // Docs state any LocalTime before 1970-01-01 will *not* have any Daylight Time applied + // Instead just use the standard offset from UTC to convert to UTC time + qTzSet(); + msecsToTime(msecs - qt_timezone() * 1000, localDate, localTime); + if (daylightStatus) + *daylightStatus = QDateTimePrivate::StandardTime; + return true; + } else if (msecs > (qint64(TIME_T_MAX) * 1000)) { + // Docs state any LocalTime after 2037-12-31 *will* have any DST applied + // but this may fall outside the supported time_t range, so need to fake it. + // Use existing method to fake the conversion, but this is deeply flawed as it may + // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month + // TODO Use QTimeZone when available to apply the future rule correctly + QDate utcDate; + QTime utcTime; + msecsToTime(msecs, &utcDate, &utcTime); + int year, month, day; + utcDate.getDate(&year, &month, &day); + // 2037 is not a leap year, so make sure date isn't Feb 29 + if (month == 2 && day == 29) + --day; + QDate fakeDate(2037, month, day); + qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch(); + bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus); + *localDate = localDate->addDays(fakeDate.daysTo(utcDate)); + return res; + } else { + // Falls inside time_t suported range so can use localtime + return qt_localtime(msecs, localDate, localTime, daylightStatus); + } +} + +// Convert a LocalTime expressed in local msecs encoding and the corresponding +// DST status into a UTC epoch msecs. Optionally populate the returned +// values from mktime for the adjusted local date and time. +static qint64 localMSecsToEpochMSecs(qint64 localMsecs, + QDateTimePrivate::DaylightStatus *daylightStatus, + QDate *localDate = 0, QTime *localTime = 0, + QString *abbreviation = 0) +{ + QDate dt; + QTime tm; + msecsToTime(localMsecs, &dt, &tm); + + const qint64 msecsMax = qint64(TIME_T_MAX) * 1000; + + if (localMsecs <= qint64(MSECS_PER_DAY)) { + + // Docs state any LocalTime before 1970-01-01 will *not* have any DST applied + + // First, if localMsecs is within +/- 1 day of minimum time_t try mktime in case it does + // fall after minimum and needs proper DST conversion + if (localMsecs >= -qint64(MSECS_PER_DAY)) { + bool valid; + qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid); + if (valid && utcMsecs >= 0) { + // mktime worked and falls in valid range, so use it + if (localDate) + *localDate = dt; + if (localTime) + *localTime = tm; + return utcMsecs; + } + } else { + // If we don't call mktime then need to call tzset to get offset + qTzSet(); + } + // Time is clearly before 1970-01-01 so just use standard offset to convert + qint64 utcMsecs = localMsecs + qt_timezone() * 1000; + if (localDate || localTime) + msecsToTime(localMsecs, localDate, localTime); + if (daylightStatus) + *daylightStatus = QDateTimePrivate::StandardTime; + if (abbreviation) + *abbreviation = qt_tzname(QDateTimePrivate::StandardTime); + return utcMsecs; + + } else if (localMsecs >= msecsMax - MSECS_PER_DAY) { + + // Docs state any LocalTime after 2037-12-31 *will* have any DST applied + // but this may fall outside the supported time_t range, so need to fake it. + + // First, if localMsecs is within +/- 1 day of maximum time_t try mktime in case it does + // fall before maximum and can use proper DST conversion + if (localMsecs <= msecsMax + MSECS_PER_DAY) { + bool valid; + qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid); + if (valid && utcMsecs <= msecsMax) { + // mktime worked and falls in valid range, so use it + if (localDate) + *localDate = dt; + if (localTime) + *localTime = tm; + return utcMsecs; + } + } + // Use existing method to fake the conversion, but this is deeply flawed as it may + // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month + // TODO Use QTimeZone when available to apply the future rule correctly + int year, month, day; + dt.getDate(&year, &month, &day); + // 2037 is not a leap year, so make sure date isn't Feb 29 + if (month == 2 && day == 29) + --day; + QDate fakeDate(2037, month, day); + qint64 fakeDiff = fakeDate.daysTo(dt); + qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation); + if (localDate) + *localDate = fakeDate.addDays(fakeDiff); + if (localTime) + *localTime = tm; + QDate utcDate; + QTime utcTime; + msecsToTime(utcMsecs, &utcDate, &utcTime); + utcDate = utcDate.addDays(fakeDiff); + utcMsecs = timeToMSecs(utcDate, utcTime); + return utcMsecs; + + } else { + + // Clearly falls inside 1970-2037 suported range so can use mktime + qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation); + if (localDate) + *localDate = dt; + if (localTime) + *localTime = tm; + return utcMsecs; + + } +} + +static inline bool specCanBeSmall(Qt::TimeSpec spec) +{ + return spec == Qt::LocalTime || spec == Qt::UTC; +} + +static inline bool msecsCanBeSmall(qint64 msecs) +{ + if (!QDateTimeData::CanBeSmall) + return false; + + ShortData sd; + sd.msecs = qintptr(msecs); + return sd.msecs == msecs; +} + +static Q_DECL_CONSTEXPR inline +QDateTimePrivate::StatusFlags mergeSpec(QDateTimePrivate::StatusFlags status, Qt::TimeSpec spec) +{ + return QDateTimePrivate::StatusFlags((status & ~QDateTimePrivate::TimeSpecMask) | + (int(spec) << QDateTimePrivate::TimeSpecShift)); +} + +static Q_DECL_CONSTEXPR inline Qt::TimeSpec extractSpec(QDateTimePrivate::StatusFlags status) +{ + return Qt::TimeSpec((status & QDateTimePrivate::TimeSpecMask) >> QDateTimePrivate::TimeSpecShift); +} + +// Set the Daylight Status if LocalTime set via msecs +static Q_DECL_RELAXED_CONSTEXPR inline QDateTimePrivate::StatusFlags +mergeDaylightStatus(QDateTimePrivate::StatusFlags sf, QDateTimePrivate::DaylightStatus status) +{ + sf &= ~QDateTimePrivate::DaylightMask; + if (status == QDateTimePrivate::DaylightTime) { + sf |= QDateTimePrivate::SetToDaylightTime; + } else if (status == QDateTimePrivate::StandardTime) { + sf |= QDateTimePrivate::SetToStandardTime; + } + return sf; +} + +// Get the DST Status if LocalTime set via msecs +static Q_DECL_RELAXED_CONSTEXPR inline +QDateTimePrivate::DaylightStatus extractDaylightStatus(QDateTimePrivate::StatusFlags status) +{ + if (status & QDateTimePrivate::SetToDaylightTime) + return QDateTimePrivate::DaylightTime; + if (status & QDateTimePrivate::SetToStandardTime) + return QDateTimePrivate::StandardTime; + return QDateTimePrivate::UnknownDaylightTime; +} + +static inline qint64 getMSecs(const QDateTimeData &d) +{ + if (d.isShort()) { + // same as, but producing better code + //return d.data.msecs; + return qintptr(d.d) >> 8; + } + return d->m_msecs; +} + +static inline QDateTimePrivate::StatusFlags getStatus(const QDateTimeData &d) +{ + if (d.isShort()) { + // same as, but producing better code + //return StatusFlag(d.data.status); + return QDateTimePrivate::StatusFlag(qintptr(d.d) & 0xFF); + } + return d->m_status; +} + +static inline Qt::TimeSpec getSpec(const QDateTimeData &d) +{ + return extractSpec(getStatus(d)); +} + +#if QT_CONFIG(timezone) +void QDateTimePrivate::setUtcOffsetByTZ(qint64 atMSecsSinceEpoch) +{ + m_offsetFromUtc = m_timeZone.d->offsetFromUtc(atMSecsSinceEpoch); +} +#endif + +// Refresh the LocalTime validity and offset +static void refreshDateTime(QDateTimeData &d) +{ + auto status = getStatus(d); + const auto spec = extractSpec(status); + const qint64 msecs = getMSecs(d); + qint64 epochMSecs = 0; + int offsetFromUtc = 0; + QDate testDate; + QTime testTime; + Q_ASSERT(spec == Qt::TimeZone || spec == Qt::LocalTime); + +#if QT_CONFIG(timezone) + // If not valid time zone then is invalid + if (spec == Qt::TimeZone) { + if (!d->m_timeZone.isValid()) { + status &= ~QDateTimePrivate::ValidDateTime; + } else { + epochMSecs = QDateTimePrivate::zoneMSecsToEpochMSecs(msecs, d->m_timeZone, extractDaylightStatus(status), &testDate, &testTime); + d->setUtcOffsetByTZ(epochMSecs); + } + } +#endif // timezone + + // If not valid date and time then is invalid + if (!(status & QDateTimePrivate::ValidDate) || !(status & QDateTimePrivate::ValidTime)) { + status &= ~QDateTimePrivate::ValidDateTime; + if (status & QDateTimePrivate::ShortData) { + d.data.status = status; + } else { + d->m_status = status; + d->m_offsetFromUtc = 0; + } + return; + } + + // We have a valid date and time and a Qt::LocalTime or Qt::TimeZone that needs calculating + // LocalTime and TimeZone might fall into a "missing" DST transition hour + // Calling toEpochMSecs will adjust the returned date/time if it does + if (spec == Qt::LocalTime) { + auto dstStatus = extractDaylightStatus(status); + epochMSecs = localMSecsToEpochMSecs(msecs, &dstStatus, &testDate, &testTime); + } + if (timeToMSecs(testDate, testTime) == msecs) { + status |= QDateTimePrivate::ValidDateTime; + // Cache the offset to use in offsetFromUtc() + offsetFromUtc = (msecs - epochMSecs) / 1000; + } else { + status &= ~QDateTimePrivate::ValidDateTime; + } + + if (status & QDateTimePrivate::ShortData) { + d.data.status = status; + } else { + d->m_status = status; + d->m_offsetFromUtc = offsetFromUtc; + } +} + +// Check the UTC / offsetFromUTC validity +static void checkValidDateTime(QDateTimeData &d) +{ + auto status = getStatus(d); + auto spec = extractSpec(status); + switch (spec) { + case Qt::OffsetFromUTC: + case Qt::UTC: + // for these, a valid date and a valid time imply a valid QDateTime + if ((status & QDateTimePrivate::ValidDate) && (status & QDateTimePrivate::ValidTime)) + status |= QDateTimePrivate::ValidDateTime; + else + status &= ~QDateTimePrivate::ValidDateTime; + if (status & QDateTimePrivate::ShortData) + d.data.status = status; + else + d->m_status = status; + break; + case Qt::TimeZone: + case Qt::LocalTime: + // for these, we need to check whether the timezone is valid and whether + // the time is valid in that timezone. Expensive, but no other option. + refreshDateTime(d); + break; + } +} + +static void setTimeSpec(QDateTimeData &d, Qt::TimeSpec spec, int offsetSeconds) +{ + auto status = getStatus(d); + status &= ~(QDateTimePrivate::ValidDateTime | QDateTimePrivate::DaylightMask | + QDateTimePrivate::TimeSpecMask); + + switch (spec) { + case Qt::OffsetFromUTC: + if (offsetSeconds == 0) + spec = Qt::UTC; + break; + case Qt::TimeZone: + // Use system time zone instead + spec = Qt::LocalTime; + Q_FALLTHROUGH(); + case Qt::UTC: + case Qt::LocalTime: + offsetSeconds = 0; + break; + } + + status = mergeSpec(status, spec); + if (d.isShort() && offsetSeconds == 0) { + d.data.status = status; + } else { + d.detach(); + d->m_status = status & ~QDateTimePrivate::ShortData; + d->m_offsetFromUtc = offsetSeconds; +#if QT_CONFIG(timezone) + d->m_timeZone = QTimeZone(); +#endif // timezone + } +} + +static void setDateTime(QDateTimeData &d, const QDate &date, const QTime &time) +{ + // If the date is valid and the time is not we set time to 00:00:00 + QTime useTime = time; + if (!useTime.isValid() && date.isValid()) + useTime = QTime::fromMSecsSinceStartOfDay(0); + + QDateTimePrivate::StatusFlags newStatus = 0; + + // Set date value and status + qint64 days = 0; + if (date.isValid()) { + days = date.toJulianDay() - JULIAN_DAY_FOR_EPOCH; + newStatus = QDateTimePrivate::ValidDate; + } + + // Set time value and status + int ds = 0; + if (useTime.isValid()) { + ds = useTime.msecsSinceStartOfDay(); + newStatus |= QDateTimePrivate::ValidTime; + } + + // Set msecs serial value + qint64 msecs = (days * MSECS_PER_DAY) + ds; + if (d.isShort()) { + // let's see if we can keep this short + if (msecsCanBeSmall(msecs)) { + // yes, we can + d.data.msecs = qintptr(msecs); + d.data.status &= ~(QDateTimePrivate::ValidityMask | QDateTimePrivate::DaylightMask); + d.data.status |= newStatus; + } else { + // nope... + d.detach(); + } + } + if (!d.isShort()) { + d.detach(); + d->m_msecs = msecs; + d->m_status &= ~(QDateTimePrivate::ValidityMask | QDateTimePrivate::DaylightMask); + d->m_status |= newStatus; + } + + // Set if date and time are valid + checkValidDateTime(d); +} + +static QPair<QDate, QTime> getDateTime(const QDateTimeData &d) +{ + QPair<QDate, QTime> result; + qint64 msecs = getMSecs(d); + auto status = getStatus(d); + msecsToTime(msecs, &result.first, &result.second); + + if (!status.testFlag(QDateTimePrivate::ValidDate)) + result.first = QDate(); + + if (!status.testFlag(QDateTimePrivate::ValidTime)) + result.second = QTime(); + + return result; +} + +/***************************************************************************** + QDateTime::Data member functions + *****************************************************************************/ + +inline QDateTime::Data::Data() +{ + // default-constructed data has a special exception: + // it can be small even if CanBeSmall == false + // (optimization so we don't allocate memory in the default constructor) + quintptr value = quintptr(mergeSpec(QDateTimePrivate::ShortData, Qt::LocalTime)); + d = reinterpret_cast<QDateTimePrivate *>(value); +} + +inline QDateTime::Data::Data(Qt::TimeSpec spec) +{ + if (CanBeSmall && Q_LIKELY(specCanBeSmall(spec))) { + d = reinterpret_cast<QDateTimePrivate *>(quintptr(mergeSpec(QDateTimePrivate::ShortData, spec))); + } else { + // the structure is too small, we need to detach + d = new QDateTimePrivate; + d->ref.ref(); + d->m_status = mergeSpec(0, spec); + } +} + +inline QDateTime::Data::Data(const Data &other) + : d(other.d) +{ + if (!isShort()) { + // check if we could shrink + if (specCanBeSmall(extractSpec(d->m_status)) && msecsCanBeSmall(d->m_msecs)) { + ShortData sd; + sd.msecs = qintptr(d->m_msecs); + sd.status = d->m_status | QDateTimePrivate::ShortData; + data = sd; + } else { + // no, have to keep it big + d->ref.ref(); + } + } +} + +inline QDateTime::Data::Data(Data &&other) + : d(other.d) +{ + // reset the other to a short state + Data dummy; + Q_ASSERT(dummy.isShort()); + other.d = dummy.d; +} + +inline QDateTime::Data &QDateTime::Data::operator=(const Data &other) +{ + if (d == other.d) + return *this; + + auto x = d; + d = other.d; + if (!other.isShort()) { + // check if we could shrink + if (specCanBeSmall(extractSpec(other.d->m_status)) && msecsCanBeSmall(other.d->m_msecs)) { + ShortData sd; + sd.msecs = qintptr(other.d->m_msecs); + sd.status = other.d->m_status | QDateTimePrivate::ShortData; + data = sd; + } else { + // no, have to keep it big + other.d->ref.ref(); + } + } + + if (!(quintptr(x) & QDateTimePrivate::ShortData) && !x->ref.deref()) + delete x; + return *this; +} + +inline QDateTime::Data::~Data() +{ + if (!isShort() && !d->ref.deref()) + delete d; +} + +inline bool QDateTime::Data::isShort() const +{ + bool b = quintptr(d) & QDateTimePrivate::ShortData; + + // sanity check: + Q_ASSERT(b || (d->m_status & QDateTimePrivate::ShortData) == 0); + + // even if CanBeSmall = false, we have short data for a default-constructed + // QDateTime object. But it's unlikely. + if (CanBeSmall) + return Q_LIKELY(b); + return Q_UNLIKELY(b); +} + +inline void QDateTime::Data::detach() +{ + QDateTimePrivate *x; + bool wasShort = isShort(); + if (wasShort) { + // force enlarging + x = new QDateTimePrivate; + x->m_status = QDateTimePrivate::StatusFlag(data.status & ~QDateTimePrivate::ShortData); + x->m_msecs = data.msecs; + } else { + if (d->ref.load() == 1) + return; + + x = new QDateTimePrivate(*d); + } + + x->ref.store(1); + if (!wasShort && !d->ref.deref()) + delete d; + d = x; +} + +inline const QDateTimePrivate *QDateTime::Data::operator->() const +{ + Q_ASSERT(!isShort()); + return d; +} + +inline QDateTimePrivate *QDateTime::Data::operator->() +{ + // should we attempt to detach here? + Q_ASSERT(!isShort()); + Q_ASSERT(d->ref.load() == 1); + return d; +} + +/***************************************************************************** + QDateTimePrivate member functions + *****************************************************************************/ + +Q_NEVER_INLINE +QDateTime::Data QDateTimePrivate::create(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec, + int offsetSeconds) +{ + QDateTime::Data result(toSpec); + setTimeSpec(result, toSpec, offsetSeconds); + setDateTime(result, toDate, toTime); + return result; +} + +#if QT_CONFIG(timezone) +inline QDateTime::Data QDateTimePrivate::create(const QDate &toDate, const QTime &toTime, + const QTimeZone &toTimeZone) +{ + QDateTime::Data result(Qt::TimeZone); + Q_ASSERT(!result.isShort()); + + result.d->m_status = mergeSpec(result.d->m_status, Qt::TimeZone); + result.d->m_timeZone = toTimeZone; + setDateTime(result, toDate, toTime); + return result; +} + +// Convert a TimeZone time expressed in zone msecs encoding into a UTC epoch msecs +// DST transitions are disambiguated by hint. +inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone &zone, + DaylightStatus hint, + QDate *zoneDate, QTime *zoneTime) +{ + // Get the effective data from QTimeZone + QTimeZonePrivate::Data data = zone.d->dataForLocalTime(zoneMSecs, int(hint)); + // Docs state any time before 1970-01-01 will *not* have any DST applied + // but all affected times afterwards will have DST applied. + if (data.atMSecsSinceEpoch < 0) { + msecsToTime(zoneMSecs, zoneDate, zoneTime); + return zoneMSecs - data.standardTimeOffset * 1000; + } else { + msecsToTime(data.atMSecsSinceEpoch + data.offsetFromUtc * 1000, zoneDate, zoneTime); + return data.atMSecsSinceEpoch; + } +} +#endif // timezone + +/***************************************************************************** + QDateTime member functions + *****************************************************************************/ + +/*! + \class QDateTime + \inmodule QtCore + \ingroup shared + \reentrant + \brief The QDateTime class provides date and time functions. + + + A QDateTime object encodes a calendar date and a clock time (a + "datetime"). It combines features of the QDate and QTime classes. + It can read the current datetime from the system clock. It + provides functions for comparing datetimes and for manipulating a + datetime by adding a number of seconds, days, months, or years. + + A QDateTime object is typically created either by giving a date + and time explicitly in the constructor, or by using the static + function currentDateTime() that returns a QDateTime object set + to the system clock's time. The date and time can be changed with + setDate() and setTime(). A datetime can also be set using the + setTime_t() function that takes a POSIX-standard "number of + seconds since 00:00:00 on January 1, 1970" value. The fromString() + function returns a QDateTime, given a string and a date format + used to interpret the date within the string. + + The date() and time() functions provide access to the date and + time parts of the datetime. The same information is provided in + textual format by the toString() function. + + QDateTime provides a full set of operators to compare two + QDateTime objects, where smaller means earlier and larger means + later. + + You can increment (or decrement) a datetime by a given number of + milliseconds using addMSecs(), seconds using addSecs(), or days + using addDays(). Similarly, you can use addMonths() and addYears(). + The daysTo() function returns the number of days between two datetimes, + secsTo() returns the number of seconds between two datetimes, and + msecsTo() returns the number of milliseconds between two datetimes. + + QDateTime can store datetimes as \l{Qt::LocalTime}{local time} or + as \l{Qt::UTC}{UTC}. QDateTime::currentDateTime() returns a + QDateTime expressed as local time; use toUTC() to convert it to + UTC. You can also use timeSpec() to find out if a QDateTime + object stores a UTC time or a local time. Operations such as + addSecs() and secsTo() are aware of daylight-saving time (DST). + + \note QDateTime does not account for leap seconds. + + \section1 Remarks + + \section2 No Year 0 + + There is no year 0. Dates in that year are considered invalid. The + year -1 is the year "1 before Christ" or "1 before current era." + The day before 1 January 1 CE is 31 December 1 BCE. + + \section2 Range of Valid Dates + + The range of valid values able to be stored in QDateTime is dependent on + the internal storage implementation. QDateTime is currently stored in a + qint64 as a serial msecs value encoding the date and time. This restricts + the date range to about +/- 292 million years, compared to the QDate range + of +/- 2 billion years. Care must be taken when creating a QDateTime with + extreme values that you do not overflow the storage. The exact range of + supported values varies depending on the Qt::TimeSpec and time zone. + + \section2 Use of System Timezone + + QDateTime uses the system's time zone information to determine the + offset of local time from UTC. If the system is not configured + correctly or not up-to-date, QDateTime will give wrong results as + well. + + \section2 Daylight-Saving Time (DST) + + QDateTime takes into account the system's time zone information + when dealing with DST. On modern Unix systems, this means it + applies the correct historical DST data whenever possible. On + Windows, where the system doesn't support historical DST data, + historical accuracy is not maintained with respect to DST. + + The range of valid dates taking DST into account is 1970-01-01 to + the present, and rules are in place for handling DST correctly + until 2037-12-31, but these could change. For dates falling + outside that range, QDateTime makes a \e{best guess} using the + rules for year 1970 or 2037, but we can't guarantee accuracy. This + means QDateTime doesn't take into account changes in a locale's + time zone before 1970, even if the system's time zone database + supports that information. + + QDateTime takes into consideration the Standard Time to Daylight-Saving Time + transition. For example if the transition is at 2am and the clock goes + forward to 3am, then there is a "missing" hour from 02:00:00 to 02:59:59.999 + which QDateTime considers to be invalid. Any date maths performed + will take this missing hour into account and return a valid result. + + \section2 Offset From UTC + + A Qt::TimeSpec of Qt::OffsetFromUTC is also supported. This allows you + to define a QDateTime relative to UTC at a fixed offset of a given number + of seconds from UTC. For example, an offset of +3600 seconds is one hour + ahead of UTC and is usually written in ISO standard notation as + "UTC+01:00". Daylight-Saving Time never applies with this TimeSpec. + + There is no explicit size restriction to the offset seconds, but there is + an implicit limit imposed when using the toString() and fromString() + methods which use a format of [+|-]hh:mm, effectively limiting the range + to +/- 99 hours and 59 minutes and whole minutes only. Note that currently + no time zone lies outside the range of +/- 14 hours. + + \section2 Time Zone Support + + A Qt::TimeSpec of Qt::TimeZone is also supported in conjunction with the + QTimeZone class. This allows you to define a datetime in a named time zone + adhering to a consistent set of daylight-saving transition rules. For + example a time zone of "Europe/Berlin" will apply the daylight-saving + rules as used in Germany since 1970. Note that the transition rules + applied depend on the platform support. See the QTimeZone documentation + for more details. + + \sa QDate, QTime, QDateTimeEdit, QTimeZone +*/ + +/*! + Constructs a null datetime (i.e. null date and null time). A null + datetime is invalid, since the date is invalid. + + \sa isValid() +*/ +QDateTime::QDateTime() noexcept(Data::CanBeSmall) +{ +} + + +/*! + Constructs a datetime with the given \a date, a valid + time(00:00:00.000), and sets the timeSpec() to Qt::LocalTime. +*/ + +QDateTime::QDateTime(const QDate &date) + : d(QDateTimePrivate::create(date, QTime(0, 0), Qt::LocalTime, 0)) +{ +} + +/*! + Constructs a datetime with the given \a date and \a time, using + the time specification defined by \a spec. + + If \a date is valid and \a time is not, the time will be set to midnight. + + If \a spec is Qt::OffsetFromUTC then it will be set to Qt::UTC, i.e. an + offset of 0 seconds. To create a Qt::OffsetFromUTC datetime use the + correct constructor. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. To create a Qt::TimeZone datetime + use the correct constructor. +*/ + +QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec) + : d(QDateTimePrivate::create(date, time, spec, 0)) +{ +} + +/*! + \since 5.2 + + Constructs a datetime with the given \a date and \a time, using + the time specification defined by \a spec and \a offsetSeconds seconds. + + If \a date is valid and \a time is not, the time will be set to midnight. + + If the \a spec is not Qt::OffsetFromUTC then \a offsetSeconds will be ignored. + + If the \a spec is Qt::OffsetFromUTC and \a offsetSeconds is 0 then the + timeSpec() will be set to Qt::UTC, i.e. an offset of 0 seconds. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. To create a Qt::TimeZone datetime + use the correct constructor. +*/ + +QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, int offsetSeconds) + : d(QDateTimePrivate::create(date, time, spec, offsetSeconds)) +{ +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + + Constructs a datetime with the given \a date and \a time, using + the Time Zone specified by \a timeZone. + + If \a date is valid and \a time is not, the time will be set to 00:00:00. + + If \a timeZone is invalid then the datetime will be invalid. +*/ + +QDateTime::QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone) + : d(QDateTimePrivate::create(date, time, timeZone)) +{ +} +#endif // timezone + +/*! + Constructs a copy of the \a other datetime. +*/ +QDateTime::QDateTime(const QDateTime &other) noexcept + : d(other.d) +{ +} + +/*! + \since 5.8 + Moves the content of the temporary \a other datetime to this object and + leaves \a other in an unspecified (but proper) state. +*/ +QDateTime::QDateTime(QDateTime &&other) noexcept + : d(std::move(other.d)) +{ +} + +/*! + Destroys the datetime. +*/ +QDateTime::~QDateTime() +{ +} + +/*! + Makes a copy of the \a other datetime and returns a reference to the + copy. +*/ + +QDateTime &QDateTime::operator=(const QDateTime &other) noexcept +{ + d = other.d; + return *this; +} +/*! + \fn void QDateTime::swap(QDateTime &other) + \since 5.0 + + Swaps this datetime with \a other. This operation is very fast + and never fails. +*/ + +/*! + Returns \c true if both the date and the time are null; otherwise + returns \c false. A null datetime is invalid. + + \sa QDate::isNull(), QTime::isNull(), isValid() +*/ + +bool QDateTime::isNull() const +{ + auto status = getStatus(d); + return !status.testFlag(QDateTimePrivate::ValidDate) && + !status.testFlag(QDateTimePrivate::ValidTime); +} + +/*! + Returns \c true if both the date and the time are valid and they are valid in + the current Qt::TimeSpec, otherwise returns \c false. + + If the timeSpec() is Qt::LocalTime or Qt::TimeZone then the date and time are + checked to see if they fall in the Standard Time to Daylight-Saving Time transition + hour, i.e. if the transition is at 2am and the clock goes forward to 3am + then the time from 02:00:00 to 02:59:59.999 is considered to be invalid. + + \sa QDate::isValid(), QTime::isValid() +*/ + +bool QDateTime::isValid() const +{ + auto status = getStatus(d); + return status & QDateTimePrivate::ValidDateTime; +} + +/*! + Returns the date part of the datetime. + + \sa setDate(), time(), timeSpec() +*/ + +QDate QDateTime::date() const +{ + auto status = getStatus(d); + if (!status.testFlag(QDateTimePrivate::ValidDate)) + return QDate(); + QDate dt; + msecsToTime(getMSecs(d), &dt, 0); + return dt; +} + +/*! + Returns the time part of the datetime. + + \sa setTime(), date(), timeSpec() +*/ + +QTime QDateTime::time() const +{ + auto status = getStatus(d); + if (!status.testFlag(QDateTimePrivate::ValidTime)) + return QTime(); + QTime tm; + msecsToTime(getMSecs(d), 0, &tm); + return tm; +} + +/*! + Returns the time specification of the datetime. + + \sa setTimeSpec(), date(), time(), Qt::TimeSpec +*/ + +Qt::TimeSpec QDateTime::timeSpec() const +{ + return getSpec(d); +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + + Returns the time zone of the datetime. + + If the timeSpec() is Qt::LocalTime then an instance of the current system + time zone will be returned. Note however that if you copy this time zone + the instance will not remain in sync if the system time zone changes. + + \sa setTimeZone(), Qt::TimeSpec +*/ + +QTimeZone QDateTime::timeZone() const +{ + switch (getSpec(d)) { + case Qt::UTC: + return QTimeZone::utc(); + case Qt::OffsetFromUTC: + return QTimeZone(d->m_offsetFromUtc); + case Qt::TimeZone: + Q_ASSERT(d->m_timeZone.isValid()); + return d->m_timeZone; + case Qt::LocalTime: + return QTimeZone::systemTimeZone(); + } + return QTimeZone(); +} +#endif // timezone + +/*! + \since 5.2 + + Returns the current Offset From UTC in seconds. + + If the timeSpec() is Qt::OffsetFromUTC this will be the value originally set. + + If the timeSpec() is Qt::TimeZone this will be the offset effective in the + Time Zone including any Daylight-Saving Offset. + + If the timeSpec() is Qt::LocalTime this will be the difference between the + Local Time and UTC including any Daylight-Saving Offset. + + If the timeSpec() is Qt::UTC this will be 0. + + \sa setOffsetFromUtc() +*/ + +int QDateTime::offsetFromUtc() const +{ + if (!d.isShort()) + return d->m_offsetFromUtc; + if (!isValid()) + return 0; + + auto spec = getSpec(d); + if (spec == Qt::LocalTime) { + // we didn't cache the value, so we need to calculate it now... + qint64 msecs = getMSecs(d); + return (msecs - toMSecsSinceEpoch()) / 1000; + } + + Q_ASSERT(spec == Qt::UTC); + return 0; +} + +/*! + \since 5.2 + + Returns the Time Zone Abbreviation for the datetime. + + If the timeSpec() is Qt::UTC this will be "UTC". + + If the timeSpec() is Qt::OffsetFromUTC this will be in the format + "UTC[+-]00:00". + + If the timeSpec() is Qt::LocalTime then the host system is queried for the + correct abbreviation. + + Note that abbreviations may or may not be localized. + + Note too that the abbreviation is not guaranteed to be a unique value, + i.e. different time zones may have the same abbreviation. + + \sa timeSpec() +*/ + +QString QDateTime::timeZoneAbbreviation() const +{ + switch (getSpec(d)) { + case Qt::UTC: + return QLatin1String("UTC"); + case Qt::OffsetFromUTC: + return QLatin1String("UTC") + toOffsetString(Qt::ISODate, d->m_offsetFromUtc); + case Qt::TimeZone: +#if !QT_CONFIG(timezone) + break; +#else + return d->m_timeZone.d->abbreviation(toMSecsSinceEpoch()); +#endif // timezone + case Qt::LocalTime: { + QString abbrev; + auto status = extractDaylightStatus(getStatus(d)); + localMSecsToEpochMSecs(getMSecs(d), &status, 0, 0, &abbrev); + return abbrev; + } + } + return QString(); +} + +/*! + \since 5.2 + + Returns if this datetime falls in Daylight-Saving Time. + + If the Qt::TimeSpec is not Qt::LocalTime or Qt::TimeZone then will always + return false. + + \sa timeSpec() +*/ + +bool QDateTime::isDaylightTime() const +{ + switch (getSpec(d)) { + case Qt::UTC: + case Qt::OffsetFromUTC: + return false; + case Qt::TimeZone: +#if !QT_CONFIG(timezone) + break; +#else + return d->m_timeZone.d->isDaylightTime(toMSecsSinceEpoch()); +#endif // timezone + case Qt::LocalTime: { + auto status = extractDaylightStatus(getStatus(d)); + if (status == QDateTimePrivate::UnknownDaylightTime) + localMSecsToEpochMSecs(getMSecs(d), &status); + return (status == QDateTimePrivate::DaylightTime); + } + } + return false; +} + +/*! + Sets the date part of this datetime to \a date. If no time is set yet, it + is set to midnight. If \a date is invalid, this QDateTime becomes invalid. + + \sa date(), setTime(), setTimeSpec() +*/ + +void QDateTime::setDate(const QDate &date) +{ + setDateTime(d, date, time()); +} + +/*! + Sets the time part of this datetime to \a time. If \a time is not valid, + this function sets it to midnight. Therefore, it's possible to clear any + set time in a QDateTime by setting it to a default QTime: + + \code + QDateTime dt = QDateTime::currentDateTime(); + dt.setTime(QTime()); + \endcode + + \sa time(), setDate(), setTimeSpec() +*/ + +void QDateTime::setTime(const QTime &time) +{ + setDateTime(d, date(), time); +} + +/*! + Sets the time specification used in this datetime to \a spec. + The datetime will refer to a different point in time. + + If \a spec is Qt::OffsetFromUTC then the timeSpec() will be set + to Qt::UTC, i.e. an effective offset of 0. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 19 + + \sa timeSpec(), setDate(), setTime(), setTimeZone(), Qt::TimeSpec +*/ + +void QDateTime::setTimeSpec(Qt::TimeSpec spec) +{ + QT_PREPEND_NAMESPACE(setTimeSpec(d, spec, 0)); + checkValidDateTime(d); +} + +/*! + \since 5.2 + + Sets the timeSpec() to Qt::OffsetFromUTC and the offset to \a offsetSeconds. + The datetime will refer to a different point in time. + + The maximum and minimum offset is 14 positive or negative hours. If + \a offsetSeconds is larger or smaller than that, then the result is + undefined. + + If \a offsetSeconds is 0 then the timeSpec() will be set to Qt::UTC. + + \sa isValid(), offsetFromUtc() +*/ + +void QDateTime::setOffsetFromUtc(int offsetSeconds) +{ + QT_PREPEND_NAMESPACE(setTimeSpec(d, Qt::OffsetFromUTC, offsetSeconds)); + checkValidDateTime(d); +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + + Sets the time zone used in this datetime to \a toZone. + The datetime will refer to a different point in time. + + If \a toZone is invalid then the datetime will be invalid. + + \sa timeZone(), Qt::TimeSpec +*/ + +void QDateTime::setTimeZone(const QTimeZone &toZone) +{ + d.detach(); // always detach + d->m_status = mergeSpec(d->m_status, Qt::TimeZone); + d->m_offsetFromUtc = 0; + d->m_timeZone = toZone; + refreshDateTime(d); +} +#endif // timezone + +/*! + \since 4.7 + + Returns the datetime as the number of milliseconds that have passed + since 1970-01-01T00:00:00.000, Coordinated Universal Time (Qt::UTC). + + On systems that do not support time zones, this function will + behave as if local time were Qt::UTC. + + The behavior for this function is undefined if the datetime stored in + this object is not valid. However, for all valid dates, this function + returns a unique value. + + \sa toSecsSinceEpoch(), setMSecsSinceEpoch() +*/ +qint64 QDateTime::toMSecsSinceEpoch() const +{ + switch (getSpec(d)) { + case Qt::UTC: + return getMSecs(d); + + case Qt::OffsetFromUTC: + return d->m_msecs - (d->m_offsetFromUtc * 1000); + + case Qt::LocalTime: { + // recalculate the local timezone + auto status = extractDaylightStatus(getStatus(d)); + return localMSecsToEpochMSecs(getMSecs(d), &status); + } + + case Qt::TimeZone: +#if !QT_CONFIG(timezone) + return 0; +#else + return QDateTimePrivate::zoneMSecsToEpochMSecs(d->m_msecs, d->m_timeZone, + extractDaylightStatus(getStatus(d))); +#endif + } + Q_UNREACHABLE(); + return 0; +} + +/*! + \since 5.8 + + Returns the datetime as the number of seconds that have passed since + 1970-01-01T00:00:00.000, Coordinated Universal Time (Qt::UTC). + + On systems that do not support time zones, this function will + behave as if local time were Qt::UTC. + + The behavior for this function is undefined if the datetime stored in + this object is not valid. However, for all valid dates, this function + returns a unique value. + + \sa toMSecsSinceEpoch(), setSecsSinceEpoch() +*/ +qint64 QDateTime::toSecsSinceEpoch() const +{ + return toMSecsSinceEpoch() / 1000; +} + +#if QT_DEPRECATED_SINCE(5, 8) +/*! + \deprecated + + Returns the datetime as the number of seconds that have passed + since 1970-01-01T00:00:00, Coordinated Universal Time (Qt::UTC). + + On systems that do not support time zones, this function will + behave as if local time were Qt::UTC. + + \note This function returns a 32-bit unsigned integer and is deprecated. + + If the date is outside the range 1970-01-01T00:00:00 to + 2106-02-07T06:28:14, this function returns -1 cast to an unsigned integer + (i.e., 0xFFFFFFFF). + + To get an extended range, use toMSecsSinceEpoch() or toSecsSinceEpoch(). + + \sa toSecsSinceEpoch(), toMSecsSinceEpoch(), setTime_t() +*/ + +uint QDateTime::toTime_t() const +{ + if (!isValid()) + return uint(-1); + qint64 retval = toMSecsSinceEpoch() / 1000; + if (quint64(retval) >= Q_UINT64_C(0xFFFFFFFF)) + return uint(-1); + return uint(retval); +} +#endif + +/*! + \since 4.7 + + Sets the date and time given the number of milliseconds \a msecs that have + passed since 1970-01-01T00:00:00.000, Coordinated Universal Time + (Qt::UTC). On systems that do not support time zones this function + will behave as if local time were Qt::UTC. + + Note that passing the minimum of \c qint64 + (\c{std::numeric_limits<qint64>::min()}) to \a msecs will result in + undefined behavior. + + \sa toMSecsSinceEpoch(), setSecsSinceEpoch() +*/ +void QDateTime::setMSecsSinceEpoch(qint64 msecs) +{ + const auto spec = getSpec(d); + auto status = getStatus(d); + + status &= ~QDateTimePrivate::ValidityMask; + switch (spec) { + case Qt::UTC: + status = status + | QDateTimePrivate::ValidDate + | QDateTimePrivate::ValidTime + | QDateTimePrivate::ValidDateTime; + break; + case Qt::OffsetFromUTC: + msecs = msecs + (d->m_offsetFromUtc * 1000); + status = status + | QDateTimePrivate::ValidDate + | QDateTimePrivate::ValidTime + | QDateTimePrivate::ValidDateTime; + break; + case Qt::TimeZone: + Q_ASSERT(!d.isShort()); +#if QT_CONFIG(timezone) + // Docs state any LocalTime before 1970-01-01 will *not* have any DST applied + // but all affected times afterwards will have DST applied. + d.detach(); + if (msecs >= 0) { + status = mergeDaylightStatus(status, + d->m_timeZone.d->isDaylightTime(msecs) + ? QDateTimePrivate::DaylightTime + : QDateTimePrivate::StandardTime); + d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs); + } else { + status = mergeDaylightStatus(status, QDateTimePrivate::StandardTime); + d->m_offsetFromUtc = d->m_timeZone.d->standardTimeOffset(msecs); + } + msecs = msecs + (d->m_offsetFromUtc * 1000); + status = status + | QDateTimePrivate::ValidDate + | QDateTimePrivate::ValidTime + | QDateTimePrivate::ValidDateTime; +#endif // timezone + break; + case Qt::LocalTime: { + QDate dt; + QTime tm; + QDateTimePrivate::DaylightStatus dstStatus; + epochMSecsToLocalTime(msecs, &dt, &tm, &dstStatus); + setDateTime(d, dt, tm); + msecs = getMSecs(d); + status = mergeDaylightStatus(getStatus(d), dstStatus); + break; + } + } + + if (msecsCanBeSmall(msecs) && d.isShort()) { + // we can keep short + d.data.msecs = qintptr(msecs); + d.data.status = status; + } else { + d.detach(); + d->m_status = status & ~QDateTimePrivate::ShortData; + d->m_msecs = msecs; + } + + if (spec == Qt::LocalTime || spec == Qt::TimeZone) + refreshDateTime(d); +} + +/*! + \since 5.8 + + Sets the date and time given the number of seconds \a secs that have + passed since 1970-01-01T00:00:00.000, Coordinated Universal Time + (Qt::UTC). On systems that do not support time zones this function + will behave as if local time were Qt::UTC. + + \sa toSecsSinceEpoch(), setMSecsSinceEpoch() +*/ +void QDateTime::setSecsSinceEpoch(qint64 secs) +{ + setMSecsSinceEpoch(secs * 1000); +} + +#if QT_DEPRECATED_SINCE(5, 8) +/*! + \fn void QDateTime::setTime_t(uint seconds) + \deprecated + + Sets the date and time given the number of \a seconds that have + passed since 1970-01-01T00:00:00, Coordinated Universal Time + (Qt::UTC). On systems that do not support time zones this function + will behave as if local time were Qt::UTC. + + \note This function is deprecated. For new code, use setSecsSinceEpoch(). + + \sa toTime_t() +*/ + +void QDateTime::setTime_t(uint secsSince1Jan1970UTC) +{ + setMSecsSinceEpoch((qint64)secsSince1Jan1970UTC * 1000); +} +#endif + +#if QT_CONFIG(datestring) +/*! + \fn QString QDateTime::toString(Qt::DateFormat format) const + + \overload + + Returns the datetime as a string in the \a format given. + + If the \a format is Qt::TextDate, the string is formatted in + the default way. QDate::shortDayName(), QDate::shortMonthName(), + and QTime::toString() are used to generate the string, so the + day and month names will be localized names using the system locale, + i.e. QLocale::system(). An example of this formatting is + "Wed May 20 03:40:13 1998". + + If the \a format is Qt::ISODate, the string format corresponds + to the ISO 8601 extended specification for representations of + dates and times, taking the form yyyy-MM-ddTHH:mm:ss[Z|[+|-]HH:mm], + depending on the timeSpec() of the QDateTime. If the timeSpec() + is Qt::UTC, Z will be appended to the string; if the timeSpec() is + Qt::OffsetFromUTC, the offset in hours and minutes from UTC will + be appended to the string. To include milliseconds in the ISO 8601 + date, use the \a format Qt::ISODateWithMs, which corresponds to + yyyy-MM-ddTHH:mm:ss.zzz[Z|[+|-]HH:mm]. + + If the \a format is Qt::SystemLocaleShortDate or + Qt::SystemLocaleLongDate, the string format depends on the locale + settings of the system. Identical to calling + QLocale::system().toString(datetime, QLocale::ShortFormat) or + QLocale::system().toString(datetime, QLocale::LongFormat). + + If the \a format is Qt::DefaultLocaleShortDate or + Qt::DefaultLocaleLongDate, the string format depends on the + default application locale. This is the locale set with + QLocale::setDefault(), or the system locale if no default locale + has been set. Identical to calling QLocale().toString(datetime, + QLocale::ShortFormat) or QLocale().toString(datetime, + QLocale::LongFormat). + + If the \a format is Qt::RFC2822Date, the string is formatted + following \l{RFC 2822}. + + If the datetime is invalid, an empty string will be returned. + + \warning The Qt::ISODate format is only valid for years in the + range 0 to 9999. This restriction may apply to locale-aware + formats as well, depending on the locale settings. + + \sa fromString(), QDate::toString(), QTime::toString(), + QLocale::toString() +*/ + +QString QDateTime::toString(Qt::DateFormat format) const +{ + QString buf; + if (!isValid()) + return buf; + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toString(*this, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toString(*this, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toString(*this, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toString(*this, QLocale::LongFormat); + case Qt::RFC2822Date: { + buf = QLocale::c().toString(*this, QStringViewLiteral("dd MMM yyyy hh:mm:ss ")); + buf += toOffsetString(Qt::TextDate, offsetFromUtc()); + return buf; + } + default: +#if QT_CONFIG(textdate) + case Qt::TextDate: { + const QPair<QDate, QTime> p = getDateTime(d); + buf = p.first.toString(Qt::TextDate); + // Insert time between date's day and year: + buf.insert(buf.lastIndexOf(QLatin1Char(' ')), + QLatin1Char(' ') + p.second.toString(Qt::TextDate)); + // Append zone/offset indicator, as appropriate: + switch (timeSpec()) { + case Qt::LocalTime: + break; +# if QT_CONFIG(timezone) + case Qt::TimeZone: + buf += QLatin1Char(' ') + d->m_timeZone.abbreviation(*this); + break; +# endif + default: + buf += QLatin1String(" GMT"); + if (getSpec(d) == Qt::OffsetFromUTC) + buf += toOffsetString(Qt::TextDate, offsetFromUtc()); + } + return buf; + } +#endif + case Qt::ISODate: + case Qt::ISODateWithMs: { + const QPair<QDate, QTime> p = getDateTime(d); + const QDate &dt = p.first; + const QTime &tm = p.second; + buf = dt.toString(Qt::ISODate); + if (buf.isEmpty()) + return QString(); // failed to convert + buf += QLatin1Char('T'); + buf += tm.toString(format); + switch (getSpec(d)) { + case Qt::UTC: + buf += QLatin1Char('Z'); + break; + case Qt::OffsetFromUTC: +#if QT_CONFIG(timezone) + case Qt::TimeZone: +#endif + buf += toOffsetString(Qt::ISODate, offsetFromUtc()); + break; + default: + break; + } + return buf; + } + } +} + +/*! + \fn QString QDateTime::toString(const QString &format) const + \fn QString QDateTime::toString(QStringView format) const + + Returns the datetime as a string. The \a format parameter + determines the format of the result string. + + These expressions may be used for the date: + + \table + \header \li Expression \li Output + \row \li d \li the day as number without a leading zero (1 to 31) + \row \li dd \li the day as number with a leading zero (01 to 31) + \row \li ddd + \li the abbreviated localized day name (e.g. 'Mon' to 'Sun'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li dddd + \li the long localized day name (e.g. 'Monday' to 'Sunday'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li M \li the month as number without a leading zero (1-12) + \row \li MM \li the month as number with a leading zero (01-12) + \row \li MMM + \li the abbreviated localized month name (e.g. 'Jan' to 'Dec'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li MMMM + \li the long localized month name (e.g. 'January' to 'December'). + Uses the system locale to localize the name, i.e. QLocale::system(). + \row \li yy \li the year as two digit number (00-99) + \row \li yyyy \li the year as four digit number + \endtable + + These expressions may be used for the time: + + \table + \header \li Expression \li Output + \row \li h + \li the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display) + \row \li hh + \li the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display) + \row \li H + \li the hour without a leading zero (0 to 23, even with AM/PM display) + \row \li HH + \li the hour with a leading zero (00 to 23, even with AM/PM display) + \row \li m \li the minute without a leading zero (0 to 59) + \row \li mm \li the minute with a leading zero (00 to 59) + \row \li s \li the whole second without a leading zero (0 to 59) + \row \li ss \li the whole second with a leading zero where applicable (00 to 59) + \row \li z \li the fractional part of the second, to go after a decimal + point, without trailing zeroes (0 to 999). Thus "\c{s.z}" + reports the seconds to full available (millisecond) precision + without trailing zeroes. + \row \li zzz \li the fractional part of the second, to millisecond + precision, including trailing zeroes where applicable (000 to 999). + \row \li AP or A + \li use AM/PM display. \e A/AP will be replaced by either "AM" or "PM". + \row \li ap or a + \li use am/pm display. \e a/ap will be replaced by either "am" or "pm". + \row \li t \li the timezone (for example "CEST") + \endtable + + Any sequence of characters enclosed in single quotes will be included + verbatim in the output string (stripped of the quotes), even if it contains + formatting characters. Two consecutive single quotes ("''") are replaced by + a single quote in the output. All other characters in the format string are + included verbatim in the output string. + + Formats without separators (e.g. "ddMM") are supported but must be used with + care, as the resulting strings aren't always reliably readable (e.g. if "dM" + produces "212" it could mean either the 2nd of December or the 21st of + February). + + Example format strings (assumed that the QDateTime is 21 May 2001 + 14:13:09.120): + + \table + \header \li Format \li Result + \row \li dd.MM.yyyy \li 21.05.2001 + \row \li ddd MMMM d yy \li Tue May 21 01 + \row \li hh:mm:ss.zzz \li 14:13:09.120 + \row \li hh:mm:ss.z \li 14:13:09.12 + \row \li h:m:s ap \li 2:13:9 pm + \endtable + + If the datetime is invalid, an empty string will be returned. + + \sa fromString(), QDate::toString(), QTime::toString(), QLocale::toString() +*/ +QString QDateTime::toString(QStringView format) const +{ + return QLocale::system().toString(*this, format); // QLocale::c() ### Qt6 +} + +#if QT_STRINGVIEW_LEVEL < 2 +QString QDateTime::toString(const QString &format) const +{ + return toString(qToStringViewIgnoringNull(format)); +} +#endif + +#endif // datestring + +static inline void massageAdjustedDateTime(const QDateTimeData &d, QDate *date, QTime *time) +{ + /* + If we have just adjusted to a day with a DST transition, our given time + may lie in the transition hour (either missing or duplicated). For any + other time, telling mktime (deep in the bowels of localMSecsToEpochMSecs) + we don't know its DST-ness will produce no adjustment (just a decision as + to its DST-ness); but for a time in spring's missing hour it'll adjust the + time while picking a DST-ness. (Handling of autumn is trickier, as either + DST-ness is valid, without adjusting the time. We might want to propagate + the daylight status in that case, but it's hard to do so without breaking + (far more common) other cases; and it makes little difference, as the two + answers do then differ only in DST-ness.) + */ + auto spec = getSpec(d); + if (spec == Qt::LocalTime) { + QDateTimePrivate::DaylightStatus status = QDateTimePrivate::UnknownDaylightTime; + localMSecsToEpochMSecs(timeToMSecs(*date, *time), &status, date, time); +#if QT_CONFIG(timezone) + } else if (spec == Qt::TimeZone) { + QDateTimePrivate::zoneMSecsToEpochMSecs(timeToMSecs(*date, *time), + d->m_timeZone, + QDateTimePrivate::UnknownDaylightTime, + date, time); +#endif // timezone + } +} + +/*! + Returns a QDateTime object containing a datetime \a ndays days + later than the datetime of this object (or earlier if \a ndays is + negative). + + If the timeSpec() is Qt::LocalTime and the resulting + date and time fall in the Standard Time to Daylight-Saving Time transition + hour then the result will be adjusted accordingly, i.e. if the transition + is at 2am and the clock goes forward to 3am and the result falls between + 2am and 3am then the result will be adjusted to fall after 3am. + + \sa daysTo(), addMonths(), addYears(), addSecs() +*/ + +QDateTime QDateTime::addDays(qint64 ndays) const +{ + QDateTime dt(*this); + QPair<QDate, QTime> p = getDateTime(d); + QDate &date = p.first; + QTime &time = p.second; + date = date.addDays(ndays); + massageAdjustedDateTime(dt.d, &date, &time); + setDateTime(dt.d, date, time); + return dt; +} + +/*! + Returns a QDateTime object containing a datetime \a nmonths months + later than the datetime of this object (or earlier if \a nmonths + is negative). + + If the timeSpec() is Qt::LocalTime and the resulting + date and time fall in the Standard Time to Daylight-Saving Time transition + hour then the result will be adjusted accordingly, i.e. if the transition + is at 2am and the clock goes forward to 3am and the result falls between + 2am and 3am then the result will be adjusted to fall after 3am. + + \sa daysTo(), addDays(), addYears(), addSecs() +*/ + +QDateTime QDateTime::addMonths(int nmonths) const +{ + QDateTime dt(*this); + QPair<QDate, QTime> p = getDateTime(d); + QDate &date = p.first; + QTime &time = p.second; + date = date.addMonths(nmonths); + massageAdjustedDateTime(dt.d, &date, &time); + setDateTime(dt.d, date, time); + return dt; +} + +/*! + Returns a QDateTime object containing a datetime \a nyears years + later than the datetime of this object (or earlier if \a nyears is + negative). + + If the timeSpec() is Qt::LocalTime and the resulting + date and time fall in the Standard Time to Daylight-Saving Time transition + hour then the result will be adjusted accordingly, i.e. if the transition + is at 2am and the clock goes forward to 3am and the result falls between + 2am and 3am then the result will be adjusted to fall after 3am. + + \sa daysTo(), addDays(), addMonths(), addSecs() +*/ + +QDateTime QDateTime::addYears(int nyears) const +{ + QDateTime dt(*this); + QPair<QDate, QTime> p = getDateTime(d); + QDate &date = p.first; + QTime &time = p.second; + date = date.addYears(nyears); + massageAdjustedDateTime(dt.d, &date, &time); + setDateTime(dt.d, date, time); + return dt; +} + +/*! + Returns a QDateTime object containing a datetime \a s seconds + later than the datetime of this object (or earlier if \a s is + negative). + + If this datetime is invalid, an invalid datetime will be returned. + + \sa addMSecs(), secsTo(), addDays(), addMonths(), addYears() +*/ + +QDateTime QDateTime::addSecs(qint64 s) const +{ + return addMSecs(s * 1000); +} + +/*! + Returns a QDateTime object containing a datetime \a msecs miliseconds + later than the datetime of this object (or earlier if \a msecs is + negative). + + If this datetime is invalid, an invalid datetime will be returned. + + \sa addSecs(), msecsTo(), addDays(), addMonths(), addYears() +*/ +QDateTime QDateTime::addMSecs(qint64 msecs) const +{ + if (!isValid()) + return QDateTime(); + + QDateTime dt(*this); + auto spec = getSpec(d); + if (spec == Qt::LocalTime || spec == Qt::TimeZone) { + // Convert to real UTC first in case crosses DST transition + dt.setMSecsSinceEpoch(toMSecsSinceEpoch() + msecs); + } else { + // No need to convert, just add on + if (d.isShort()) { + // need to check if we need to enlarge first + msecs += dt.d.data.msecs; + if (msecsCanBeSmall(msecs)) { + dt.d.data.msecs = qintptr(msecs); + } else { + dt.d.detach(); + dt.d->m_msecs = msecs; + } + } else { + dt.d.detach(); + dt.d->m_msecs += msecs; + } + } + return dt; +} + +/*! + Returns the number of days from this datetime to the \a other + datetime. The number of days is counted as the number of times + midnight is reached between this datetime to the \a other + datetime. This means that a 10 minute difference from 23:55 to + 0:05 the next day counts as one day. + + If the \a other datetime is earlier than this datetime, + the value returned is negative. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 15 + + \sa addDays(), secsTo(), msecsTo() +*/ + +qint64 QDateTime::daysTo(const QDateTime &other) const +{ + return date().daysTo(other.date()); +} + +/*! + Returns the number of seconds from this datetime to the \a other + datetime. If the \a other datetime is earlier than this datetime, + the value returned is negative. + + Before performing the comparison, the two datetimes are converted + to Qt::UTC to ensure that the result is correct if daylight-saving + (DST) applies to one of the two datetimes but not the other. + + Returns 0 if either datetime is invalid. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 11 + + \sa addSecs(), daysTo(), QTime::secsTo() +*/ + +qint64 QDateTime::secsTo(const QDateTime &other) const +{ + return (msecsTo(other) / 1000); +} + +/*! + Returns the number of milliseconds from this datetime to the \a other + datetime. If the \a other datetime is earlier than this datetime, + the value returned is negative. + + Before performing the comparison, the two datetimes are converted + to Qt::UTC to ensure that the result is correct if daylight-saving + (DST) applies to one of the two datetimes and but not the other. + + Returns 0 if either datetime is invalid. + + \sa addMSecs(), daysTo(), QTime::msecsTo() +*/ + +qint64 QDateTime::msecsTo(const QDateTime &other) const +{ + if (!isValid() || !other.isValid()) + return 0; + + return other.toMSecsSinceEpoch() - toMSecsSinceEpoch(); +} + +/*! + \fn QDateTime QDateTime::toTimeSpec(Qt::TimeSpec spec) const + + Returns a copy of this datetime converted to the given time + \a spec. + + If \a spec is Qt::OffsetFromUTC then it is set to Qt::UTC. To set to a + spec of Qt::OffsetFromUTC use toOffsetFromUtc(). + + If \a spec is Qt::TimeZone then it is set to Qt::LocalTime, + i.e. the local Time Zone. + + Example: + \snippet code/src_corelib_tools_qdatetime.cpp 16 + + \sa timeSpec(), toTimeZone(), toUTC(), toLocalTime() +*/ + +QDateTime QDateTime::toTimeSpec(Qt::TimeSpec spec) const +{ + if (getSpec(d) == spec && (spec == Qt::UTC || spec == Qt::LocalTime)) + return *this; + + if (!isValid()) { + QDateTime ret = *this; + ret.setTimeSpec(spec); + return ret; + } + + return fromMSecsSinceEpoch(toMSecsSinceEpoch(), spec, 0); +} + +/*! + \since 5.2 + + \fn QDateTime QDateTime::toOffsetFromUtc(int offsetSeconds) const + + Returns a copy of this datetime converted to a spec of Qt::OffsetFromUTC + with the given \a offsetSeconds. + + If the \a offsetSeconds equals 0 then a UTC datetime will be returned + + \sa setOffsetFromUtc(), offsetFromUtc(), toTimeSpec() +*/ + +QDateTime QDateTime::toOffsetFromUtc(int offsetSeconds) const +{ + if (getSpec(d) == Qt::OffsetFromUTC + && d->m_offsetFromUtc == offsetSeconds) + return *this; + + if (!isValid()) { + QDateTime ret = *this; + ret.setOffsetFromUtc(offsetSeconds); + return ret; + } + + return fromMSecsSinceEpoch(toMSecsSinceEpoch(), Qt::OffsetFromUTC, offsetSeconds); +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + + Returns a copy of this datetime converted to the given \a timeZone + + \sa timeZone(), toTimeSpec() +*/ + +QDateTime QDateTime::toTimeZone(const QTimeZone &timeZone) const +{ + if (getSpec(d) == Qt::TimeZone && d->m_timeZone == timeZone) + return *this; + + if (!isValid()) { + QDateTime ret = *this; + ret.setTimeZone(timeZone); + return ret; + } + + return fromMSecsSinceEpoch(toMSecsSinceEpoch(), timeZone); +} +#endif // timezone + +/*! + Returns \c true if this datetime is equal to the \a other datetime; + otherwise returns \c false. + + \sa operator!=() +*/ + +bool QDateTime::operator==(const QDateTime &other) const +{ + if (getSpec(d) == Qt::LocalTime + && getStatus(d) == getStatus(other.d)) { + return getMSecs(d) == getMSecs(other.d); + } + // Convert to UTC and compare + return (toMSecsSinceEpoch() == other.toMSecsSinceEpoch()); +} + +/*! + \fn bool QDateTime::operator!=(const QDateTime &other) const + + Returns \c true if this datetime is different from the \a other + datetime; otherwise returns \c false. + + Two datetimes are different if either the date, the time, or the + time zone components are different. + + \sa operator==() +*/ + +/*! + Returns \c true if this datetime is earlier than the \a other + datetime; otherwise returns \c false. +*/ + +bool QDateTime::operator<(const QDateTime &other) const +{ + if (getSpec(d) == Qt::LocalTime + && getStatus(d) == getStatus(other.d)) { + return getMSecs(d) < getMSecs(other.d); + } + // Convert to UTC and compare + return (toMSecsSinceEpoch() < other.toMSecsSinceEpoch()); +} + +/*! + \fn bool QDateTime::operator<=(const QDateTime &other) const + + Returns \c true if this datetime is earlier than or equal to the + \a other datetime; otherwise returns \c false. +*/ + +/*! + \fn bool QDateTime::operator>(const QDateTime &other) const + + Returns \c true if this datetime is later than the \a other datetime; + otherwise returns \c false. +*/ + +/*! + \fn bool QDateTime::operator>=(const QDateTime &other) const + + Returns \c true if this datetime is later than or equal to the + \a other datetime; otherwise returns \c false. +*/ + +/*! + \fn QDateTime QDateTime::currentDateTime() + Returns the current datetime, as reported by the system clock, in + the local time zone. + + \sa currentDateTimeUtc(), QDate::currentDate(), QTime::currentTime(), toTimeSpec() +*/ + +/*! + \fn QDateTime QDateTime::currentDateTimeUtc() + \since 4.7 + Returns the current datetime, as reported by the system clock, in + UTC. + + \sa currentDateTime(), QDate::currentDate(), QTime::currentTime(), toTimeSpec() +*/ + +/*! + \fn qint64 QDateTime::currentMSecsSinceEpoch() + \since 4.7 + + Returns the number of milliseconds since 1970-01-01T00:00:00 Universal + Coordinated Time. This number is like the POSIX time_t variable, but + expressed in milliseconds instead. + + \sa currentDateTime(), currentDateTimeUtc(), toTime_t(), toTimeSpec() +*/ + +/*! + \fn qint64 QDateTime::currentSecsSinceEpoch() + \since 5.8 + + Returns the number of seconds since 1970-01-01T00:00:00 Universal + Coordinated Time. + + \sa currentMSecsSinceEpoch() +*/ + +#if defined(Q_OS_WIN) +static inline uint msecsFromDecomposed(int hour, int minute, int sec, int msec = 0) +{ + return MSECS_PER_HOUR * hour + MSECS_PER_MIN * minute + 1000 * sec + msec; +} + +QDate QDate::currentDate() +{ + QDate d; + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetLocalTime(&st); + d.jd = julianDayFromDate(st.wYear, st.wMonth, st.wDay); + return d; +} + +QTime QTime::currentTime() +{ + QTime ct; + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetLocalTime(&st); + ct.setHMS(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + return ct; +} + +QDateTime QDateTime::currentDateTime() +{ + QDate d; + QTime t; + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetLocalTime(&st); + d.jd = julianDayFromDate(st.wYear, st.wMonth, st.wDay); + t.mds = msecsFromDecomposed(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + return QDateTime(d, t); +} + +QDateTime QDateTime::currentDateTimeUtc() +{ + QDate d; + QTime t; + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetSystemTime(&st); + d.jd = julianDayFromDate(st.wYear, st.wMonth, st.wDay); + t.mds = msecsFromDecomposed(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + return QDateTime(d, t, Qt::UTC); +} + +qint64 QDateTime::currentMSecsSinceEpoch() noexcept +{ + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetSystemTime(&st); + + return msecsFromDecomposed(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds) + + qint64(julianDayFromDate(st.wYear, st.wMonth, st.wDay) + - julianDayFromDate(1970, 1, 1)) * Q_INT64_C(86400000); +} + +qint64 QDateTime::currentSecsSinceEpoch() noexcept +{ + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + GetSystemTime(&st); + + return st.wHour * SECS_PER_HOUR + st.wMinute * SECS_PER_MIN + st.wSecond + + qint64(julianDayFromDate(st.wYear, st.wMonth, st.wDay) + - julianDayFromDate(1970, 1, 1)) * Q_INT64_C(86400); +} + +#elif defined(Q_OS_UNIX) +QDate QDate::currentDate() +{ + return QDateTime::currentDateTime().date(); +} + +QTime QTime::currentTime() +{ + return QDateTime::currentDateTime().time(); +} + +QDateTime QDateTime::currentDateTime() +{ + return fromMSecsSinceEpoch(currentMSecsSinceEpoch(), Qt::LocalTime); +} + +QDateTime QDateTime::currentDateTimeUtc() +{ + return fromMSecsSinceEpoch(currentMSecsSinceEpoch(), Qt::UTC); +} + +qint64 QDateTime::currentMSecsSinceEpoch() noexcept +{ + // posix compliant system + // we have milliseconds + struct timeval tv; + gettimeofday(&tv, 0); + return qint64(tv.tv_sec) * Q_INT64_C(1000) + tv.tv_usec / 1000; +} + +qint64 QDateTime::currentSecsSinceEpoch() noexcept +{ + struct timeval tv; + gettimeofday(&tv, 0); + return qint64(tv.tv_sec); +} +#else +#error "What system is this?" +#endif + +#if QT_DEPRECATED_SINCE(5, 8) +/*! + \since 4.2 + \deprecated + + Returns a datetime whose date and time are the number of \a seconds + that have passed since 1970-01-01T00:00:00, Coordinated Universal + Time (Qt::UTC) and converted to Qt::LocalTime. On systems that do not + support time zones, the time will be set as if local time were Qt::UTC. + + \note This function is deprecated. Please use fromSecsSinceEpoch() in new + code. + + \sa toTime_t(), setTime_t() +*/ +QDateTime QDateTime::fromTime_t(uint seconds) +{ + return fromMSecsSinceEpoch((qint64)seconds * 1000, Qt::LocalTime); +} + +/*! + \since 5.2 + \deprecated + + Returns a datetime whose date and time are the number of \a seconds + that have passed since 1970-01-01T00:00:00, Coordinated Universal + Time (Qt::UTC) and converted to the given \a spec. + + If the \a spec is not Qt::OffsetFromUTC then the \a offsetSeconds will be + ignored. If the \a spec is Qt::OffsetFromUTC and the \a offsetSeconds is 0 + then the spec will be set to Qt::UTC, i.e. an offset of 0 seconds. + + \note This function is deprecated. Please use fromSecsSinceEpoch() in new + code. + + \sa toTime_t(), setTime_t() +*/ +QDateTime QDateTime::fromTime_t(uint seconds, Qt::TimeSpec spec, int offsetSeconds) +{ + return fromMSecsSinceEpoch((qint64)seconds * 1000, spec, offsetSeconds); +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + \deprecated + + Returns a datetime whose date and time are the number of \a seconds + that have passed since 1970-01-01T00:00:00, Coordinated Universal + Time (Qt::UTC) and with the given \a timeZone. + + \note This function is deprecated. Please use fromSecsSinceEpoch() in new + code. + + \sa toTime_t(), setTime_t() +*/ +QDateTime QDateTime::fromTime_t(uint seconds, const QTimeZone &timeZone) +{ + return fromMSecsSinceEpoch((qint64)seconds * 1000, timeZone); +} +#endif +#endif // QT_DEPRECATED_SINCE(5, 8) + +/*! + \since 4.7 + + Returns a datetime whose date and time are the number of milliseconds, \a msecs, + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC), and converted to Qt::LocalTime. On systems that do not + support time zones, the time will be set as if local time were Qt::UTC. + + Note that there are possible values for \a msecs that lie outside the valid + range of QDateTime, both negative and positive. The behavior of this + function is undefined for those values. + + \sa toMSecsSinceEpoch(), setMSecsSinceEpoch() +*/ +QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs) +{ + return fromMSecsSinceEpoch(msecs, Qt::LocalTime); +} + +/*! + \since 5.2 + + Returns a datetime whose date and time are the number of milliseconds \a msecs + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC) and converted to the given \a spec. + + Note that there are possible values for \a msecs that lie outside the valid + range of QDateTime, both negative and positive. The behavior of this + function is undefined for those values. + + If the \a spec is not Qt::OffsetFromUTC then the \a offsetSeconds will be + ignored. If the \a spec is Qt::OffsetFromUTC and the \a offsetSeconds is 0 + then the spec will be set to Qt::UTC, i.e. an offset of 0 seconds. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. + + \sa toMSecsSinceEpoch(), setMSecsSinceEpoch() +*/ +QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int offsetSeconds) +{ + QDateTime dt; + QT_PREPEND_NAMESPACE(setTimeSpec(dt.d, spec, offsetSeconds)); + dt.setMSecsSinceEpoch(msecs); + return dt; +} + +/*! + \since 5.8 + + Returns a datetime whose date and time are the number of seconds \a secs + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC) and converted to the given \a spec. + + Note that there are possible values for \a secs that lie outside the valid + range of QDateTime, both negative and positive. The behavior of this + function is undefined for those values. + + If the \a spec is not Qt::OffsetFromUTC then the \a offsetSeconds will be + ignored. If the \a spec is Qt::OffsetFromUTC and the \a offsetSeconds is 0 + then the spec will be set to Qt::UTC, i.e. an offset of 0 seconds. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. + + \sa toSecsSinceEpoch(), setSecsSinceEpoch() +*/ +QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds) +{ + return fromMSecsSinceEpoch(secs * 1000, spec, offsetSeconds); +} + +#if QT_CONFIG(timezone) +/*! + \since 5.2 + + Returns a datetime whose date and time are the number of milliseconds \a msecs + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC) and with the given \a timeZone. + + \sa fromSecsSinceEpoch() +*/ +QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone) +{ + QDateTime dt; + dt.setTimeZone(timeZone); + dt.setMSecsSinceEpoch(msecs); + return dt; +} + +/*! + \since 5.8 + + Returns a datetime whose date and time are the number of seconds \a secs + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC) and with the given \a timeZone. + + \sa fromMSecsSinceEpoch() +*/ +QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone) +{ + return fromMSecsSinceEpoch(secs * 1000, timeZone); +} +#endif + +#if QT_DEPRECATED_SINCE(5, 2) +/*! + \since 4.4 + \internal + \obsolete + + This method was added in 4.4 but never documented as public. It was replaced + in 5.2 with public method setOffsetFromUtc() for consistency with QTimeZone. + + This method should never be made public. + + \sa setOffsetFromUtc() + */ +void QDateTime::setUtcOffset(int seconds) +{ + setOffsetFromUtc(seconds); +} + +/*! + \since 4.4 + \internal + \obsolete + + This method was added in 4.4 but never documented as public. It was replaced + in 5.1 with public method offsetFromUTC() for consistency with QTimeZone. + + This method should never be made public. + + \sa offsetFromUTC() +*/ +int QDateTime::utcOffset() const +{ + return offsetFromUtc(); +} +#endif // QT_DEPRECATED_SINCE + +#if QT_CONFIG(datestring) + +/*! + Returns the QDateTime represented by the \a string, using the + \a format given, or an invalid datetime if this is not possible. + + Note for Qt::TextDate: It is recommended that you use the + English short month names (e.g. "Jan"). Although localized month + names can also be used, they depend on the user's locale settings. + + \sa toString(), QLocale::toDateTime() +*/ +QDateTime QDateTime::fromString(const QString &string, Qt::DateFormat format) +{ + if (string.isEmpty()) + return QDateTime(); + + switch (format) { + case Qt::SystemLocaleDate: + case Qt::SystemLocaleShortDate: + return QLocale::system().toDateTime(string, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toDateTime(string, QLocale::LongFormat); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toDateTime(string, QLocale::ShortFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toDateTime(string, QLocale::LongFormat); + case Qt::RFC2822Date: { + const ParsedRfcDateTime rfc = rfcDateImpl(string); + + if (!rfc.date.isValid() || !rfc.time.isValid()) + return QDateTime(); + + QDateTime dateTime(rfc.date, rfc.time, Qt::UTC); + dateTime.setOffsetFromUtc(rfc.utcOffset); + return dateTime; + } + case Qt::ISODate: + case Qt::ISODateWithMs: { + const int size = string.size(); + if (size < 10) + return QDateTime(); + + QDate date = QDate::fromString(string.left(10), Qt::ISODate); + if (!date.isValid()) + return QDateTime(); + if (size == 10) + return QDateTime(date); + + Qt::TimeSpec spec = Qt::LocalTime; + QStringRef isoString(&string); + isoString = isoString.mid(10); // trim "yyyy-MM-dd" + + // Must be left with T and at least one digit for the hour: + if (isoString.size() < 2 + || !(isoString.startsWith(QLatin1Char('T')) + // FIXME: QSql relies on QVariant::toDateTime() accepting a space here: + || isoString.startsWith(QLatin1Char(' ')))) { + return QDateTime(); + } + isoString = isoString.mid(1); // trim 'T' (or space) + + int offset = 0; + // Check end of string for Time Zone definition, either Z for UTC or [+-]HH:mm for Offset + if (isoString.endsWith(QLatin1Char('Z'))) { + spec = Qt::UTC; + isoString.chop(1); // trim 'Z' + } else { + // the loop below is faster but functionally equal to: + // const int signIndex = isoString.indexOf(QRegExp(QStringLiteral("[+-]"))); + int signIndex = isoString.size() - 1; + Q_ASSERT(signIndex >= 0); + bool found = false; + { + const QChar plus = QLatin1Char('+'); + const QChar minus = QLatin1Char('-'); + do { + QChar character(isoString.at(signIndex)); + found = character == plus || character == minus; + } while (!found && --signIndex >= 0); + } + + if (found) { + bool ok; + offset = fromOffsetString(isoString.mid(signIndex), &ok); + if (!ok) + return QDateTime(); + isoString = isoString.left(signIndex); + spec = Qt::OffsetFromUTC; + } + } + + // Might be end of day (24:00, including variants), which QTime considers invalid. + // ISO 8601 (section 4.2.3) says that 24:00 is equivalent to 00:00 the next day. + bool isMidnight24 = false; + QTime time = fromIsoTimeString(isoString, format, &isMidnight24); + if (!time.isValid()) + return QDateTime(); + if (isMidnight24) + date = date.addDays(1); + return QDateTime(date, time, spec, offset); + } +#if QT_CONFIG(textdate) + case Qt::TextDate: { + QVector<QStringRef> parts = string.splitRef(QLatin1Char(' '), QString::SkipEmptyParts); + + if ((parts.count() < 5) || (parts.count() > 6)) + return QDateTime(); + + // Accept "Sun Dec 1 13:02:00 1974" and "Sun 1. Dec 13:02:00 1974" + int month = 0; + int day = 0; + bool ok = false; + + // First try month then day + month = fromShortMonthName(parts.at(1)); + if (month) + day = parts.at(2).toInt(); + + // If failed try day then month + if (!month || !day) { + month = fromShortMonthName(parts.at(2)); + if (month) { + QStringRef dayStr = parts.at(1); + if (dayStr.endsWith(QLatin1Char('.'))) { + dayStr = dayStr.left(dayStr.size() - 1); + day = dayStr.toInt(); + } + } + } + + // If both failed, give up + if (!month || !day) + return QDateTime(); + + // Year can be before or after time, "Sun Dec 1 1974 13:02:00" or "Sun Dec 1 13:02:00 1974" + // Guess which by looking for ':' in the time + int year = 0; + int yearPart = 0; + int timePart = 0; + if (parts.at(3).contains(QLatin1Char(':'))) { + yearPart = 4; + timePart = 3; + } else if (parts.at(4).contains(QLatin1Char(':'))) { + yearPart = 3; + timePart = 4; + } else { + return QDateTime(); + } + + year = parts.at(yearPart).toInt(&ok); + if (!ok) + return QDateTime(); + + QDate date(year, month, day); + if (!date.isValid()) + return QDateTime(); + + QVector<QStringRef> timeParts = parts.at(timePart).split(QLatin1Char(':')); + if (timeParts.count() < 2 || timeParts.count() > 3) + return QDateTime(); + + int hour = timeParts.at(0).toInt(&ok); + if (!ok) + return QDateTime(); + + int minute = timeParts.at(1).toInt(&ok); + if (!ok) + return QDateTime(); + + int second = 0; + int millisecond = 0; + if (timeParts.count() > 2) { + const QVector<QStringRef> secondParts = timeParts.at(2).split(QLatin1Char('.')); + if (secondParts.size() > 2) { + return QDateTime(); + } + + second = secondParts.first().toInt(&ok); + if (!ok) { + return QDateTime(); + } + + if (secondParts.size() > 1) { + millisecond = secondParts.last().toInt(&ok); + if (!ok) { + return QDateTime(); + } + } + } + + QTime time(hour, minute, second, millisecond); + if (!time.isValid()) + return QDateTime(); + + if (parts.count() == 5) + return QDateTime(date, time, Qt::LocalTime); + + QStringRef tz = parts.at(5); + if (!tz.startsWith(QLatin1String("GMT"), Qt::CaseInsensitive)) + return QDateTime(); + tz = tz.mid(3); + if (!tz.isEmpty()) { + int offset = fromOffsetString(tz, &ok); + if (!ok) + return QDateTime(); + return QDateTime(date, time, Qt::OffsetFromUTC, offset); + } else { + return QDateTime(date, time, Qt::UTC); + } + } +#endif // textdate + } + + return QDateTime(); +} + +/*! + Returns the QDateTime represented by the \a string, using the \a + format given, or an invalid datetime if the string cannot be parsed. + + These expressions may be used for the date part of the format string: + + \table + \header \li Expression \li Output + \row \li d \li the day as number without a leading zero (1 to 31) + \row \li dd \li the day as number with a leading zero (01 to 31) + \row \li ddd + \li the abbreviated localized day name (e.g. 'Mon' to 'Sun'). + Uses QDate::shortDayName(). + \row \li dddd + \li the long localized day name (e.g. 'Monday' to 'Sunday'). + Uses QDate::longDayName(). + \row \li M \li the month as number without a leading zero (1-12) + \row \li MM \li the month as number with a leading zero (01-12) + \row \li MMM + \li the abbreviated localized month name (e.g. 'Jan' to 'Dec'). + Uses QDate::shortMonthName(). + \row \li MMMM + \li the long localized month name (e.g. 'January' to 'December'). + Uses QDate::longMonthName(). + \row \li yy \li the year as two digit number (00-99) + \row \li yyyy \li the year as four digit number + \endtable + + \note Unlike the other version of this function, day and month names must + be given in the user's local language. It is only possible to use the English + names if the user's language is English. + + These expressions may be used for the time part of the format string: + + \table + \header \li Expression \li Output + \row \li h + \li the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display) + \row \li hh + \li the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display) + \row \li H + \li the hour without a leading zero (0 to 23, even with AM/PM display) + \row \li HH + \li the hour with a leading zero (00 to 23, even with AM/PM display) + \row \li m \li the minute without a leading zero (0 to 59) + \row \li mm \li the minute with a leading zero (00 to 59) + \row \li s \li the whole second without a leading zero (0 to 59) + \row \li ss \li the whole second with a leading zero where applicable (00 to 59) + \row \li z \li the fractional part of the second, to go after a decimal + point, without trailing zeroes (0 to 999). Thus "\c{s.z}" + reports the seconds to full available (millisecond) precision + without trailing zeroes. + \row \li zzz \li the fractional part of the second, to millisecond + precision, including trailing zeroes where applicable (000 to 999). + \row \li AP or A + \li interpret as an AM/PM time. \e AP must be either "AM" or "PM". + \row \li ap or a + \li Interpret as an AM/PM time. \e ap must be either "am" or "pm". + \endtable + + All other input characters will be treated as text. Any sequence + of characters that are enclosed in single quotes will also be + treated as text and not be used as an expression. + + \snippet code/src_corelib_tools_qdatetime.cpp 12 + + If the format is not satisfied, an invalid QDateTime is returned. + The expressions that don't have leading zeroes (d, M, h, m, s, z) will be + greedy. This means that they will use two digits even if this will + put them outside the range and/or leave too few digits for other + sections. + + \snippet code/src_corelib_tools_qdatetime.cpp 13 + + This could have meant 1 January 00:30.00 but the M will grab + two digits. + + Incorrectly specified fields of the \a string will cause an invalid + QDateTime to be returned. For example, consider the following code, + where the two digit year 12 is read as 1912 (see the table below for all + field defaults); the resulting datetime is invalid because 23 April 1912 + was a Tuesday, not a Monday: + + \snippet code/src_corelib_tools_qdatetime.cpp 20 + + The correct code is: + + \snippet code/src_corelib_tools_qdatetime.cpp 21 + + For any field that is not represented in the format, the following + defaults are used: + + \table + \header \li Field \li Default value + \row \li Year \li 1900 + \row \li Month \li 1 (January) + \row \li Day \li 1 + \row \li Hour \li 0 + \row \li Minute \li 0 + \row \li Second \li 0 + \endtable + + For example: + + \snippet code/src_corelib_tools_qdatetime.cpp 14 + + \sa toString(), QDate::fromString(), QTime::fromString(), + QLocale::toDateTime() +*/ + +QDateTime QDateTime::fromString(const QString &string, const QString &format) +{ +#if QT_CONFIG(datetimeparser) + QTime time; + QDate date; + + QDateTimeParser dt(QVariant::DateTime, QDateTimeParser::FromString); + // dt.setDefaultLocale(QLocale::c()); ### Qt 6 + if (dt.parseFormat(format) && dt.fromString(string, &date, &time)) + return QDateTime(date, time); +#else + Q_UNUSED(string); + Q_UNUSED(format); +#endif + return QDateTime(); +} + +#endif // datestring +/*! + \fn QDateTime QDateTime::toLocalTime() const + + Returns a datetime containing the date and time information in + this datetime, but specified using the Qt::LocalTime definition. + + Example: + + \snippet code/src_corelib_tools_qdatetime.cpp 17 + + \sa toTimeSpec() +*/ + +/*! + \fn QDateTime QDateTime::toUTC() const + + Returns a datetime containing the date and time information in + this datetime, but specified using the Qt::UTC definition. + + Example: + + \snippet code/src_corelib_tools_qdatetime.cpp 18 + + \sa toTimeSpec() +*/ + +/***************************************************************************** + Date/time stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +/*! + \relates QDate + + Writes the \a date to stream \a out. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &out, const QDate &date) +{ + if (out.version() < QDataStream::Qt_5_0) + return out << quint32(date.jd); + else + return out << qint64(date.jd); +} + +/*! + \relates QDate + + Reads a date from stream \a in into the \a date. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &in, QDate &date) +{ + if (in.version() < QDataStream::Qt_5_0) { + quint32 jd; + in >> jd; + // Older versions consider 0 an invalid jd. + date.jd = (jd != 0 ? jd : QDate::nullJd()); + } else { + qint64 jd; + in >> jd; + date.jd = jd; + } + + return in; +} + +/*! + \relates QTime + + Writes \a time to stream \a out. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &out, const QTime &time) +{ + if (out.version() >= QDataStream::Qt_4_0) { + return out << quint32(time.mds); + } else { + // Qt3 had no support for reading -1, QTime() was valid and serialized as 0 + return out << quint32(time.isNull() ? 0 : time.mds); + } +} + +/*! + \relates QTime + + Reads a time from stream \a in into the given \a time. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &in, QTime &time) +{ + quint32 ds; + in >> ds; + if (in.version() >= QDataStream::Qt_4_0) { + time.mds = int(ds); + } else { + // Qt3 would write 0 for a null time + time.mds = (ds == 0) ? QTime::NullTime : int(ds); + } + return in; +} + +/*! + \relates QDateTime + + Writes \a dateTime to the \a out stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QDateTime &dateTime) +{ + QPair<QDate, QTime> dateAndTime; + + if (out.version() >= QDataStream::Qt_5_2) { + + // In 5.2 we switched to using Qt::TimeSpec and added offset support + dateAndTime = getDateTime(dateTime.d); + out << dateAndTime << qint8(dateTime.timeSpec()); + if (dateTime.timeSpec() == Qt::OffsetFromUTC) + out << qint32(dateTime.offsetFromUtc()); +#if QT_CONFIG(timezone) + else if (dateTime.timeSpec() == Qt::TimeZone) + out << dateTime.timeZone(); +#endif // timezone + + } else if (out.version() == QDataStream::Qt_5_0) { + + // In Qt 5.0 we incorrectly serialised all datetimes as UTC. + // This approach is wrong and should not be used again; it breaks + // the guarantee that a deserialised local datetime is the same time + // of day, regardless of which timezone it was serialised in. + dateAndTime = getDateTime((dateTime.isValid() ? dateTime.toUTC() : dateTime).d); + out << dateAndTime << qint8(dateTime.timeSpec()); + + } else if (out.version() >= QDataStream::Qt_4_0) { + + // From 4.0 to 5.1 (except 5.0) we used QDateTimePrivate::Spec + dateAndTime = getDateTime(dateTime.d); + out << dateAndTime; + switch (dateTime.timeSpec()) { + case Qt::UTC: + out << (qint8)QDateTimePrivate::UTC; + break; + case Qt::OffsetFromUTC: + out << (qint8)QDateTimePrivate::OffsetFromUTC; + break; + case Qt::TimeZone: + out << (qint8)QDateTimePrivate::TimeZone; + break; + case Qt::LocalTime: + out << (qint8)QDateTimePrivate::LocalUnknown; + break; + } + + } else { // version < QDataStream::Qt_4_0 + + // Before 4.0 there was no TimeSpec, only Qt::LocalTime was supported + dateAndTime = getDateTime(dateTime.d); + out << dateAndTime; + + } + + return out; +} + +/*! + \relates QDateTime + + Reads a datetime from the stream \a in into \a dateTime. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &in, QDateTime &dateTime) +{ + QDate dt; + QTime tm; + qint8 ts = 0; + Qt::TimeSpec spec = Qt::LocalTime; + qint32 offset = 0; +#if QT_CONFIG(timezone) + QTimeZone tz; +#endif // timezone + + if (in.version() >= QDataStream::Qt_5_2) { + + // In 5.2 we switched to using Qt::TimeSpec and added offset support + in >> dt >> tm >> ts; + spec = static_cast<Qt::TimeSpec>(ts); + if (spec == Qt::OffsetFromUTC) { + in >> offset; + dateTime = QDateTime(dt, tm, spec, offset); +#if QT_CONFIG(timezone) + } else if (spec == Qt::TimeZone) { + in >> tz; + dateTime = QDateTime(dt, tm, tz); +#endif // timezone + } else { + dateTime = QDateTime(dt, tm, spec); + } + + } else if (in.version() == QDataStream::Qt_5_0) { + + // In Qt 5.0 we incorrectly serialised all datetimes as UTC + in >> dt >> tm >> ts; + spec = static_cast<Qt::TimeSpec>(ts); + dateTime = QDateTime(dt, tm, Qt::UTC); + dateTime = dateTime.toTimeSpec(spec); + + } else if (in.version() >= QDataStream::Qt_4_0) { + + // From 4.0 to 5.1 (except 5.0) we used QDateTimePrivate::Spec + in >> dt >> tm >> ts; + switch ((QDateTimePrivate::Spec)ts) { + case QDateTimePrivate::UTC: + spec = Qt::UTC; + break; + case QDateTimePrivate::OffsetFromUTC: + spec = Qt::OffsetFromUTC; + break; + case QDateTimePrivate::TimeZone: + spec = Qt::TimeZone; +#if QT_CONFIG(timezone) + // FIXME: need to use a different constructor ! +#endif + break; + case QDateTimePrivate::LocalUnknown: + case QDateTimePrivate::LocalStandard: + case QDateTimePrivate::LocalDST: + spec = Qt::LocalTime; + break; + } + dateTime = QDateTime(dt, tm, spec, offset); + + } else { // version < QDataStream::Qt_4_0 + + // Before 4.0 there was no TimeSpec, only Qt::LocalTime was supported + in >> dt >> tm; + dateTime = QDateTime(dt, tm, spec, offset); + + } + + return in; +} +#endif // QT_NO_DATASTREAM + +/***************************************************************************** + Date / Time Debug Streams +*****************************************************************************/ + +#if !defined(QT_NO_DEBUG_STREAM) && QT_CONFIG(datestring) +QDebug operator<<(QDebug dbg, const QDate &date) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QDate("; + if (date.isValid()) + dbg.nospace() << date.toString(Qt::ISODate); + else + dbg.nospace() << "Invalid"; + dbg.nospace() << ')'; + return dbg; +} + +QDebug operator<<(QDebug dbg, const QTime &time) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QTime("; + if (time.isValid()) + dbg.nospace() << time.toString(QStringViewLiteral("HH:mm:ss.zzz")); + else + dbg.nospace() << "Invalid"; + dbg.nospace() << ')'; + return dbg; +} + +QDebug operator<<(QDebug dbg, const QDateTime &date) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QDateTime("; + if (date.isValid()) { + const Qt::TimeSpec ts = date.timeSpec(); + dbg.noquote() << date.toString(QStringViewLiteral("yyyy-MM-dd HH:mm:ss.zzz t")) + << ' ' << ts; + switch (ts) { + case Qt::UTC: + break; + case Qt::OffsetFromUTC: + dbg.space() << date.offsetFromUtc() << 's'; + break; + case Qt::TimeZone: +#if QT_CONFIG(timezone) + dbg.space() << date.timeZone().id(); +#endif // timezone + break; + case Qt::LocalTime: + break; + } + } else { + dbg.nospace() << "Invalid"; + } + return dbg.nospace() << ')'; +} +#endif // debug_stream && datestring + +/*! \fn uint qHash(const QDateTime &key, uint seed = 0) + \relates QHash + \since 5.0 + + Returns the hash value for the \a key, using \a seed to seed the calculation. +*/ +uint qHash(const QDateTime &key, uint seed) +{ + // Use to toMSecsSinceEpoch instead of individual qHash functions for + // QDate/QTime/spec/offset because QDateTime::operator== converts both arguments + // to the same timezone. If we don't, qHash would return different hashes for + // two QDateTimes that are equivalent once converted to the same timezone. + return qHash(key.toMSecsSinceEpoch(), seed); +} + +/*! \fn uint qHash(const QDate &key, uint seed = 0) + \relates QHash + \since 5.0 + + Returns the hash value for the \a key, using \a seed to seed the calculation. +*/ +uint qHash(const QDate &key, uint seed) noexcept +{ + return qHash(key.toJulianDay(), seed); +} + +/*! \fn uint qHash(const QTime &key, uint seed = 0) + \relates QHash + \since 5.0 + + Returns the hash value for the \a key, using \a seed to seed the calculation. +*/ +uint qHash(const QTime &key, uint seed) noexcept +{ + return qHash(key.msecsSinceStartOfDay(), seed); +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qdatetime.h b/src/corelib/time/qdatetime.h new file mode 100644 index 0000000000..3e3b953b52 --- /dev/null +++ b/src/corelib/time/qdatetime.h @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIME_H +#define QDATETIME_H + +#include <QtCore/qstring.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qshareddata.h> + +#include <limits> + +#if defined(Q_OS_DARWIN) || defined(Q_QDOC) +Q_FORWARD_DECLARE_CF_TYPE(CFDate); +Q_FORWARD_DECLARE_OBJC_CLASS(NSDate); +#endif + +QT_BEGIN_NAMESPACE + +class QTimeZone; +class QDateTime; + +class Q_CORE_EXPORT QDate +{ +public: + enum MonthNameType { // ### Qt 6: remove, along with methods using it + DateFormat = 0, + StandaloneFormat + }; +private: + explicit Q_DECL_CONSTEXPR QDate(qint64 julianDay) : jd(julianDay) {} +public: + Q_DECL_CONSTEXPR QDate() : jd(nullJd()) {} + QDate(int y, int m, int d); + + Q_DECL_CONSTEXPR bool isNull() const { return !isValid(); } + Q_DECL_CONSTEXPR bool isValid() const { return jd >= minJd() && jd <= maxJd(); } + + int year() const; + int month() const; + int day() const; + int dayOfWeek() const; + int dayOfYear() const; + int daysInMonth() const; + int daysInYear() const; + int weekNumber(int *yearNum = nullptr) const; + + QDateTime startOfDay(Qt::TimeSpec spec = Qt::LocalTime, int offsetSeconds = 0) const; + QDateTime endOfDay(Qt::TimeSpec spec = Qt::LocalTime, int offsetSeconds = 0) const; +#if QT_CONFIG(timezone) + QDateTime startOfDay(const QTimeZone &zone) const; + QDateTime endOfDay(const QTimeZone &zone) const; +#endif + +#if QT_DEPRECATED_SINCE(5, 10) && QT_CONFIG(textdate) + QT_DEPRECATED_X("Use QLocale::monthName or QLocale::standaloneMonthName") + static QString shortMonthName(int month, MonthNameType type = DateFormat); + QT_DEPRECATED_X("Use QLocale::dayName or QLocale::standaloneDayName") + static QString shortDayName(int weekday, MonthNameType type = DateFormat); + QT_DEPRECATED_X("Use QLocale::monthName or QLocale::standaloneMonthName") + static QString longMonthName(int month, MonthNameType type = DateFormat); + QT_DEPRECATED_X("Use QLocale::dayName or QLocale::standaloneDayName") + static QString longDayName(int weekday, MonthNameType type = DateFormat); +#endif // textdate && deprecated +#if QT_CONFIG(datestring) + QString toString(Qt::DateFormat f = Qt::TextDate) const; +#if QT_STRINGVIEW_LEVEL < 2 + QString toString(const QString &format) const; +#endif + QString toString(QStringView format) const; +#endif +#if QT_DEPRECATED_SINCE(5,0) + QT_DEPRECATED_X("Use setDate() instead") inline bool setYMD(int y, int m, int d) + { if (uint(y) <= 99) y += 1900; return setDate(y, m, d); } +#endif + + bool setDate(int year, int month, int day); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + void getDate(int *year, int *month, int *day); // ### Qt 6: remove +#endif // < Qt 6 + void getDate(int *year, int *month, int *day) const; + + Q_REQUIRED_RESULT QDate addDays(qint64 days) const; + Q_REQUIRED_RESULT QDate addMonths(int months) const; + Q_REQUIRED_RESULT QDate addYears(int years) const; + qint64 daysTo(const QDate &) const; + + Q_DECL_CONSTEXPR bool operator==(const QDate &other) const { return jd == other.jd; } + Q_DECL_CONSTEXPR bool operator!=(const QDate &other) const { return jd != other.jd; } + Q_DECL_CONSTEXPR bool operator< (const QDate &other) const { return jd < other.jd; } + Q_DECL_CONSTEXPR bool operator<=(const QDate &other) const { return jd <= other.jd; } + Q_DECL_CONSTEXPR bool operator> (const QDate &other) const { return jd > other.jd; } + Q_DECL_CONSTEXPR bool operator>=(const QDate &other) const { return jd >= other.jd; } + + static QDate currentDate(); +#if QT_CONFIG(datestring) + static QDate fromString(const QString &s, Qt::DateFormat f = Qt::TextDate); + static QDate fromString(const QString &s, const QString &format); +#endif + static bool isValid(int y, int m, int d); + static bool isLeapYear(int year); + + static Q_DECL_CONSTEXPR inline QDate fromJulianDay(qint64 jd_) + { return jd_ >= minJd() && jd_ <= maxJd() ? QDate(jd_) : QDate() ; } + Q_DECL_CONSTEXPR inline qint64 toJulianDay() const { return jd; } + +private: + // using extra parentheses around min to avoid expanding it if it is a macro + static Q_DECL_CONSTEXPR inline qint64 nullJd() { return (std::numeric_limits<qint64>::min)(); } + static Q_DECL_CONSTEXPR inline qint64 minJd() { return Q_INT64_C(-784350574879); } + static Q_DECL_CONSTEXPR inline qint64 maxJd() { return Q_INT64_C( 784354017364); } + + qint64 jd; + + friend class QDateTime; + friend class QDateTimePrivate; +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDate &); + friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QDate &); +#endif +}; +Q_DECLARE_TYPEINFO(QDate, Q_MOVABLE_TYPE); + +class Q_CORE_EXPORT QTime +{ + explicit Q_DECL_CONSTEXPR QTime(int ms) : mds(ms) + {} +public: + Q_DECL_CONSTEXPR QTime(): mds(NullTime) + {} + QTime(int h, int m, int s = 0, int ms = 0); + + Q_DECL_CONSTEXPR bool isNull() const { return mds == NullTime; } + bool isValid() const; + + int hour() const; + int minute() const; + int second() const; + int msec() const; +#if QT_CONFIG(datestring) + QString toString(Qt::DateFormat f = Qt::TextDate) const; +#if QT_STRINGVIEW_LEVEL < 2 + QString toString(const QString &format) const; +#endif + QString toString(QStringView format) const; +#endif + bool setHMS(int h, int m, int s, int ms = 0); + + Q_REQUIRED_RESULT QTime addSecs(int secs) const; + int secsTo(const QTime &) const; + Q_REQUIRED_RESULT QTime addMSecs(int ms) const; + int msecsTo(const QTime &) const; + + Q_DECL_CONSTEXPR bool operator==(const QTime &other) const { return mds == other.mds; } + Q_DECL_CONSTEXPR bool operator!=(const QTime &other) const { return mds != other.mds; } + Q_DECL_CONSTEXPR bool operator< (const QTime &other) const { return mds < other.mds; } + Q_DECL_CONSTEXPR bool operator<=(const QTime &other) const { return mds <= other.mds; } + Q_DECL_CONSTEXPR bool operator> (const QTime &other) const { return mds > other.mds; } + Q_DECL_CONSTEXPR bool operator>=(const QTime &other) const { return mds >= other.mds; } + + static Q_DECL_CONSTEXPR inline QTime fromMSecsSinceStartOfDay(int msecs) { return QTime(msecs); } + Q_DECL_CONSTEXPR inline int msecsSinceStartOfDay() const { return mds == NullTime ? 0 : mds; } + + static QTime currentTime(); +#if QT_CONFIG(datestring) + static QTime fromString(const QString &s, Qt::DateFormat f = Qt::TextDate); + static QTime fromString(const QString &s, const QString &format); +#endif + static bool isValid(int h, int m, int s, int ms = 0); + +#if QT_DEPRECATED_SINCE(5, 14) // ### Qt 6: remove + QT_DEPRECATED_X("Use QElapsedTimer instead") void start(); + QT_DEPRECATED_X("Use QElapsedTimer instead") int restart(); + QT_DEPRECATED_X("Use QElapsedTimer instead") int elapsed() const; +#endif +private: + enum TimeFlag { NullTime = -1 }; + Q_DECL_CONSTEXPR inline int ds() const { return mds == -1 ? 0 : mds; } + int mds; + + friend class QDateTime; + friend class QDateTimePrivate; +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTime &); + friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTime &); +#endif +}; +Q_DECLARE_TYPEINFO(QTime, Q_MOVABLE_TYPE); + +class QDateTimePrivate; + +class Q_CORE_EXPORT QDateTime +{ + // ### Qt 6: revisit the optimization + struct ShortData { +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + quintptr status : 8; +#endif + // note: this is only 24 bits on 32-bit systems... + qintptr msecs : sizeof(void *) * 8 - 8; + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + quintptr status : 8; +#endif + }; + + union Data { + enum { + // To be of any use, we need at least 60 years around 1970, which + // is 1,893,456,000,000 ms. That requires 41 bits to store, plus + // the sign bit. With the status byte, the minimum size is 50 bits. + CanBeSmall = sizeof(ShortData) * 8 > 50 + }; + + Data(); + Data(Qt::TimeSpec); + Data(const Data &other); + Data(Data &&other); + Data &operator=(const Data &other); + ~Data(); + + bool isShort() const; + void detach(); + + const QDateTimePrivate *operator->() const; + QDateTimePrivate *operator->(); + + QDateTimePrivate *d; + ShortData data; + }; + +public: + QDateTime() noexcept(Data::CanBeSmall); + explicit QDateTime(const QDate &); + QDateTime(const QDate &, const QTime &, Qt::TimeSpec spec = Qt::LocalTime); + // ### Qt 6: Merge with above with default offsetSeconds = 0 + QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, int offsetSeconds); +#if QT_CONFIG(timezone) + QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone); +#endif // timezone + QDateTime(const QDateTime &other) noexcept; + QDateTime(QDateTime &&other) noexcept; + ~QDateTime(); + + QDateTime &operator=(QDateTime &&other) noexcept { swap(other); return *this; } + QDateTime &operator=(const QDateTime &other) noexcept; + + void swap(QDateTime &other) noexcept { qSwap(d.d, other.d.d); } + + bool isNull() const; + bool isValid() const; + + QDate date() const; + QTime time() const; + Qt::TimeSpec timeSpec() const; + int offsetFromUtc() const; +#if QT_CONFIG(timezone) + QTimeZone timeZone() const; +#endif // timezone + QString timeZoneAbbreviation() const; + bool isDaylightTime() const; + + qint64 toMSecsSinceEpoch() const; + qint64 toSecsSinceEpoch() const; + + void setDate(const QDate &date); + void setTime(const QTime &time); + void setTimeSpec(Qt::TimeSpec spec); + void setOffsetFromUtc(int offsetSeconds); +#if QT_CONFIG(timezone) + void setTimeZone(const QTimeZone &toZone); +#endif // timezone + void setMSecsSinceEpoch(qint64 msecs); + void setSecsSinceEpoch(qint64 secs); + +#if QT_CONFIG(datestring) + QString toString(Qt::DateFormat f = Qt::TextDate) const; +#if QT_STRINGVIEW_LEVEL < 2 + QString toString(const QString &format) const; +#endif + QString toString(QStringView format) const; +#endif + Q_REQUIRED_RESULT QDateTime addDays(qint64 days) const; + Q_REQUIRED_RESULT QDateTime addMonths(int months) const; + Q_REQUIRED_RESULT QDateTime addYears(int years) const; + Q_REQUIRED_RESULT QDateTime addSecs(qint64 secs) const; + Q_REQUIRED_RESULT QDateTime addMSecs(qint64 msecs) const; + + QDateTime toTimeSpec(Qt::TimeSpec spec) const; + inline QDateTime toLocalTime() const { return toTimeSpec(Qt::LocalTime); } + inline QDateTime toUTC() const { return toTimeSpec(Qt::UTC); } + QDateTime toOffsetFromUtc(int offsetSeconds) const; +#if QT_CONFIG(timezone) + QDateTime toTimeZone(const QTimeZone &toZone) const; +#endif // timezone + + qint64 daysTo(const QDateTime &) const; + qint64 secsTo(const QDateTime &) const; + qint64 msecsTo(const QDateTime &) const; + + bool operator==(const QDateTime &other) const; + inline bool operator!=(const QDateTime &other) const { return !(*this == other); } + bool operator<(const QDateTime &other) const; + inline bool operator<=(const QDateTime &other) const { return !(other < *this); } + inline bool operator>(const QDateTime &other) const { return other < *this; } + inline bool operator>=(const QDateTime &other) const { return !(*this < other); } + +#if QT_DEPRECATED_SINCE(5, 2) // ### Qt 6: remove + QT_DEPRECATED_X("Use setOffsetFromUtc() instead") void setUtcOffset(int seconds); + QT_DEPRECATED_X("Use offsetFromUtc() instead") int utcOffset() const; +#endif // QT_DEPRECATED_SINCE + + static QDateTime currentDateTime(); + static QDateTime currentDateTimeUtc(); +#if QT_CONFIG(datestring) + static QDateTime fromString(const QString &s, Qt::DateFormat f = Qt::TextDate); + static QDateTime fromString(const QString &s, const QString &format); +#endif + +#if QT_DEPRECATED_SINCE(5, 8) + uint toTime_t() const; + void setTime_t(uint secsSince1Jan1970UTC); + static QDateTime fromTime_t(uint secsSince1Jan1970UTC); + static QDateTime fromTime_t(uint secsSince1Jan1970UTC, Qt::TimeSpec spec, + int offsetFromUtc = 0); + static QDateTime fromTime_t(uint secsSince1Jan1970UTC, const QTimeZone &timeZone); +#endif + + static QDateTime fromMSecsSinceEpoch(qint64 msecs); + // ### Qt 6: Merge with above with default spec = Qt::LocalTime + static QDateTime fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int offsetFromUtc = 0); + static QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spe = Qt::LocalTime, int offsetFromUtc = 0); + +#if QT_CONFIG(timezone) + static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone); + static QDateTime fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone); +#endif + + static qint64 currentMSecsSinceEpoch() noexcept; + static qint64 currentSecsSinceEpoch() noexcept; + +#if defined(Q_OS_DARWIN) || defined(Q_QDOC) + static QDateTime fromCFDate(CFDateRef date); + CFDateRef toCFDate() const Q_DECL_CF_RETURNS_RETAINED; + static QDateTime fromNSDate(const NSDate *date); + NSDate *toNSDate() const Q_DECL_NS_RETURNS_AUTORELEASED; +#endif + +private: + friend class QDateTimePrivate; + + Data d; + +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDateTime &); + friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QDateTime &); +#endif + +#if !defined(QT_NO_DEBUG_STREAM) && QT_CONFIG(datestring) + friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QDateTime &); +#endif +}; +Q_DECLARE_SHARED(QDateTime) + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDate &); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QDate &); +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTime &); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTime &); +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDateTime &); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QDateTime &); +#endif // QT_NO_DATASTREAM + +#if !defined(QT_NO_DEBUG_STREAM) && QT_CONFIG(datestring) +Q_CORE_EXPORT QDebug operator<<(QDebug, const QDate &); +Q_CORE_EXPORT QDebug operator<<(QDebug, const QTime &); +Q_CORE_EXPORT QDebug operator<<(QDebug, const QDateTime &); +#endif + +// QDateTime is not noexcept for now -- to be revised once +// timezone and calendaring support is added +Q_CORE_EXPORT uint qHash(const QDateTime &key, uint seed = 0); +Q_CORE_EXPORT uint qHash(const QDate &key, uint seed = 0) noexcept; +Q_CORE_EXPORT uint qHash(const QTime &key, uint seed = 0) noexcept; + +QT_END_NAMESPACE + +#endif // QDATETIME_H diff --git a/src/corelib/time/qdatetime_p.h b/src/corelib/time/qdatetime_p.h new file mode 100644 index 0000000000..6018f8f7b0 --- /dev/null +++ b/src/corelib/time/qdatetime_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIME_P_H +#define QDATETIME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include "qplatformdefs.h" +#include "QtCore/qatomic.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qpair.h" + +#if QT_CONFIG(timezone) +#include "qtimezone.h" +#endif + +QT_BEGIN_NAMESPACE + +class QDateTimePrivate +{ +public: + // forward the declarations from QDateTime (this makes them public) + typedef QDateTime::ShortData QDateTimeShortData; + typedef QDateTime::Data QDateTimeData; + + // Never change or delete this enum, it is required for backwards compatible + // serialization of QDateTime before 5.2, so is essentially public API + enum Spec { + LocalUnknown = -1, + LocalStandard = 0, + LocalDST = 1, + UTC = 2, + OffsetFromUTC = 3, + TimeZone = 4 + }; + + // Daylight Time Status + enum DaylightStatus { + UnknownDaylightTime = -1, + StandardTime = 0, + DaylightTime = 1 + }; + + // Status of date/time + enum StatusFlag { + ShortData = 0x01, + + ValidDate = 0x02, + ValidTime = 0x04, + ValidDateTime = 0x08, + + TimeSpecMask = 0x30, + + SetToStandardTime = 0x40, + SetToDaylightTime = 0x80 + }; + Q_DECLARE_FLAGS(StatusFlags, StatusFlag) + + enum { + TimeSpecShift = 4, + ValidityMask = ValidDate | ValidTime | ValidDateTime, + DaylightMask = SetToStandardTime | SetToDaylightTime + }; + + QDateTimePrivate() : m_msecs(0), + m_status(StatusFlag(Qt::LocalTime << TimeSpecShift)), + m_offsetFromUtc(0), + ref(0) + { + } + + static QDateTime::Data create(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec, + int offsetSeconds); + +#if QT_CONFIG(timezone) + static QDateTime::Data create(const QDate &toDate, const QTime &toTime, const QTimeZone & timeZone); +#endif // timezone + + qint64 m_msecs; + StatusFlags m_status; + int m_offsetFromUtc; + mutable QAtomicInt ref; +#if QT_CONFIG(timezone) + QTimeZone m_timeZone; +#endif // timezone + +#if QT_CONFIG(timezone) + static qint64 zoneMSecsToEpochMSecs(qint64 msecs, const QTimeZone &zone, + DaylightStatus hint = UnknownDaylightTime, + QDate *localDate = nullptr, QTime *localTime = nullptr); + + // Inlined for its one caller in qdatetime.cpp + inline void setUtcOffsetByTZ(qint64 atMSecsSinceEpoch); +#endif // timezone + + // ### Qt 5.14: expose publicly in QDateTime + // The first and last years of which QDateTime can represent some part: + enum class YearRange : qint32 { First = -292275056, Last = +292278994 }; +}; + +QT_END_NAMESPACE + +#endif // QDATETIME_P_H diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp new file mode 100644 index 0000000000..728b066db1 --- /dev/null +++ b/src/corelib/time/qdatetimeparser.cpp @@ -0,0 +1,2047 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "private/qdatetimeparser_p.h" + +#include "qdatastream.h" +#include "qset.h" +#include "qlocale.h" +#include "qdatetime.h" +#if QT_CONFIG(timezone) +#include "qtimezone.h" +#endif +#include "qdebug.h" + +//#define QDATETIMEPARSER_DEBUG +#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM) +# define QDTPDEBUG qDebug() +# define QDTPDEBUGN qDebug +#else +# define QDTPDEBUG if (false) qDebug() +# define QDTPDEBUGN if (false) qDebug +#endif + +QT_BEGIN_NAMESPACE + +QDateTimeParser::~QDateTimeParser() +{ +} + +/*! + \internal + Gets the digit from a datetime. E.g. + + QDateTime var(QDate(2004, 02, 02)); + int digit = getDigit(var, Year); + // digit = 2004 +*/ + +int QDateTimeParser::getDigit(const QDateTime &t, int index) const +{ + if (index < 0 || index >= sectionNodes.size()) { +#if QT_CONFIG(datestring) + qWarning("QDateTimeParser::getDigit() Internal error (%ls %d)", + qUtf16Printable(t.toString()), index); +#else + qWarning("QDateTimeParser::getDigit() Internal error (%d)", index); +#endif + return -1; + } + const SectionNode &node = sectionNodes.at(index); + switch (node.type) { + case TimeZoneSection: return t.offsetFromUtc(); + case Hour24Section: case Hour12Section: return t.time().hour(); + case MinuteSection: return t.time().minute(); + case SecondSection: return t.time().second(); + case MSecSection: return t.time().msec(); + case YearSection2Digits: + case YearSection: return t.date().year(); + case MonthSection: return t.date().month(); + case DaySection: return t.date().day(); + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: return t.date().day(); + case AmPmSection: return t.time().hour() > 11 ? 1 : 0; + + default: break; + } + +#if QT_CONFIG(datestring) + qWarning("QDateTimeParser::getDigit() Internal error 2 (%ls %d)", + qUtf16Printable(t.toString()), index); +#else + qWarning("QDateTimeParser::getDigit() Internal error 2 (%d)", index); +#endif + return -1; +} + +/*! + \internal + Sets a digit in a datetime. E.g. + + QDateTime var(QDate(2004, 02, 02)); + int digit = getDigit(var, Year); + // digit = 2004 + setDigit(&var, Year, 2005); + digit = getDigit(var, Year); + // digit = 2005 +*/ + +bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const +{ + if (index < 0 || index >= sectionNodes.size()) { +#if QT_CONFIG(datestring) + qWarning("QDateTimeParser::setDigit() Internal error (%ls %d %d)", + qUtf16Printable(v.toString()), index, newVal); +#else + qWarning("QDateTimeParser::setDigit() Internal error (%d %d)", index, newVal); +#endif + return false; + } + const SectionNode &node = sectionNodes.at(index); + + const QDate date = v.date(); + const QTime time = v.time(); + int year = date.year(); + int month = date.month(); + int day = date.day(); + int hour = time.hour(); + int minute = time.minute(); + int second = time.second(); + int msec = time.msec(); + Qt::TimeSpec tspec = v.timeSpec(); + // Only offset from UTC is amenable to setting an int value: + int offset = tspec == Qt::OffsetFromUTC ? v.offsetFromUtc() : 0; + + switch (node.type) { + case Hour24Section: case Hour12Section: hour = newVal; break; + case MinuteSection: minute = newVal; break; + case SecondSection: second = newVal; break; + case MSecSection: msec = newVal; break; + case YearSection2Digits: + case YearSection: year = newVal; break; + case MonthSection: month = newVal; break; + case DaySection: + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + if (newVal > 31) { + // have to keep legacy behavior. setting the + // date to 32 should return false. Setting it + // to 31 for february should return true + return false; + } + day = newVal; + break; + case TimeZoneSection: + if (newVal < absoluteMin(index) || newVal > absoluteMax(index)) + return false; + tspec = Qt::OffsetFromUTC; + offset = newVal; + break; + case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break; + default: + qWarning("QDateTimeParser::setDigit() Internal error (%ls)", + qUtf16Printable(node.name())); + break; + } + + if (!(node.type & DaySectionMask)) { + if (day < cachedDay) + day = cachedDay; + const int max = QDate(year, month, 1).daysInMonth(); + if (day > max) { + day = max; + } + } + + const QDate newDate(year, month, day); + const QTime newTime(hour, minute, second, msec); + if (!newDate.isValid() || !newTime.isValid()) + return false; + + // Preserve zone: + v = +#if QT_CONFIG(timezone) + tspec == Qt::TimeZone ? QDateTime(newDate, newTime, v.timeZone()) : +#endif + QDateTime(newDate, newTime, tspec, offset); + return true; +} + + + +/*! + \internal + + Returns the absolute maximum for a section +*/ + +int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const +{ + const SectionNode &sn = sectionNode(s); + switch (sn.type) { +#if QT_CONFIG(timezone) + case TimeZoneSection: return QTimeZone::MaxUtcOffsetSecs; +#endif + case Hour24Section: + case Hour12Section: return 23; // this is special-cased in + // parseSection. We want it to be + // 23 for the stepBy case. + case MinuteSection: + case SecondSection: return 59; + case MSecSection: return 999; + case YearSection2Digits: + case YearSection: return 9999; // sectionMaxSize will prevent + // people from typing in a larger + // number in count == 2 sections. + // stepBy() will work on real years anyway + case MonthSection: return 12; + case DaySection: + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: return cur.isValid() ? cur.date().daysInMonth() : 31; + case AmPmSection: return 1; + default: break; + } + qWarning("QDateTimeParser::absoluteMax() Internal error (%ls)", + qUtf16Printable(sn.name())); + return -1; +} + +/*! + \internal + + Returns the absolute minimum for a section +*/ + +int QDateTimeParser::absoluteMin(int s) const +{ + const SectionNode &sn = sectionNode(s); + switch (sn.type) { +#if QT_CONFIG(timezone) + case TimeZoneSection: return QTimeZone::MinUtcOffsetSecs; +#endif + case Hour24Section: + case Hour12Section: + case MinuteSection: + case SecondSection: + case MSecSection: + case YearSection2Digits: + case YearSection: return 0; + case MonthSection: + case DaySection: + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: return 1; + case AmPmSection: return 0; + default: break; + } + qWarning("QDateTimeParser::absoluteMin() Internal error (%ls, %0x)", + qUtf16Printable(sn.name()), sn.type); + return -1; +} + +/*! + \internal + + Returns the sectionNode for the Section \a s. +*/ + +const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(int sectionIndex) const +{ + if (sectionIndex < 0) { + switch (sectionIndex) { + case FirstSectionIndex: + return first; + case LastSectionIndex: + return last; + case NoSectionIndex: + return none; + } + } else if (sectionIndex < sectionNodes.size()) { + return sectionNodes.at(sectionIndex); + } + + qWarning("QDateTimeParser::sectionNode() Internal error (%d)", + sectionIndex); + return none; +} + +QDateTimeParser::Section QDateTimeParser::sectionType(int sectionIndex) const +{ + return sectionNode(sectionIndex).type; +} + + +/*! + \internal + + Returns the starting position for section \a s. +*/ + +int QDateTimeParser::sectionPos(int sectionIndex) const +{ + return sectionPos(sectionNode(sectionIndex)); +} + +int QDateTimeParser::sectionPos(const SectionNode &sn) const +{ + switch (sn.type) { + case FirstSection: return 0; + case LastSection: return displayText().size() - 1; + default: break; + } + if (sn.pos == -1) { + qWarning("QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name())); + return -1; + } + return sn.pos; +} + + +/*! + \internal + + helper function for parseFormat. removes quotes that are + not escaped and removes the escaping on those that are escaped + +*/ + +static QString unquote(const QStringRef &str) +{ + const QChar quote(QLatin1Char('\'')); + const QChar slash(QLatin1Char('\\')); + const QChar zero(QLatin1Char('0')); + QString ret; + QChar status(zero); + const int max = str.size(); + for (int i=0; i<max; ++i) { + if (str.at(i) == quote) { + if (status != quote) { + status = quote; + } else if (!ret.isEmpty() && str.at(i - 1) == slash) { + ret[ret.size() - 1] = quote; + } else { + status = zero; + } + } else { + ret += str.at(i); + } + } + return ret; +} +/*! + \internal + + Parses the format \a newFormat. If successful, returns \c true and + sets up the format. Else keeps the old format and returns \c false. + +*/ + +static inline int countRepeat(const QString &str, int index, int maxCount) +{ + int count = 1; + const QChar ch(str.at(index)); + const int max = qMin(index + maxCount, str.size()); + while (index + count < max && str.at(index + count) == ch) { + ++count; + } + return count; +} + +static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote) +{ + const QStringRef separator = string.midRef(from, size); + list->append(lastQuote >= from ? unquote(separator) : separator.toString()); +} + + +bool QDateTimeParser::parseFormat(const QString &newFormat) +{ + const QLatin1Char quote('\''); + const QLatin1Char slash('\\'); + const QLatin1Char zero('0'); + if (newFormat == displayFormat && !newFormat.isEmpty()) { + return true; + } + + QDTPDEBUGN("parseFormat: %s", newFormat.toLatin1().constData()); + + QVector<SectionNode> newSectionNodes; + Sections newDisplay = 0; + QStringList newSeparators; + int i, index = 0; + int add = 0; + QChar status(zero); + const int max = newFormat.size(); + int lastQuote = -1; + for (i = 0; i<max; ++i) { + if (newFormat.at(i) == quote) { + lastQuote = i; + ++add; + if (status != quote) { + status = quote; + } else if (i > 0 && newFormat.at(i - 1) != slash) { + status = zero; + } + } else if (status != quote) { + const char sect = newFormat.at(i).toLatin1(); + switch (sect) { + case 'H': + case 'h': + if (parserType != QVariant::Date) { + const Section hour = (sect == 'h') ? Hour12Section : Hour24Section; + const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= hour; + } + break; + case 'm': + if (parserType != QVariant::Date) { + const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= MinuteSection; + } + break; + case 's': + if (parserType != QVariant::Date) { + const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= SecondSection; + } + break; + + case 'z': + if (parserType != QVariant::Date) { + const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= MSecSection; + } + break; + case 'A': + case 'a': + if (parserType != QVariant::Date) { + const bool cap = (sect == 'A'); + const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + newDisplay |= AmPmSection; + if (i + 1 < newFormat.size() + && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { + ++i; + } + index = i + 1; + } + break; + case 'y': + if (parserType != QVariant::Time) { + const int repeat = countRepeat(newFormat, i, 4); + if (repeat >= 2) { + const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits, + i - add, repeat == 4 ? 4 : 2, 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= sn.type; + } + } + break; + case 'M': + if (parserType != QVariant::Time) { + const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; + newSectionNodes.append(sn); + newSeparators.append(unquote(newFormat.midRef(index, i - index))); + i += sn.count - 1; + index = i + 1; + newDisplay |= MonthSection; + } + break; + case 'd': + if (parserType != QVariant::Time) { + const int repeat = countRepeat(newFormat, i, 4); + const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong + : (repeat == 3 ? DayOfWeekSectionShort : DaySection)); + const SectionNode sn = { sectionType, i - add, repeat, 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= sn.type; + } + break; + case 't': + if (parserType != QVariant::Time) { + const SectionNode sn = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= TimeZoneSection; + } + break; + default: + break; + } + } + } + if (newSectionNodes.isEmpty() && context == DateTimeEdit) { + return false; + } + + if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) { + const int count = newSectionNodes.size(); + for (int i = 0; i < count; ++i) { + SectionNode &node = newSectionNodes[i]; + if (node.type == Hour12Section) + node.type = Hour24Section; + } + } + + if (index < max) { + appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); + } else { + newSeparators.append(QString()); + } + + displayFormat = newFormat; + separators = newSeparators; + sectionNodes = newSectionNodes; + display = newDisplay; + last.pos = -1; + +// for (int i=0; i<sectionNodes.size(); ++i) { +// QDTPDEBUG << sectionNodes.at(i).name() << sectionNodes.at(i).count; +// } + + QDTPDEBUG << newFormat << displayFormat; + QDTPDEBUGN("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData()); + + return true; +} + +/*! + \internal + + Returns the size of section \a s. +*/ + +int QDateTimeParser::sectionSize(int sectionIndex) const +{ + if (sectionIndex < 0) + return 0; + + if (sectionIndex >= sectionNodes.size()) { + qWarning("QDateTimeParser::sectionSize Internal error (%d)", sectionIndex); + return -1; + } + + if (sectionIndex == sectionNodes.size() - 1) { + // In some cases there is a difference between displayText() and text. + // e.g. when text is 2000/01/31 and displayText() is "2000/2/31" - text + // is the previous value and displayText() is the new value. + // The size difference is always due to leading zeroes. + int sizeAdjustment = 0; + const int displayTextSize = displayText().size(); + if (displayTextSize != text.size()) { + // Any zeroes added before this section will affect our size. + int preceedingZeroesAdded = 0; + if (sectionNodes.size() > 1 && context == DateTimeEdit) { + const auto begin = sectionNodes.cbegin(); + const auto end = begin + sectionIndex; + for (auto sectionIt = begin; sectionIt != end; ++sectionIt) + preceedingZeroesAdded += sectionIt->zeroesAdded; + } + sizeAdjustment = preceedingZeroesAdded; + } + + return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size(); + } else { + return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex) + - separators.at(sectionIndex + 1).size(); + } +} + + +int QDateTimeParser::sectionMaxSize(Section s, int count) const +{ +#if QT_CONFIG(textdate) + int mcount = 12; +#endif + + switch (s) { + case FirstSection: + case NoSection: + case LastSection: return 0; + + case AmPmSection: { + const int lowerMax = qMin(getAmPmText(AmText, LowerCase).size(), + getAmPmText(PmText, LowerCase).size()); + const int upperMax = qMin(getAmPmText(AmText, UpperCase).size(), + getAmPmText(PmText, UpperCase).size()); + return qMin(4, qMin(lowerMax, upperMax)); + } + + case Hour24Section: + case Hour12Section: + case MinuteSection: + case SecondSection: + case DaySection: return 2; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: +#if !QT_CONFIG(textdate) + return 2; +#else + mcount = 7; + Q_FALLTHROUGH(); +#endif + case MonthSection: +#if !QT_CONFIG(textdate) + return 2; +#else + if (count <= 2) + return 2; + + { + int ret = 0; + const QLocale l = locale(); + const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; + for (int i=1; i<=mcount; ++i) { + const QString str = (s == MonthSection + ? l.monthName(i, format) + : l.dayName(i, format)); + ret = qMax(str.size(), ret); + } + return ret; + } +#endif + case MSecSection: return 3; + case YearSection: return 4; + case YearSection2Digits: return 2; + // Arbitrarily many tokens (each up to 14 bytes) joined with / separators: + case TimeZoneSection: return std::numeric_limits<int>::max(); + + case CalendarPopupSection: + case Internal: + case TimeSectionMask: + case DateSectionMask: + case HourSectionMask: + case YearSectionMask: + case DayOfWeekSectionMask: + case DaySectionMask: + qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s", + SectionNode::name(s).toLatin1().constData()); + + case NoSectionIndex: + case FirstSectionIndex: + case LastSectionIndex: + case CalendarPopupIndex: + // these cases can't happen + break; + } + return -1; +} + + +int QDateTimeParser::sectionMaxSize(int index) const +{ + const SectionNode &sn = sectionNode(index); + return sectionMaxSize(sn.type, sn.count); +} + +/*! + \internal + + Returns the text of section \a s. This function operates on the + arg text rather than edit->text(). +*/ + + +QString QDateTimeParser::sectionText(const QString &text, int sectionIndex, int index) const +{ + const SectionNode &sn = sectionNode(sectionIndex); + switch (sn.type) { + case NoSectionIndex: + case FirstSectionIndex: + case LastSectionIndex: + return QString(); + default: break; + } + + return text.mid(index, sectionSize(sectionIndex)); +} + +QString QDateTimeParser::sectionText(int sectionIndex) const +{ + const SectionNode &sn = sectionNode(sectionIndex); + return sectionText(displayText(), sectionIndex, sn.pos); +} + + +#if QT_CONFIG(datestring) + +QDateTimeParser::ParsedSection +QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, + int offset, QString *text) const +{ + ParsedSection result; // initially Invalid + const SectionNode &sn = sectionNode(sectionIndex); + if (sn.type & Internal) { + qWarning("QDateTimeParser::parseSection Internal error (%ls %d)", + qUtf16Printable(sn.name()), sectionIndex); + return result; + } + + const int sectionmaxsize = sectionMaxSize(sectionIndex); + QStringRef sectionTextRef = text->midRef(offset, sectionmaxsize); + + QDTPDEBUG << "sectionValue for" << sn.name() + << "with text" << *text << "and (at" << offset + << ") st:" << sectionTextRef; + + switch (sn.type) { + case AmPmSection: { + QString sectiontext = sectionTextRef.toString(); + int used; + const int ampm = findAmPm(sectiontext, sectionIndex, &used); + switch (ampm) { + case AM: // sectiontext == AM + case PM: // sectiontext == PM + result = ParsedSection(Acceptable, ampm, used); + break; + case PossibleAM: // sectiontext => AM + case PossiblePM: // sectiontext => PM + result = ParsedSection(Intermediate, ampm - 2, used); + break; + case PossibleBoth: // sectiontext => AM|PM + result = ParsedSection(Intermediate, 0, used); + break; + case Neither: + QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1"; + break; + default: + QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm); + break; + } + if (result.state != Invalid) + text->replace(offset, used, sectiontext.constData(), used); + break; } + case TimeZoneSection: +#if QT_CONFIG(timezone) + result = findTimeZone(sectionTextRef, currentValue, + absoluteMax(sectionIndex), + absoluteMin(sectionIndex)); +#endif + break; + case MonthSection: + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + if (sn.count >= 3) { + QString sectiontext = sectionTextRef.toString(); + int num = 0, used = 0; + if (sn.type == MonthSection) { + const QDate minDate = getMinimum().date(); + const int min = (currentValue.date().year() == minDate.year()) + ? minDate.month() : 1; + num = findMonth(sectiontext.toLower(), min, sectionIndex, §iontext, &used); + } else { + num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used); + } + + result = ParsedSection(Intermediate, num, used); + if (num != -1) { + text->replace(offset, used, sectiontext.constData(), used); + if (used == sectiontext.size()) + result = ParsedSection(Acceptable, num, used); + } + break; + } + Q_FALLTHROUGH(); + // All numeric: + case DaySection: + case YearSection: + case YearSection2Digits: + case Hour12Section: + case Hour24Section: + case MinuteSection: + case SecondSection: + case MSecSection: { + int sectiontextSize = sectionTextRef.size(); + if (sectiontextSize == 0) { + result = ParsedSection(Intermediate); + } else { + for (int i = 0; i < sectiontextSize; ++i) { + if (sectionTextRef.at(i).isSpace()) + sectiontextSize = i; // which exits the loop + } + + const int absMax = absoluteMax(sectionIndex); + QLocale loc; + bool ok = true; + int last = -1, used = -1; + + Q_ASSERT(sectiontextSize <= sectionmaxsize); + QStringRef digitsStr = sectionTextRef.left(sectiontextSize); + for (int digits = sectiontextSize; digits >= 1; --digits) { + digitsStr.truncate(digits); + int tmp = (int)loc.toUInt(digitsStr, &ok); + if (ok && sn.type == Hour12Section) { + if (tmp > 12) { + tmp = -1; + ok = false; + } else if (tmp == 12) { + tmp = 0; + } + } + if (ok && tmp <= absMax) { + QDTPDEBUG << sectionTextRef.left(digits) << tmp << digits; + last = tmp; + used = digits; + break; + } + } + + if (last == -1) { + QChar first(sectionTextRef.at(0)); + if (separators.at(sectionIndex + 1).startsWith(first)) + result = ParsedSection(Intermediate, 0, used); + else + QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" << last << ok; + } else { + const FieldInfo fi = fieldInfo(sectionIndex); + const bool unfilled = used < sectionmaxsize; + if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002 + for (int i = used; i < sectionmaxsize; ++i) + last *= 10; + } + // Even those *= 10s can't take last above absMax: + Q_ASSERT(last <= absMax); + const int absMin = absoluteMin(sectionIndex); + if (last < absMin) { + if (unfilled) + result = ParsedSection(Intermediate, last, used); + else + QDTPDEBUG << "invalid because" << last << "is less than absoluteMin" << absMin; + } else if (unfilled && (fi & (FixedWidth|Numeric)) == (FixedWidth|Numeric)) { + if (skipToNextSection(sectionIndex, currentValue, digitsStr)) { + const int missingZeroes = sectionmaxsize - digitsStr.size(); + result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes); + text->insert(offset, QString(missingZeroes, QLatin1Char('0'))); + ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded); + } else { + result = ParsedSection(Intermediate, last, used);; + } + } else { + result = ParsedSection(Acceptable, last, used); + } + } + } + break; } + default: + qWarning("QDateTimeParser::parseSection Internal error (%ls %d)", + qUtf16Printable(sn.name()), sectionIndex); + return result; + } + Q_ASSERT(result.state != Invalid || result.value == -1); + + return result; +} + +/*! + \internal + + Returns a date consistent with the given data on parts specified by known, + while staying as close to the given data as it can. Returns an invalid date + when on valid date is consistent with the data. +*/ + +static QDate actualDate(QDateTimeParser::Sections known, int year, int year2digits, + int month, int day, int dayofweek) +{ + QDate actual(year, month, day); + if (actual.isValid() && year % 100 == year2digits && actual.dayOfWeek() == dayofweek) + return actual; // The obvious candidate is fine :-) + + if (dayofweek < 1 || dayofweek > 7) // Invalid: ignore + known &= ~QDateTimeParser::DayOfWeekSectionMask; + + // Assuming year > 0 ... + if (year % 100 != year2digits) { + if (known & QDateTimeParser::YearSection2Digits) { + // Over-ride year, even if specified: + year += year2digits - year % 100; + known &= ~QDateTimeParser::YearSection; + } else { + year2digits = year % 100; + } + } + Q_ASSERT(year % 100 == year2digits); + + if (month < 1) { // If invalid, clip to nearest valid and ignore in known. + month = 1; + known &= ~QDateTimeParser::MonthSection; + } else if (month > 12) { + month = 12; + known &= ~QDateTimeParser::MonthSection; + } + + QDate first(year, month, 1); + int last = known & QDateTimeParser::YearSection && known & QDateTimeParser::MonthSection + ? first.daysInMonth() : 0; + // If we also know day-of-week, tweak last to the last in the month that matches it: + if (last && known & QDateTimeParser::DayOfWeekSectionMask) { + int diff = (dayofweek - first.dayOfWeek() - last) % 7; + Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0. + last += diff; + } + if (day < 1) { + if (known & QDateTimeParser::DayOfWeekSectionMask && last) { + day = 1 + dayofweek - first.dayOfWeek(); + if (day < 1) + day += 7; + } else { + day = 1; + } + known &= ~QDateTimeParser::DaySection; + } else if (day > 31) { + day = last; + known &= ~QDateTimeParser::DaySection; + } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) { + day = last; + } + + actual = QDate(year, month, day); + if (!actual.isValid() // We can't do better than we have, in this case + || (known & QDateTimeParser::DaySection + && known & QDateTimeParser::MonthSection + && known & QDateTimeParser::YearSection) // ditto + || actual.dayOfWeek() == dayofweek // Good enough, use it. + || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) { // No contradiction, use it. + return actual; + } + + /* + Now it gets trickier. + + We have some inconsistency in our data; we've been told day of week, but + it doesn't fit with our year, month and day. At least one of these is + unknown, though: so we can fix day of week by tweaking it. + */ + + if ((known & QDateTimeParser::DaySection) == 0) { + // Relatively easy to fix. + day += dayofweek - actual.dayOfWeek(); + if (day < 1) + day += 7; + else if (day > actual.daysInMonth()) + day -= 7; + actual = QDate(year, month, day); + return actual; + } + + if ((known & QDateTimeParser::MonthSection) == 0) { + /* + Try possible month-offsets, m, preferring small; at least one (present + month doesn't work) and at most 11 (max month, 12, minus min, 1); try + in both directions, ignoring any offset that takes us out of range. + */ + for (int m = 1; m < 12; m++) { + if (m < month) { + actual = QDate(year, month - m, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + if (m + month <= 12) { + actual = QDate(year, month + m, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + } + // Should only get here in corner cases; e.g. day == 31 + actual = QDate(year, month, day); // Restore from trial values. + } + + if ((known & QDateTimeParser::YearSection) == 0) { + if (known & QDateTimeParser::YearSection2Digits) { + /* + Two-digit year and month are specified; choice of century can only + fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if + diff is in the other. It's also only reasonable to consider + adjacent century, e.g. if year thinks it's 2012 and two-digit year + is '97, it makes sense to consider 1997. If either adjacent + century does work, the other won't. + */ + actual = QDate(year + 100, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + actual = QDate(year - 100, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } else { + // Offset by 7 is usually enough, but rare cases may need more: + for (int y = 1; y < 12; y++) { + actual = QDate(year - y, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + actual = QDate(year + y, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + } + actual = QDate(year, month, day); // Restore from trial values. + } + + return actual; // It'll just have to do :-( +} + +/*! + \internal +*/ + +static QTime actualTime(QDateTimeParser::Sections known, + int hour, int hour12, int ampm, + int minute, int second, int msec) +{ + // If we have no conflict, or don't know enough to diagonose one, use this: + QTime actual(hour, minute, second, msec); + if (hour12 < 0 || hour12 > 12) { // ignore bogus value + known &= ~QDateTimeParser::Hour12Section; + hour12 = hour % 12; + } + + if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) { + if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12) + return actual; + + if ((known & QDateTimeParser::Hour24Section) == 0) + hour = hour12 + (hour > 12 ? 12 : 0); + } else { + Q_ASSERT(ampm == 0 || ampm == 1); + if (hour - hour12 == ampm * 12) + return actual; + + if ((known & QDateTimeParser::Hour24Section) == 0 + && known & QDateTimeParser::Hour12Section) { + hour = hour12 + ampm * 12; + } + } + actual = QTime(hour, minute, second, msec); + return actual; +} + +/*! + \internal +*/ +QDateTimeParser::StateNode +QDateTimeParser::scanString(const QDateTime &defaultValue, + bool fixup, QString *input) const +{ + State state = Acceptable; + bool conflicts = false; + const int sectionNodesCount = sectionNodes.size(); + int padding = 0; + int pos = 0; + int year, month, day; + const QDate defaultDate = defaultValue.date(); + const QTime defaultTime = defaultValue.time(); + defaultDate.getDate(&year, &month, &day); + int year2digits = year % 100; + int hour = defaultTime.hour(); + int hour12 = -1; + int minute = defaultTime.minute(); + int second = defaultTime.second(); + int msec = defaultTime.msec(); + int dayofweek = defaultDate.dayOfWeek(); + Qt::TimeSpec tspec = defaultValue.timeSpec(); + int zoneOffset = 0; // In seconds; local - UTC +#if QT_CONFIG(timezone) + QTimeZone timeZone; +#endif + switch (tspec) { + case Qt::OffsetFromUTC: // timeZone is ignored + zoneOffset = defaultValue.offsetFromUtc(); + break; +#if QT_CONFIG(timezone) + case Qt::TimeZone: + timeZone = defaultValue.timeZone(); + if (timeZone.isValid()) + zoneOffset = timeZone.offsetFromUtc(defaultValue); + // else: is there anything we can do about this ? + break; +#endif + default: // zoneOffset and timeZone are ignored + break; + } + + int ampm = -1; + Sections isSet = NoSection; + + for (int index = 0; index < sectionNodesCount; ++index) { + Q_ASSERT(state != Invalid); + const QString &separator = separators.at(index); + if (input->midRef(pos, separator.size()) != separator) { + QDTPDEBUG << "invalid because" << input->midRef(pos, separator.size()) + << "!=" << separator + << index << pos << currentSectionIndex; + return StateNode(); + } + pos += separator.size(); + sectionNodes[index].pos = pos; + int *current = 0; + const SectionNode sn = sectionNodes.at(index); + ParsedSection sect; + + { + const QDate date = actualDate(isSet, year, year2digits, month, day, dayofweek); + const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec); + sect = parseSection( +#if QT_CONFIG(timezone) + tspec == Qt::TimeZone ? QDateTime(date, time, timeZone) : +#endif + QDateTime(date, time, tspec, zoneOffset), + index, pos, input); + } + + QDTPDEBUG << "sectionValue" << sn.name() << *input + << "pos" << pos << "used" << sect.used << stateName(sect.state); + + padding += sect.zeroes; + if (fixup && sect.state == Intermediate && sect.used < sn.count) { + const FieldInfo fi = fieldInfo(index); + if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) { + const QString newText = QString::fromLatin1("%1").arg(sect.value, sn.count, 10, QLatin1Char('0')); + input->replace(pos, sect.used, newText); + sect.used = sn.count; + } + } + + state = qMin<State>(state, sect.state); + // QDateTimeEdit can fix Intermediate and zeroes, but input needing that didn't match format: + if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes))) + return StateNode(); + + switch (sn.type) { + case TimeZoneSection: + current = &zoneOffset; + if (sect.used > 0) { +#if QT_CONFIG(timezone) // Synchronize with what findTimeZone() found: + QStringRef zoneName = input->midRef(pos, sect.used); + Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0 + const QByteArray latinZone(zoneName == QLatin1String("Z") + ? QByteArray("UTC") : zoneName.toLatin1()); + timeZone = QTimeZone(latinZone); + tspec = timeZone.isValid() + ? (QTimeZone::isTimeZoneIdAvailable(latinZone) + ? Qt::TimeZone + : Qt::OffsetFromUTC) + : (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime); +#else + tspec = Qt::LocalTime; +#endif + } + break; + case Hour24Section: current = &hour; break; + case Hour12Section: current = &hour12; break; + case MinuteSection: current = &minute; break; + case SecondSection: current = &second; break; + case MSecSection: current = &msec; break; + case YearSection: current = &year; break; + case YearSection2Digits: current = &year2digits; break; + case MonthSection: current = &month; break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: current = &dayofweek; break; + case DaySection: current = &day; sect.value = qMax<int>(1, sect.value); break; + case AmPmSection: current = &m; break; + default: + qWarning("QDateTimeParser::parse Internal error (%ls)", + qUtf16Printable(sn.name())); + break; + } + + if (sect.used > 0) + pos += sect.used; + QDTPDEBUG << index << sn.name() << "is set to" + << pos << "state is" << stateName(state); + + if (!current) { + qWarning("QDateTimeParser::parse Internal error 2"); + return StateNode(); + } + if (isSet & sn.type && *current != sect.value) { + QDTPDEBUG << "CONFLICT " << sn.name() << *current << sect.value; + conflicts = true; + if (index != currentSectionIndex || sect.state == Invalid) { + continue; + } + } + if (sect.state != Invalid) + *current = sect.value; + + // Record the present section: + isSet |= sn.type; + } + + if (input->midRef(pos) != separators.last()) { + QDTPDEBUG << "invalid because" << input->midRef(pos) + << "!=" << separators.last() << pos; + return StateNode(); + } + + if (parserType != QVariant::Time) { + if (year % 100 != year2digits && (isSet & YearSection2Digits)) { + if (!(isSet & YearSection)) { + year = (year / 100) * 100; + year += year2digits; + } else { + conflicts = true; + const SectionNode &sn = sectionNode(currentSectionIndex); + if (sn.type == YearSection2Digits) { + year = (year / 100) * 100; + year += year2digits; + } + } + } + + const QDate date(year, month, day); + const int diff = dayofweek - date.dayOfWeek(); + if (diff != 0 && state == Acceptable && isSet & DayOfWeekSectionMask) { + if (isSet & DaySection) + conflicts = true; + const SectionNode &sn = sectionNode(currentSectionIndex); + if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) { + // dayofweek should be preferred + day += diff; + if (day <= 0) { + day += 7; + } else if (day > date.daysInMonth()) { + day -= 7; + } + QDTPDEBUG << year << month << day << dayofweek + << diff << QDate(year, month, day).dayOfWeek(); + } + } + + bool needfixday = false; + if (sectionType(currentSectionIndex) & DaySectionMask) { + cachedDay = day; + } else if (cachedDay > day) { + day = cachedDay; + needfixday = true; + } + + if (!QDate::isValid(year, month, day)) { + if (day < 32) { + cachedDay = day; + } + if (day > 28 && QDate::isValid(year, month, 1)) { + needfixday = true; + } + } + if (needfixday) { + if (context == FromString) { + return StateNode(); + } + if (state == Acceptable && fixday) { + day = qMin<int>(day, QDate(year, month, 1).daysInMonth()); + + const QLocale loc = locale(); + for (int i=0; i<sectionNodesCount; ++i) { + const SectionNode sn = sectionNode(i); + if (sn.type & DaySection) { + input->replace(sectionPos(sn), sectionSize(i), loc.toString(day)); + } else if (sn.type & DayOfWeekSectionMask) { + const int dayOfWeek = QDate(year, month, day).dayOfWeek(); + const QLocale::FormatType dayFormat = + (sn.type == DayOfWeekSectionShort + ? QLocale::ShortFormat : QLocale::LongFormat); + const QString dayName(loc.dayName(dayOfWeek, dayFormat)); + input->replace(sectionPos(sn), sectionSize(i), dayName); + } + } + } else if (state > Intermediate) { + state = Intermediate; + } + } + } + + if (parserType != QVariant::Date) { + if (isSet & Hour12Section) { + const bool hasHour = isSet & Hour24Section; + if (ampm == -1) { + if (hasHour) { + ampm = (hour < 12 ? 0 : 1); + } else { + ampm = 0; // no way to tell if this is am or pm so I assume am + } + } + hour12 = (ampm == 0 ? hour12 % 12 : (hour12 % 12) + 12); + if (!hasHour) { + hour = hour12; + } else if (hour != hour12) { + conflicts = true; + } + } else if (ampm != -1) { + if (!(isSet & (Hour24Section))) { + hour = (12 * ampm); // special case. Only ap section + } else if ((ampm == 0) != (hour < 12)) { + conflicts = true; + } + } + + } + + QDTPDEBUG << year << month << day << hour << minute << second << msec; + Q_ASSERT(state != Invalid); + + const QDate date(year, month, day); + const QTime time(hour, minute, second, msec); + const QDateTime when = +#if QT_CONFIG(timezone) + tspec == Qt::TimeZone ? QDateTime(date, time, timeZone) : +#endif + QDateTime(date, time, tspec, zoneOffset); + + // If hour wasn't specified, check the default we're using exists on the + // given date (which might be a spring-forward, skipping an hour). + if (parserType == QVariant::DateTime && !(isSet & HourSectionMask) && !when.isValid()) { + qint64 msecs = when.toMSecsSinceEpoch(); + // Fortunately, that gets a useful answer ... + const QDateTime replace = +#if QT_CONFIG(timezone) + tspec == Qt::TimeZone + ? QDateTime::fromMSecsSinceEpoch(msecs, timeZone) : +#endif + QDateTime::fromMSecsSinceEpoch(msecs, tspec, zoneOffset); + const QTime tick = replace.time(); + if (replace.date() == date + && (!(isSet & MinuteSection) || tick.minute() == minute) + && (!(isSet & SecondSection) || tick.second() == second) + && (!(isSet & MSecSection) || tick.msec() == msec)) { + return StateNode(replace, state, padding, conflicts); + } + } + + return StateNode(when, state, padding, conflicts); +} + +/*! + \internal +*/ + +QDateTimeParser::StateNode +QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const +{ + const QDateTime minimum = getMinimum(); + const QDateTime maximum = getMaximum(); + + QDTPDEBUG << "parse" << input; + StateNode scan = scanString(defaultValue, fixup, &input); + QDTPDEBUGN("'%s' => '%s'(%s)", input.toLatin1().constData(), + scan.value.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(), + stateName(scan.state).toLatin1().constData()); + + if (scan.value.isValid() && scan.state != Invalid) { + if (context != FromString && scan.value < minimum) { + const QLatin1Char space(' '); + if (scan.value >= minimum) + qWarning("QDateTimeParser::parse Internal error 3 (%ls %ls)", + qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString())); + + bool done = false; + scan.state = Invalid; + const int sectionNodesCount = sectionNodes.size(); + for (int i=0; i<sectionNodesCount && !done; ++i) { + const SectionNode &sn = sectionNodes.at(i); + QString t = sectionText(input, i, sn.pos).toLower(); + if ((t.size() < sectionMaxSize(i) + && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) + || t.contains(space)) { + switch (sn.type) { + case AmPmSection: + switch (findAmPm(t, i)) { + case AM: + case PM: + scan.state = Acceptable; + done = true; + break; + case Neither: + scan.state = Invalid; + done = true; + break; + case PossibleAM: + case PossiblePM: + case PossibleBoth: { + const QDateTime copy(scan.value.addSecs(12 * 60 * 60)); + if (copy >= minimum && copy <= maximum) { + scan.state = Intermediate; + done = true; + } + break; } + } + Q_FALLTHROUGH(); + case MonthSection: + if (sn.count >= 3) { + const int finalMonth = scan.value.date().month(); + int tmp = finalMonth; + // I know the first possible month makes the date too early + while ((tmp = findMonth(t, tmp + 1, i)) != -1) { + const QDateTime copy(scan.value.addMonths(tmp - finalMonth)); + if (copy >= minimum && copy <= maximum) + break; // break out of while + } + if (tmp != -1) { + scan.state = Intermediate; + done = true; + } + break; + } + Q_FALLTHROUGH(); + default: { + int toMin; + int toMax; + + if (sn.type & TimeSectionMask) { + if (scan.value.daysTo(minimum) != 0) { + break; + } + const QTime time = scan.value.time(); + toMin = time.msecsTo(minimum.time()); + if (scan.value.daysTo(maximum) > 0) + toMax = -1; // can't get to max + else + toMax = time.msecsTo(maximum.time()); + } else { + toMin = scan.value.daysTo(minimum); + toMax = scan.value.daysTo(maximum); + } + const int maxChange = sn.maxChange(); + if (toMin > maxChange) { + QDTPDEBUG << "invalid because toMin > maxChange" << toMin + << maxChange << t << scan.value << minimum; + scan.state = Invalid; + done = true; + break; + } else if (toMax > maxChange) { + toMax = -1; // can't get to max + } + + const int min = getDigit(minimum, i); + if (min == -1) { + qWarning("QDateTimeParser::parse Internal error 4 (%ls)", + qUtf16Printable(sn.name())); + scan.state = Invalid; + done = true; + break; + } + + int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value); + int pos = position + scan.padded - sn.pos; + if (pos < 0 || pos >= t.size()) + pos = -1; + if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) { + QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max + << sn.name() << "returned" << toMax << toMin << pos; + scan.state = Invalid; + done = true; + break; + } + scan.state = Intermediate; + done = true; + break; } + } + } + } + } else { + if (context == FromString) { + // optimization + Q_ASSERT(maximum.date().toJulianDay() == 5373484); + if (scan.value.date().toJulianDay() > 5373484) + scan.state = Invalid; + } else { + if (scan.value > maximum) + scan.state = Invalid; + } + + QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum; + } + } + text = scan.input = input; + // Set spec *after* all checking, so validity is a property of the string: + scan.value = scan.value.toTimeSpec(spec); + return scan; +} + +/* + \internal + \brief Returns the index in \a entries with the best prefix match to \a text + + Scans \a entries looking for an entry overlapping \a text as much as possible + (an exact match beats any prefix match; a match of the full entry as prefix of + text beats any entry but one matching a longer prefix; otherwise, the match of + longest prefix wins, earlier entries beating later on a draw). Records the + length of overlap in *used (if \a used is non-NULL) and the first entry that + overlapped this much in *usedText (if \a usedText is non-NULL). + */ +static int findTextEntry(const QString &text, const QVector<QString> &entries, QString *usedText, int *used) +{ + if (text.isEmpty()) + return -1; + + int bestMatch = -1; + int bestCount = 0; + for (int n = 0; n < entries.size(); ++n) + { + const QString &name = entries.at(n); + + const int limit = qMin(text.size(), name.size()); + int i = 0; + while (i < limit && text.at(i) == name.at(i).toLower()) + ++i; + // Full match beats an equal prefix match: + if (i > bestCount || (i == bestCount && i == name.size())) { + bestCount = i; + bestMatch = n; + if (i == name.size() && i == text.size()) + break; // Exact match, name == text, wins. + } + } + if (usedText && bestMatch != -1) + *usedText = entries.at(bestMatch); + if (used) + *used = bestCount; + + return bestMatch; +} + +/*! + \internal + finds the first possible monthname that \a str1 can + match. Starting from \a index; str should already by lowered +*/ + +int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex, + QString *usedMonth, int *used) const +{ + const SectionNode &sn = sectionNode(sectionIndex); + if (sn.type != MonthSection) { + qWarning("QDateTimeParser::findMonth Internal error"); + return -1; + } + + QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat; + QLocale l = locale(); + QVector<QString> monthNames; + monthNames.reserve(13 - startMonth); + for (int month = startMonth; month <= 12; ++month) + monthNames.append(l.monthName(month, type)); + + const int index = findTextEntry(str1, monthNames, usedMonth, used); + return index < 0 ? index : index + startMonth; +} + +int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const +{ + const SectionNode &sn = sectionNode(sectionIndex); + if (!(sn.type & DaySectionMask)) { + qWarning("QDateTimeParser::findDay Internal error"); + return -1; + } + + QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; + QLocale l = locale(); + QVector<QString> daysOfWeek; + daysOfWeek.reserve(8 - startDay); + for (int day = startDay; day <= 7; ++day) + daysOfWeek.append(l.dayName(day, type)); + + const int index = findTextEntry(str1, daysOfWeek, usedDay, used); + return index < 0 ? index : index + startDay; +} + +/*! + \internal + + Return's .value is zone's offset, zone time - UTC time, in seconds. + See QTimeZonePrivate::isValidId() for the format of zone names. + */ +QDateTimeParser::ParsedSection +QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when, + int maxVal, int minVal) const +{ +#if QT_CONFIG(timezone) + int index = startsWithLocalTimeZone(str); + int offset; + + if (index > 0) { + // We won't actually use this, but we need a valid return: + offset = QDateTime(when.date(), when.time(), Qt::LocalTime).offsetFromUtc(); + } else { + int size = str.length(); + offset = std::numeric_limits<int>::max(); // deliberately out of range + Q_ASSERT(offset > QTimeZone::MaxUtcOffsetSecs); // cf. absoluteMax() + + // Collect up plausibly-valid characters; let QTimeZone work out what's truly valid. + while (index < size) { + QChar here = str[index]; + if (here < 127 + && (here.isLetterOrNumber() + || here == '/' || here == '-' + || here == '_' || here == '.' + || here == '+' || here == ':')) + index++; + else + break; + } + + while (index > 0) { + str.truncate(index); + if (str == QLatin1String("Z")) { + offset = 0; // "Zulu" time - a.k.a. UTC + break; + } + QTimeZone zone(str.toLatin1()); + if (zone.isValid()) { + offset = zone.offsetFromUtc(when); + break; + } + index--; // maybe we collected too much ... + } + } + + if (index > 0 && maxVal >= offset && offset >= minVal) + return ParsedSection(Acceptable, offset, index); + +#endif // timezone + return ParsedSection(); +} + +/*! + \internal + + Returns + AM if str == tr("AM") + PM if str == tr("PM") + PossibleAM if str can become tr("AM") + PossiblePM if str can become tr("PM") + PossibleBoth if str can become tr("PM") and can become tr("AM") + Neither if str can't become anything sensible +*/ +QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const +{ + const SectionNode &s = sectionNode(sectionIndex); + if (s.type != AmPmSection) { + qWarning("QDateTimeParser::findAmPm Internal error"); + return Neither; + } + if (used) + *used = str.size(); + if (QStringRef(&str).trimmed().isEmpty()) { + return PossibleBoth; + } + const QLatin1Char space(' '); + int size = sectionMaxSize(sectionIndex); + + enum { + amindex = 0, + pmindex = 1 + }; + QString ampm[2]; + ampm[amindex] = getAmPmText(AmText, s.count == 1 ? UpperCase : LowerCase); + ampm[pmindex] = getAmPmText(PmText, s.count == 1 ? UpperCase : LowerCase); + for (int i=0; i<2; ++i) + ampm[i].truncate(size); + + QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1]; + + if (str.indexOf(ampm[amindex], 0, Qt::CaseInsensitive) == 0) { + str = ampm[amindex]; + return AM; + } else if (str.indexOf(ampm[pmindex], 0, Qt::CaseInsensitive) == 0) { + str = ampm[pmindex]; + return PM; + } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) { + return Neither; + } + size = qMin(size, str.size()); + + bool broken[2] = {false, false}; + for (int i=0; i<size; ++i) { + if (str.at(i) != space) { + for (int j=0; j<2; ++j) { + if (!broken[j]) { + int index = ampm[j].indexOf(str.at(i)); + QDTPDEBUG << "looking for" << str.at(i) + << "in" << ampm[j] << "and got" << index; + if (index == -1) { + if (str.at(i).category() == QChar::Letter_Uppercase) { + index = ampm[j].indexOf(str.at(i).toLower()); + QDTPDEBUG << "trying with" << str.at(i).toLower() + << "in" << ampm[j] << "and got" << index; + } else if (str.at(i).category() == QChar::Letter_Lowercase) { + index = ampm[j].indexOf(str.at(i).toUpper()); + QDTPDEBUG << "trying with" << str.at(i).toUpper() + << "in" << ampm[j] << "and got" << index; + } + if (index == -1) { + broken[j] = true; + if (broken[amindex] && broken[pmindex]) { + QDTPDEBUG << str << "didn't make it"; + return Neither; + } + continue; + } else { + str[i] = ampm[j].at(index); // fix case + } + } + ampm[j].remove(index, 1); + } + } + } + } + if (!broken[pmindex] && !broken[amindex]) + return PossibleBoth; + return (!broken[amindex] ? PossibleAM : PossiblePM); +} +#endif // datestring + +/*! + \internal + Max number of units that can be changed by this section. +*/ + +int QDateTimeParser::SectionNode::maxChange() const +{ + switch (type) { + // Time. unit is msec + case MSecSection: return 999; + case SecondSection: return 59 * 1000; + case MinuteSection: return 59 * 60 * 1000; + case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000; + + // Date. unit is day + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: return 7; + case DaySection: return 30; + case MonthSection: return 365 - 31; + case YearSection: return 9999 * 365; + case YearSection2Digits: return 100 * 365; + default: + qWarning("QDateTimeParser::maxChange() Internal error (%ls)", + qUtf16Printable(name())); + } + + return -1; +} + +QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const +{ + FieldInfo ret = 0; + const SectionNode &sn = sectionNode(index); + switch (sn.type) { + case MSecSection: + ret |= Fraction; + Q_FALLTHROUGH(); + case SecondSection: + case MinuteSection: + case Hour24Section: + case Hour12Section: + case YearSection2Digits: + ret |= AllowPartial; + Q_FALLTHROUGH(); + case YearSection: + ret |= Numeric; + if (sn.count != 1) + ret |= FixedWidth; + break; + case MonthSection: + case DaySection: + switch (sn.count) { + case 2: + ret |= FixedWidth; + Q_FALLTHROUGH(); + case 1: + ret |= (Numeric|AllowPartial); + break; + } + break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + if (sn.count == 3) + ret |= FixedWidth; + break; + case AmPmSection: + ret |= FixedWidth; + break; + case TimeZoneSection: + break; + default: + qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)", + index, qUtf16Printable(sn.name()), sn.count); + break; + } + return ret; +} + +QString QDateTimeParser::SectionNode::format() const +{ + QChar fillChar; + switch (type) { + case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap"); + case MSecSection: fillChar = QLatin1Char('z'); break; + case SecondSection: fillChar = QLatin1Char('s'); break; + case MinuteSection: fillChar = QLatin1Char('m'); break; + case Hour24Section: fillChar = QLatin1Char('H'); break; + case Hour12Section: fillChar = QLatin1Char('h'); break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + case DaySection: fillChar = QLatin1Char('d'); break; + case MonthSection: fillChar = QLatin1Char('M'); break; + case YearSection2Digits: + case YearSection: fillChar = QLatin1Char('y'); break; + default: + qWarning("QDateTimeParser::sectionFormat Internal error (%ls)", + qUtf16Printable(name(type))); + return QString(); + } + if (fillChar.isNull()) { + qWarning("QDateTimeParser::sectionFormat Internal error 2"); + return QString(); + } + return QString(count, fillChar); +} + + +/*! + \internal + + Returns \c true if str can be modified to represent a + number that is within min and max. +*/ + +bool QDateTimeParser::potentialValue(const QStringRef &str, int min, int max, int index, + const QDateTime ¤tValue, int insert) const +{ + if (str.isEmpty()) { + return true; + } + const int size = sectionMaxSize(index); + int val = (int)locale().toUInt(str); + const SectionNode &sn = sectionNode(index); + if (sn.type == YearSection2Digits) { + const int year = currentValue.date().year(); + val += year - (year % 100); + } + if (val >= min && val <= max && str.size() == size) { + return true; + } else if (val > max) { + return false; + } else if (str.size() == size && val < min) { + return false; + } + + const int len = size - str.size(); + for (int i=0; i<len; ++i) { + for (int j=0; j<10; ++j) { + if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) { + return true; + } else if (insert >= 0) { + const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert); + if (potentialValue(tmp, min, max, index, currentValue, insert)) + return true; + } + } + } + + return false; +} + +/*! + \internal +*/ +bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QStringRef &text) const +{ + Q_ASSERT(text.size() < sectionMaxSize(index)); + const SectionNode &node = sectionNode(index); + int min = absoluteMin(index); + int max = absoluteMax(index, current); + // Time-zone field is only numeric if given as offset from UTC: + if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) { + const QDateTime maximum = getMaximum(); + const QDateTime minimum = getMinimum(); + Q_ASSERT(current >= minimum && current <= maximum); + + QDateTime tmp = current; + if (!setDigit(tmp, index, min) || tmp < minimum) + min = getDigit(minimum, index); + + if (!setDigit(tmp, index, max) || tmp > maximum) + max = getDigit(maximum, index); + } + int pos = cursorPosition() - node.pos; + if (pos < 0 || pos >= text.size()) + pos = -1; + + /* + If the value potentially can become another valid entry we don't want to + skip to the next. E.g. In a M field (month without leading 0) if you type + 1 we don't want to autoskip (there might be [012] following) but if you + type 3 we do. + */ + return !potentialValue(text, min, max, index, current, pos); +} + +/*! + \internal + For debugging. Returns the name of the section \a s. +*/ + +QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s) +{ + switch (s) { + case QDateTimeParser::AmPmSection: return QLatin1String("AmPmSection"); + case QDateTimeParser::DaySection: return QLatin1String("DaySection"); + case QDateTimeParser::DayOfWeekSectionShort: return QLatin1String("DayOfWeekSectionShort"); + case QDateTimeParser::DayOfWeekSectionLong: return QLatin1String("DayOfWeekSectionLong"); + case QDateTimeParser::Hour24Section: return QLatin1String("Hour24Section"); + case QDateTimeParser::Hour12Section: return QLatin1String("Hour12Section"); + case QDateTimeParser::MSecSection: return QLatin1String("MSecSection"); + case QDateTimeParser::MinuteSection: return QLatin1String("MinuteSection"); + case QDateTimeParser::MonthSection: return QLatin1String("MonthSection"); + case QDateTimeParser::SecondSection: return QLatin1String("SecondSection"); + case QDateTimeParser::TimeZoneSection: return QLatin1String("TimeZoneSection"); + case QDateTimeParser::YearSection: return QLatin1String("YearSection"); + case QDateTimeParser::YearSection2Digits: return QLatin1String("YearSection2Digits"); + case QDateTimeParser::NoSection: return QLatin1String("NoSection"); + case QDateTimeParser::FirstSection: return QLatin1String("FirstSection"); + case QDateTimeParser::LastSection: return QLatin1String("LastSection"); + default: return QLatin1String("Unknown section ") + QString::number(int(s)); + } +} + +/*! + \internal + For debugging. Returns the name of the state \a s. +*/ + +QString QDateTimeParser::stateName(State s) const +{ + switch (s) { + case Invalid: return QLatin1String("Invalid"); + case Intermediate: return QLatin1String("Intermediate"); + case Acceptable: return QLatin1String("Acceptable"); + default: return QLatin1String("Unknown state ") + QString::number(s); + } +} + +#if QT_CONFIG(datestring) +bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const +{ + QDateTime val(QDate(1900, 1, 1).startOfDay()); + const StateNode tmp = parse(t, -1, val, false); + if (tmp.state != Acceptable || tmp.conflicts) { + return false; + } + if (time) { + const QTime t = tmp.value.time(); + if (!t.isValid()) { + return false; + } + *time = t; + } + + if (date) { + const QDate d = tmp.value.date(); + if (!d.isValid()) { + return false; + } + *date = d; + } + return true; +} +#endif // datestring + +QDateTime QDateTimeParser::getMinimum() const +{ + // Cache the most common case + if (spec == Qt::LocalTime) { + static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime)); + return localTimeMin; + } + return QDateTime(QDATETIMEEDIT_DATE_MIN.startOfDay(spec)); +} + +QDateTime QDateTimeParser::getMaximum() const +{ + // Cache the most common case + if (spec == Qt::LocalTime) { + static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime)); + return localTimeMax; + } + return QDateTime(QDATETIMEEDIT_DATE_MAX.endOfDay(spec)); +} + +QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const +{ + const QLocale loc = locale(); + QString raw = ap == AmText ? loc.amText() : loc.pmText(); + return cs == UpperCase ? raw.toUpper() : raw.toLower(); +} + +/* + \internal + + I give arg2 preference because arg1 is always a QDateTime. +*/ + +bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2) +{ + return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count); +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qdatetimeparser_p.h b/src/corelib/time/qdatetimeparser_p.h new file mode 100644 index 0000000000..d9e39f0795 --- /dev/null +++ b/src/corelib/time/qdatetimeparser_p.h @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIMEPARSER_P_H +#define QDATETIMEPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include "qplatformdefs.h" +#include "QtCore/qatomic.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qstringlist.h" +#include "QtCore/qlocale.h" +#ifndef QT_BOOTSTRAPPED +# include "QtCore/qvariant.h" +#endif +#include "QtCore/qvector.h" +#include "QtCore/qcoreapplication.h" + +QT_REQUIRE_CONFIG(datetimeparser); + +#define QDATETIMEEDIT_TIME_MIN QTime(0, 0) // Prefer QDate::startOfDay() +#define QDATETIMEEDIT_TIME_MAX QTime(23, 59, 59, 999) // Prefer QDate::endOfDay() +#define QDATETIMEEDIT_DATE_MIN QDate(100, 1, 1) +#define QDATETIMEEDIT_COMPAT_DATE_MIN QDate(1752, 9, 14) +#define QDATETIMEEDIT_DATE_MAX QDate(9999, 12, 31) +#define QDATETIMEEDIT_DATE_INITIAL QDate(2000, 1, 1) + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QDateTimeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDateTimeParser) +public: + enum Context { + FromString, + DateTimeEdit + }; + QDateTimeParser(QVariant::Type t, Context ctx) + : currentSectionIndex(-1), display(nullptr), cachedDay(-1), parserType(t), + fixday(false), spec(Qt::LocalTime), context(ctx) + { + defaultLocale = QLocale::system(); + first.type = FirstSection; + first.pos = -1; + first.count = -1; + first.zeroesAdded = 0; + last.type = LastSection; + last.pos = -1; + last.count = -1; + last.zeroesAdded = 0; + none.type = NoSection; + none.pos = -1; + none.count = -1; + none.zeroesAdded = 0; + } + virtual ~QDateTimeParser(); + + enum Section { + NoSection = 0x00000, + AmPmSection = 0x00001, + MSecSection = 0x00002, + SecondSection = 0x00004, + MinuteSection = 0x00008, + Hour12Section = 0x00010, + Hour24Section = 0x00020, + TimeZoneSection = 0x00040, + HourSectionMask = (Hour12Section | Hour24Section), + TimeSectionMask = (MSecSection | SecondSection | MinuteSection | + HourSectionMask | AmPmSection | TimeZoneSection), + + DaySection = 0x00100, + MonthSection = 0x00200, + YearSection = 0x00400, + YearSection2Digits = 0x00800, + YearSectionMask = YearSection | YearSection2Digits, + DayOfWeekSectionShort = 0x01000, + DayOfWeekSectionLong = 0x02000, + DayOfWeekSectionMask = DayOfWeekSectionShort | DayOfWeekSectionLong, + DaySectionMask = DaySection | DayOfWeekSectionMask, + DateSectionMask = DaySectionMask | MonthSection | YearSectionMask, + + Internal = 0x10000, + FirstSection = 0x20000 | Internal, + LastSection = 0x40000 | Internal, + CalendarPopupSection = 0x80000 | Internal, + + NoSectionIndex = -1, + FirstSectionIndex = -2, + LastSectionIndex = -3, + CalendarPopupIndex = -4 + }; // extending qdatetimeedit.h's equivalent + Q_DECLARE_FLAGS(Sections, Section) + + struct Q_CORE_EXPORT SectionNode { + Section type; + mutable int pos; + int count; + int zeroesAdded; + + static QString name(Section s); + QString name() const { return name(type); } + QString format() const; + int maxChange() const; + }; + + enum State { // duplicated from QValidator + Invalid, + Intermediate, + Acceptable + }; + + struct StateNode { + StateNode() : state(Invalid), padded(0), conflicts(false) {} + StateNode(const QDateTime &val, State ok=Acceptable, int pad=0, bool bad=false) + : value(val), state(ok), padded(pad), conflicts(bad) {} + QString input; + QDateTime value; + State state; + int padded; + bool conflicts; + }; + + enum AmPm { + AmText, + PmText + }; + + enum Case { + UpperCase, + LowerCase + }; + +#if QT_CONFIG(datestring) + StateNode parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const; + bool fromString(const QString &text, QDate *date, QTime *time) const; +#endif + bool parseFormat(const QString &format); + + enum FieldInfoFlag { + Numeric = 0x01, + FixedWidth = 0x02, + AllowPartial = 0x04, + Fraction = 0x08 + }; + Q_DECLARE_FLAGS(FieldInfo, FieldInfoFlag) + + FieldInfo fieldInfo(int index) const; + + void setDefaultLocale(const QLocale &loc) { defaultLocale = loc; } + virtual QString displayText() const { return text; } + +private: + int sectionMaxSize(Section s, int count) const; + QString sectionText(const QString &text, int sectionIndex, int index) const; +#if QT_CONFIG(datestring) + StateNode scanString(const QDateTime &defaultValue, + bool fixup, QString *input) const; + struct ParsedSection { + int value; + int used; + int zeroes; + State state; + Q_DECL_CONSTEXPR ParsedSection(State ok = Invalid, + int val = 0, int read = 0, int zs = 0) + : value(ok == Invalid ? -1 : val), used(read), zeroes(zs), state(ok) + {} + }; + ParsedSection parseSection(const QDateTime ¤tValue, int sectionIndex, + int offset, QString *text) const; + int findMonth(const QString &str1, int monthstart, int sectionIndex, + QString *monthName = nullptr, int *used = nullptr) const; + int findDay(const QString &str1, int intDaystart, int sectionIndex, + QString *dayName = nullptr, int *used = nullptr) const; + ParsedSection findTimeZone(QStringRef str, const QDateTime &when, + int maxVal, int minVal) const; +#if QT_CONFIG(timezone) + // Implemented in qdatetime.cpp: + static int startsWithLocalTimeZone(const QStringRef name); +#endif + + enum AmPmFinder { + Neither = -1, + AM = 0, + PM = 1, + PossibleAM = 2, + PossiblePM = 3, + PossibleBoth = 4 + }; + AmPmFinder findAmPm(QString &str, int index, int *used = nullptr) const; +#endif // datestring + + bool potentialValue(const QStringRef &str, int min, int max, int index, + const QDateTime ¤tValue, int insert) const; + bool potentialValue(const QString &str, int min, int max, int index, + const QDateTime ¤tValue, int insert) const + { + return potentialValue(QStringRef(&str), min, max, index, currentValue, insert); + } + +protected: // for the benefit of QDateTimeEditPrivate + int sectionSize(int index) const; + int sectionMaxSize(int index) const; + int sectionPos(int index) const; + int sectionPos(const SectionNode &sn) const; + + const SectionNode §ionNode(int index) const; + Section sectionType(int index) const; + QString sectionText(int sectionIndex) const; + int getDigit(const QDateTime &dt, int index) const; + bool setDigit(QDateTime &t, int index, int newval) const; + + int absoluteMax(int index, const QDateTime &value = QDateTime()) const; + int absoluteMin(int index) const; + + bool skipToNextSection(int section, const QDateTime ¤t, const QStringRef §ionText) const; + bool skipToNextSection(int section, const QDateTime ¤t, const QString §ionText) const + { + return skipToNextSection(section, current, QStringRef(§ionText)); + } + QString stateName(State s) const; + virtual QDateTime getMinimum() const; + virtual QDateTime getMaximum() const; + virtual int cursorPosition() const { return -1; } + virtual QString getAmPmText(AmPm ap, Case cs) const; + virtual QLocale locale() const { return defaultLocale; } + + mutable int currentSectionIndex; + Sections display; + /* + This stores the most recently selected day. + It is useful when considering the following scenario: + + 1. Date is: 31/01/2000 + 2. User increments month: 29/02/2000 + 3. User increments month: 31/03/2000 + + At step 1, cachedDay stores 31. At step 2, the 31 is invalid for February, so the cachedDay is not updated. + At step 3, the month is changed to March, for which 31 is a valid day. Since 29 < 31, the day is set to cachedDay. + This is good for when users have selected their desired day and are scrolling up or down in the month or year section + and do not want smaller months (or non-leap years) to alter the day that they chose. + */ + mutable int cachedDay; + mutable QString text; + QVector<SectionNode> sectionNodes; + SectionNode first, last, none, popup; + QStringList separators; + QString displayFormat; + QLocale defaultLocale; + QVariant::Type parserType; + bool fixday; + Qt::TimeSpec spec; // spec if used by QDateTimeEdit + Context context; +}; +Q_DECLARE_TYPEINFO(QDateTimeParser::SectionNode, Q_PRIMITIVE_TYPE); + +Q_CORE_EXPORT bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2); + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::Sections) +Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::FieldInfo) + +QT_END_NAMESPACE + +#endif // QDATETIME_P_H diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp new file mode 100644 index 0000000000..ef323de14a --- /dev/null +++ b/src/corelib/time/qtimezone.cpp @@ -0,0 +1,997 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdatetime.h> + +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +// Create default time zone using appropriate backend +static QTimeZonePrivate *newBackendTimeZone() +{ +#ifdef QT_NO_SYSTEMLOCALE +#if QT_CONFIG(icu) + return new QIcuTimeZonePrivate(); +#else + return new QUtcTimeZonePrivate(); +#endif +#else +#if defined Q_OS_MAC + return new QMacTimeZonePrivate(); +#elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) + return new QAndroidTimeZonePrivate(); +#elif defined(Q_OS_UNIX) || defined(Q_OS_ANDROID_EMBEDDED) + return new QTzTimeZonePrivate(); +#elif QT_CONFIG(icu) + return new QIcuTimeZonePrivate(); +#elif defined Q_OS_WIN + return new QWinTimeZonePrivate(); +#else + return new QUtcTimeZonePrivate(); +#endif // System Locales +#endif // QT_NO_SYSTEMLOCALE +} + +// Create named time zone using appropriate backend +static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) +{ +#ifdef QT_NO_SYSTEMLOCALE +#if QT_CONFIG(icu) + return new QIcuTimeZonePrivate(ianaId); +#else + return new QUtcTimeZonePrivate(ianaId); +#endif +#else +#if defined Q_OS_MAC + return new QMacTimeZonePrivate(ianaId); +#elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) + return new QAndroidTimeZonePrivate(ianaId); +#elif defined(Q_OS_UNIX) || defined(Q_OS_ANDROID_EMBEDDED) + return new QTzTimeZonePrivate(ianaId); +#elif QT_CONFIG(icu) + return new QIcuTimeZonePrivate(ianaId); +#elif defined Q_OS_WIN + return new QWinTimeZonePrivate(ianaId); +#else + return new QUtcTimeZonePrivate(ianaId); +#endif // System Locales +#endif // QT_NO_SYSTEMLOCALE +} + +class QTimeZoneSingleton +{ +public: + QTimeZoneSingleton() : backend(newBackendTimeZone()) {} + + // The backend_tz is the tz to use in static methods such as availableTimeZoneIds() and + // isTimeZoneIdAvailable() and to create named IANA time zones. This is usually the host + // system, but may be different if the host resources are insufficient or if + // QT_NO_SYSTEMLOCALE is set. A simple UTC backend is used if no alternative is available. + QSharedDataPointer<QTimeZonePrivate> backend; +}; + +Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); + +/*! + \class QTimeZone + \inmodule QtCore + \since 5.2 + + \brief The QTimeZone class converts between UTC and local time in a specific + time zone. + + \threadsafe + + This class provides a stateless calculator for time zone conversions + between UTC and the local time in a specific time zone. By default it uses + the host system time zone data to perform these conversions. + + This class is primarily designed for use in QDateTime; most applications + will not need to access this class directly and should instead use + QDateTime with a Qt::TimeSpec of Qt::TimeZone. + + \note For consistency with QDateTime, QTimeZone does not account for leap + seconds. + + \section1 Remarks + + \section2 IANA Time Zone IDs + + QTimeZone uses the IANA time zone IDs as defined in the IANA Time Zone + Database (http://www.iana.org/time-zones). This is to ensure a standard ID + across all supported platforms. Most platforms support the IANA IDs + and the IANA Database natively, but for Windows a mapping is required to + the native IDs. See below for more details. + + The IANA IDs can and do change on a regular basis, and can vary depending + on how recently the host system data was updated. As such you cannot rely + on any given ID existing on any host system. You must use + availableTimeZoneIds() to determine what IANA IDs are available. + + The IANA IDs and database are also know as the Olson IDs and database, + named after their creator. + + \section2 UTC Offset Time Zones + + A default UTC time zone backend is provided which is always guaranteed to + be available. This provides a set of generic Offset From UTC time zones + in the range UTC-14:00 to UTC+14:00. These time zones can be created + using either the standard ISO format names "UTC+00:00" as listed by + availableTimeZoneIds(), or using the number of offset seconds. + + \section2 Windows Time Zones + + Windows native time zone support is severely limited compared to the + standard IANA TZ Database. Windows time zones cover larger geographic + areas and are thus less accurate in their conversions. They also do not + support as much historic conversion data and so may only be accurate for + the current year. + + QTimeZone uses a conversion table derived form the Unicode CLDR data to map + between IANA IDs and Windows IDs. Depending on your version of Windows + and Qt, this table may not be able to provide a valid conversion, in which + "UTC" will be returned. + + QTimeZone provides a public API to use this conversion table. The Windows ID + used is the Windows Registry Key for the time zone which is also the MS + Exchange EWS ID as well, but is different to the Time Zone Name (TZID) and + COD code used by MS Exchange in versions before 2007. + + \section2 System Time Zone + + QTimeZone does not support any concept of a system or default time zone. + If you require a QDateTime that uses the current system time zone at any + given moment then you should use a Qt::TimeSpec of Qt::LocalTime. + + The method systemTimeZoneId() returns the current system IANA time zone + ID which on Unix-like systems will always be correct. On Windows this ID is + translated from the Windows system ID using an internal translation + table and the user's selected country. As a consequence there is a small + chance any Windows install may have IDs not known by Qt, in which case + "UTC" will be returned. + + Creating a new QTimeZone instance using the system time zone ID will only + produce a fixed named copy of the time zone, it will not change if the + system time zone changes. + + \section2 Time Zone Offsets + + The difference between UTC and the local time in a time zone is expressed + as an offset in seconds from UTC, i.e. the number of seconds to add to UTC + to obtain the local time. The total offset is comprised of two component + parts, the standard time offset and the daylight-saving time offset. The + standard time offset is the number of seconds to add to UTC to obtain + standard time in the time zone. The daylight-saving time offset is the + number of seconds to add to the standard time offset to obtain + daylight-saving time (abbreviated DST and sometimes called "daylight time" + or "summer time") in the time zone. + + Note that the standard and DST offsets for a time zone may change over time + as countries have changed DST laws or even their standard time offset. + + \section2 License + + This class includes data obtained from the CLDR data files under the terms + of the Unicode Data Files and Software License. See + \l{Unicode Common Locale Data Repository (CLDR)} for details. + + \sa QDateTime +*/ + +/*! + \enum QTimeZone::anonymous + + Sane UTC offsets range from -14 to +14 hours. + No known zone > 12 hrs West of Greenwich (Baker Island, USA). + No known zone > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati). + + \value MinUtcOffsetSecs + -14 * 3600, + + \value MaxUtcOffsetSecs + +14 * 3600 +*/ + +/*! + \enum QTimeZone::TimeType + + The type of time zone time, for example when requesting the name. In time + zones that do not apply DST, all three values may return the same result. + + \value StandardTime + The standard time in a time zone, i.e. when Daylight-Saving is not + in effect. + For example when formatting a display name this will show something + like "Pacific Standard Time". + \value DaylightTime + A time when Daylight-Saving is in effect. + For example when formatting a display name this will show something + like "Pacific daylight-saving time". + \value GenericTime + A time which is not specifically Standard or Daylight-Saving time, + either an unknown time or a neutral form. + For example when formatting a display name this will show something + like "Pacific Time". +*/ + +/*! + \enum QTimeZone::NameType + + The type of time zone name. + + \value DefaultName + The default form of the time zone name, e.g. LongName, ShortName or OffsetName + \value LongName + The long form of the time zone name, e.g. "Central European Time" + \value ShortName + The short form of the time zone name, usually an abbreviation, e.g. "CET" + \value OffsetName + The standard ISO offset form of the time zone name, e.g. "UTC+01:00" +*/ + +/*! + \class QTimeZone::OffsetData + \inmodule QtCore + + The time zone offset data for a given moment in time, i.e. the time zone + offsets and abbreviation to use at that moment in time. + + \list + \li OffsetData::atUtc The datetime of the offset data in UTC time. + \li OffsetData::offsetFromUtc The total offset from UTC in effect at the datetime. + \li OffsetData::standardTimeOffset The standard time offset component of the total offset. + \li OffsetData::daylightTimeOffset The DST offset component of the total offset. + \li OffsetData::abbreviation The abbreviation in effect at the datetime. + \endlist + + For example, for time zone "Europe/Berlin" the OffsetDate in standard and DST might be: + + \list + \li atUtc = QDateTime(QDate(2013, 1, 1), QTime(0, 0, 0), Qt::UTC) + \li offsetFromUtc = 3600 + \li standardTimeOffset = 3600 + \li daylightTimeOffset = 0 + \li abbreviation = "CET" + \endlist + + \list + \li atUtc = QDateTime(QDate(2013, 6, 1), QTime(0, 0, 0), Qt::UTC) + \li offsetFromUtc = 7200 + \li standardTimeOffset = 3600 + \li daylightTimeOffset = 3600 + \li abbreviation = "CEST" + \endlist +*/ + +/*! + \typedef QTimeZone::OffsetDataList + + Synonym for QVector<OffsetData>. +*/ + +/*! + Create a null/invalid time zone instance. +*/ + +QTimeZone::QTimeZone() noexcept + : d(0) +{ +} + +/*! + Creates an instance of the requested time zone \a ianaId. + + The ID must be one of the available system IDs otherwise an invalid + time zone will be returned. + + \sa availableTimeZoneIds() +*/ + +QTimeZone::QTimeZone(const QByteArray &ianaId) +{ + // Try and see if it's a valid UTC offset ID, just as quick to try create as look-up + d = new QUtcTimeZonePrivate(ianaId); + // If not a valid UTC offset ID then try create it with the system backend + // Relies on backend not creating valid tz with invalid name + if (!d->isValid()) + d = newBackendTimeZone(ianaId); +} + +/*! + Creates an instance of a time zone with the requested Offset from UTC of + \a offsetSeconds. + + The \a offsetSeconds from UTC must be in the range -14 hours to +14 hours + otherwise an invalid time zone will be returned. +*/ + +QTimeZone::QTimeZone(int offsetSeconds) + : d((offsetSeconds >= MinUtcOffsetSecs && offsetSeconds <= MaxUtcOffsetSecs) + ? new QUtcTimeZonePrivate(offsetSeconds) : nullptr) +{ +} + +/*! + Creates a custom time zone with an ID of \a ianaId and an offset from UTC + of \a offsetSeconds. The \a name will be the name used by displayName() + for the LongName, the \a abbreviation will be used by displayName() for the + ShortName and by abbreviation(), and the optional \a country will be used + by country(). The \a comment is an optional note that may be displayed in + a GUI to assist users in selecting a time zone. + + The \a ianaId must not be one of the available system IDs returned by + availableTimeZoneIds(). The \a offsetSeconds from UTC must be in the range + -14 hours to +14 hours. + + If the custom time zone does not have a specific country then set it to the + default value of QLocale::AnyCountry. +*/ + +QTimeZone::QTimeZone(const QByteArray &ianaId, int offsetSeconds, const QString &name, + const QString &abbreviation, QLocale::Country country, const QString &comment) + : d() +{ + if (!isTimeZoneIdAvailable(ianaId)) + d = new QUtcTimeZonePrivate(ianaId, offsetSeconds, name, abbreviation, country, comment); +} + +/*! + \internal + + Private. Create time zone with given private backend +*/ + +QTimeZone::QTimeZone(QTimeZonePrivate &dd) + : d(&dd) +{ +} + +/*! + Copy constructor, copy \a other to this. +*/ + +QTimeZone::QTimeZone(const QTimeZone &other) + : d(other.d) +{ +} + +/*! + Destroys the time zone. +*/ + +QTimeZone::~QTimeZone() +{ +} + +/*! + \fn QTimeZone::swap(QTimeZone &other) + + Swaps this time zone instance with \a other. This function is very + fast and never fails. +*/ + +/*! + Assignment operator, assign \a other to this. +*/ + +QTimeZone &QTimeZone::operator=(const QTimeZone &other) +{ + d = other.d; + return *this; +} + +/* + \fn void QTimeZone::swap(QTimeZone &other) + + Swaps this timezone with \a other. This function is very fast and + never fails. +*/ + +/*! + \fn QTimeZone &QTimeZone::operator=(QTimeZone &&other) + + Move-assigns \a other to this QTimeZone instance, transferring the + ownership of the managed pointer to this instance. +*/ + +/*! + Returns \c true if this time zone is equal to the \a other time zone. +*/ + +bool QTimeZone::operator==(const QTimeZone &other) const +{ + if (d && other.d) + return (*d == *other.d); + else + return (d == other.d); +} + +/*! + Returns \c true if this time zone is not equal to the \a other time zone. +*/ + +bool QTimeZone::operator!=(const QTimeZone &other) const +{ + if (d && other.d) + return (*d != *other.d); + else + return (d != other.d); +} + +/*! + Returns \c true if this time zone is valid. +*/ + +bool QTimeZone::isValid() const +{ + if (d) + return d->isValid(); + else + return false; +} + +/*! + Returns the IANA ID for the time zone. + + IANA IDs are used on all platforms. On Windows these are translated + from the Windows ID into the closest IANA ID for the time zone and country. +*/ + +QByteArray QTimeZone::id() const +{ + if (d) + return d->id(); + else + return QByteArray(); +} + +/*! + Returns the country for the time zone. +*/ + +QLocale::Country QTimeZone::country() const +{ + if (isValid()) + return d->country(); + else + return QLocale::AnyCountry; +} + +/*! + Returns any comment for the time zone. + + A comment may be provided by the host platform to assist users in + choosing the correct time zone. Depending on the platform this may not + be localized. +*/ + +QString QTimeZone::comment() const +{ + if (isValid()) + return d->comment(); + else + return QString(); +} + +/*! + Returns the localized time zone display name at the given \a atDateTime + for the given \a nameType in the given \a locale. The \a nameType and + \a locale requested may not be supported on all platforms, in which case + the best available option will be returned. + + If the \a locale is not provided then the application default locale will + be used. + + The display name may change depending on DST or historical events. + + \sa abbreviation() +*/ + +QString QTimeZone::displayName(const QDateTime &atDateTime, NameType nameType, + const QLocale &locale) const +{ + if (isValid()) + return d->displayName(atDateTime.toMSecsSinceEpoch(), nameType, locale); + else + return QString(); +} + +/*! + Returns the localized time zone display name for the given \a timeType + and \a nameType in the given \a locale. The \a nameType and \a locale + requested may not be supported on all platforms, in which case the best + available option will be returned. + + If the \a locale is not provided then the application default locale will + be used. + + Where the time zone display names have changed over time then the most + recent names will be used. + + \sa abbreviation() +*/ + +QString QTimeZone::displayName(TimeType timeType, NameType nameType, + const QLocale &locale) const +{ + if (isValid()) + return d->displayName(timeType, nameType, locale); + else + return QString(); +} + +/*! + Returns the time zone abbreviation at the given \a atDateTime. The + abbreviation may change depending on DST or even historical events. + + Note that the abbreviation is not guaranteed to be unique to this time zone + and should not be used in place of the ID or display name. + + \sa displayName() +*/ + +QString QTimeZone::abbreviation(const QDateTime &atDateTime) const +{ + if (isValid()) + return d->abbreviation(atDateTime.toMSecsSinceEpoch()); + else + return QString(); +} + +/*! + Returns the total effective offset at the given \a atDateTime, i.e. the + number of seconds to add to UTC to obtain the local time. This includes + any DST offset that may be in effect, i.e. it is the sum of + standardTimeOffset() and daylightTimeOffset() for the given datetime. + + For example, for the time zone "Europe/Berlin" the standard time offset is + +3600 seconds and the DST offset is +3600 seconds. During standard time + offsetFromUtc() will return +3600 (UTC+01:00), and during DST it will + return +7200 (UTC+02:00). + + \sa standardTimeOffset(), daylightTimeOffset() +*/ + +int QTimeZone::offsetFromUtc(const QDateTime &atDateTime) const +{ + if (isValid()) + return d->offsetFromUtc(atDateTime.toMSecsSinceEpoch()); + else + return 0; +} + +/*! + Returns the standard time offset at the given \a atDateTime, i.e. the + number of seconds to add to UTC to obtain the local Standard Time. This + excludes any DST offset that may be in effect. + + For example, for the time zone "Europe/Berlin" the standard time offset is + +3600 seconds. During both standard and DST offsetFromUtc() will return + +3600 (UTC+01:00). + + \sa offsetFromUtc(), daylightTimeOffset() +*/ + +int QTimeZone::standardTimeOffset(const QDateTime &atDateTime) const +{ + if (isValid()) + return d->standardTimeOffset(atDateTime.toMSecsSinceEpoch()); + else + return 0; +} + +/*! + Returns the daylight-saving time offset at the given \a atDateTime, + i.e. the number of seconds to add to the standard time offset to obtain the + local daylight-saving time. + + For example, for the time zone "Europe/Berlin" the DST offset is +3600 + seconds. During standard time daylightTimeOffset() will return 0, and when + daylight-saving is in effect it will return +3600. + + \sa offsetFromUtc(), standardTimeOffset() +*/ + +int QTimeZone::daylightTimeOffset(const QDateTime &atDateTime) const +{ + if (hasDaylightTime()) + return d->daylightTimeOffset(atDateTime.toMSecsSinceEpoch()); + else + return 0; +} + +/*! + Returns \c true if the time zone has practiced daylight-saving at any time. + + \sa isDaylightTime(), daylightTimeOffset() +*/ + +bool QTimeZone::hasDaylightTime() const +{ + if (isValid()) + return d->hasDaylightTime(); + else + return false; +} + +/*! + Returns \c true if daylight-saving was in effect at the given \a atDateTime. + + \sa hasDaylightTime(), daylightTimeOffset() +*/ + +bool QTimeZone::isDaylightTime(const QDateTime &atDateTime) const +{ + if (hasDaylightTime()) + return d->isDaylightTime(atDateTime.toMSecsSinceEpoch()); + else + return false; +} + +/*! + Returns the effective offset details at the given \a forDateTime. This is + the equivalent of calling offsetFromUtc(), abbreviation(), etc individually but is + more efficient. + + \sa offsetFromUtc(), standardTimeOffset(), daylightTimeOffset(), abbreviation() +*/ + +QTimeZone::OffsetData QTimeZone::offsetData(const QDateTime &forDateTime) const +{ + if (hasTransitions()) + return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch())); + else + return QTimeZonePrivate::invalidOffsetData(); +} + +/*! + Returns \c true if the system backend supports obtaining transitions. + + Transitions are changes in the time-zone: these happen when DST turns on or + off and when authorities alter the offsets for the time-zone. + + \sa nextTransition(), previousTransition(), transitions() +*/ + +bool QTimeZone::hasTransitions() const +{ + if (isValid()) + return d->hasTransitions(); + else + return false; +} + +/*! + Returns the first time zone Transition after the given \a afterDateTime. + This is most useful when you have a Transition time and wish to find the + Transition after it. + + If there is no transition after the given \a afterDateTime then an invalid + OffsetData will be returned with an invalid QDateTime. + + The given \a afterDateTime is exclusive. + + \sa hasTransitions(), previousTransition(), transitions() +*/ + +QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime) const +{ + if (hasTransitions()) + return QTimeZonePrivate::toOffsetData(d->nextTransition(afterDateTime.toMSecsSinceEpoch())); + else + return QTimeZonePrivate::invalidOffsetData(); +} + +/*! + Returns the first time zone Transition before the given \a beforeDateTime. + This is most useful when you have a Transition time and wish to find the + Transition before it. + + If there is no transition before the given \a beforeDateTime then an invalid + OffsetData will be returned with an invalid QDateTime. + + The given \a beforeDateTime is exclusive. + + \sa hasTransitions(), nextTransition(), transitions() +*/ + +QTimeZone::OffsetData QTimeZone::previousTransition(const QDateTime &beforeDateTime) const +{ + if (hasTransitions()) + return QTimeZonePrivate::toOffsetData(d->previousTransition(beforeDateTime.toMSecsSinceEpoch())); + else + return QTimeZonePrivate::invalidOffsetData(); +} + +/*! + Returns a list of all time zone transitions between the given datetimes. + + The given \a fromDateTime and \a toDateTime are inclusive. + + \sa hasTransitions(), nextTransition(), previousTransition() +*/ + +QTimeZone::OffsetDataList QTimeZone::transitions(const QDateTime &fromDateTime, + const QDateTime &toDateTime) const +{ + OffsetDataList list; + if (hasTransitions()) { + const QTimeZonePrivate::DataList plist = d->transitions(fromDateTime.toMSecsSinceEpoch(), + toDateTime.toMSecsSinceEpoch()); + list.reserve(plist.count()); + for (const QTimeZonePrivate::Data &pdata : plist) + list.append(QTimeZonePrivate::toOffsetData(pdata)); + } + return list; +} + +// Static methods + +/*! + Returns the current system time zone IANA ID. + + On Windows this ID is translated from the Windows ID using an internal + translation table and the user's selected country. As a consequence there + is a small chance any Windows install may have IDs not known by Qt, in + which case "UTC" will be returned. +*/ + +QByteArray QTimeZone::systemTimeZoneId() +{ + return global_tz->backend->systemTimeZoneId(); +} + +/*! + \since 5.5 + Returns a QTimeZone object that refers to the local system time, as + specified by systemTimeZoneId(). + + \sa utc() +*/ +QTimeZone QTimeZone::systemTimeZone() +{ + return QTimeZone(QTimeZone::systemTimeZoneId()); +} + +/*! + \since 5.5 + Returns a QTimeZone object that refers to UTC (Universal Time Coordinated). + + \sa systemTimeZone() +*/ +QTimeZone QTimeZone::utc() +{ + return QTimeZone(QTimeZonePrivate::utcQByteArray()); +} + +/*! + Returns \c true if a given time zone \a ianaId is available on this system. + + \sa availableTimeZoneIds() +*/ + +bool QTimeZone::isTimeZoneIdAvailable(const QByteArray &ianaId) +{ + // isValidId is not strictly required, but faster to weed out invalid + // IDs as availableTimeZoneIds() may be slow + if (!QTimeZonePrivate::isValidId(ianaId)) + return false; + return QUtcTimeZonePrivate().isTimeZoneIdAvailable(ianaId) || + global_tz->backend->isTimeZoneIdAvailable(ianaId); +} + +static QList<QByteArray> set_union(const QList<QByteArray> &l1, const QList<QByteArray> &l2) +{ + QList<QByteArray> result; + result.reserve(l1.size() + l2.size()); + std::set_union(l1.begin(), l1.end(), + l2.begin(), l2.end(), + std::back_inserter(result)); + return result; +} + +/*! + Returns a list of all available IANA time zone IDs on this system. + + \sa isTimeZoneIdAvailable() +*/ + +QList<QByteArray> QTimeZone::availableTimeZoneIds() +{ + return set_union(QUtcTimeZonePrivate().availableTimeZoneIds(), + global_tz->backend->availableTimeZoneIds()); +} + +/*! + Returns a list of all available IANA time zone IDs for a given \a country. + + As a special case, a \a country of Qt::AnyCountry returns those time zones + that do not have any country related to them, such as UTC. If you require + a list of all time zone IDs for all countries then use the standard + availableTimeZoneIds() method. + + \sa isTimeZoneIdAvailable() +*/ + +QList<QByteArray> QTimeZone::availableTimeZoneIds(QLocale::Country country) +{ + return set_union(QUtcTimeZonePrivate().availableTimeZoneIds(country), + global_tz->backend->availableTimeZoneIds(country)); +} + +/*! + Returns a list of all available IANA time zone IDs with a given standard + time offset of \a offsetSeconds. + + \sa isTimeZoneIdAvailable() +*/ + +QList<QByteArray> QTimeZone::availableTimeZoneIds(int offsetSeconds) +{ + return set_union(QUtcTimeZonePrivate().availableTimeZoneIds(offsetSeconds), + global_tz->backend->availableTimeZoneIds(offsetSeconds)); +} + +/*! + Returns the Windows ID equivalent to the given \a ianaId. + + \sa windowsIdToDefaultIanaId(), windowsIdToIanaIds() +*/ + +QByteArray QTimeZone::ianaIdToWindowsId(const QByteArray &ianaId) +{ + return QTimeZonePrivate::ianaIdToWindowsId(ianaId); +} + +/*! + Returns the default IANA ID for a given \a windowsId. + + Because a Windows ID can cover several IANA IDs in several different + countries, this function returns the most frequently used IANA ID with no + regard for the country and should thus be used with care. It is usually + best to request the default for a specific country. + + \sa ianaIdToWindowsId(), windowsIdToIanaIds() +*/ + +QByteArray QTimeZone::windowsIdToDefaultIanaId(const QByteArray &windowsId) +{ + return QTimeZonePrivate::windowsIdToDefaultIanaId(windowsId); +} + +/*! + Returns the default IANA ID for a given \a windowsId and \a country. + + Because a Windows ID can cover several IANA IDs within a given country, + the most frequently used IANA ID in that country is returned. + + As a special case, QLocale::AnyCountry returns the default of those IANA IDs + that do not have any specific country. + + \sa ianaIdToWindowsId(), windowsIdToIanaIds() +*/ + +QByteArray QTimeZone::windowsIdToDefaultIanaId(const QByteArray &windowsId, + QLocale::Country country) +{ + return QTimeZonePrivate::windowsIdToDefaultIanaId(windowsId, country); +} + +/*! + Returns all the IANA IDs for a given \a windowsId. + + The returned list is sorted alphabetically. + + \sa ianaIdToWindowsId(), windowsIdToDefaultIanaId() +*/ + +QList<QByteArray> QTimeZone::windowsIdToIanaIds(const QByteArray &windowsId) +{ + return QTimeZonePrivate::windowsIdToIanaIds(windowsId); +} + +/*! + Returns all the IANA IDs for a given \a windowsId and \a country. + + As a special case QLocale::AnyCountry returns those IANA IDs that do + not have any specific country. + + The returned list is in order of frequency of usage, i.e. larger zones + within a country are listed first. + + \sa ianaIdToWindowsId(), windowsIdToDefaultIanaId() +*/ + +QList<QByteArray> QTimeZone::windowsIdToIanaIds(const QByteArray &windowsId, + QLocale::Country country) +{ + return QTimeZonePrivate::windowsIdToIanaIds(windowsId, country); +} + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz) +{ + tz.d->serialize(ds); + return ds; +} + +QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) +{ + QString ianaId; + ds >> ianaId; + if (ianaId == QLatin1String("OffsetFromUtc")) { + int utcOffset; + QString name; + QString abbreviation; + int country; + QString comment; + ds >> ianaId >> utcOffset >> name >> abbreviation >> country >> comment; + // Try creating as a system timezone, which succeeds (producing a valid + // zone) iff ianaId is valid; we can then ignore the other data. + tz = QTimeZone(ianaId.toUtf8()); + // If not, then construct a custom timezone using all the saved values: + if (!tz.isValid()) + tz = QTimeZone(ianaId.toUtf8(), utcOffset, name, abbreviation, + QLocale::Country(country), comment); + } else { + tz = QTimeZone(ianaId.toUtf8()); + } + return ds; +} +#endif // QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QTimeZone &tz) +{ + QDebugStateSaver saver(dbg); + //TODO Include backend and data version details? + dbg.nospace() << "QTimeZone(" << QString::fromUtf8(tz.id()) << ')'; + return dbg; +} +#endif + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezone.h b/src/corelib/time/qtimezone.h new file mode 100644 index 0000000000..62ecee49bb --- /dev/null +++ b/src/corelib/time/qtimezone.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QTIMEZONE_H +#define QTIMEZONE_H + +#include <QtCore/qshareddata.h> +#include <QtCore/qlocale.h> +#include <QtCore/qdatetime.h> + +QT_REQUIRE_CONFIG(timezone); + +#if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE) +Q_FORWARD_DECLARE_CF_TYPE(CFTimeZone); +Q_FORWARD_DECLARE_OBJC_CLASS(NSTimeZone); +#endif + +QT_BEGIN_NAMESPACE + +class QTimeZonePrivate; + +class Q_CORE_EXPORT QTimeZone +{ +public: + // Sane UTC offsets range from -14 to +14 hours: + enum { + // No known zone > 12 hrs West of Greenwich (Baker Island, USA) + MinUtcOffsetSecs = -14 * 3600, + // No known zone > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati) + MaxUtcOffsetSecs = +14 * 3600 + }; + + enum TimeType { + StandardTime = 0, + DaylightTime = 1, + GenericTime = 2 + }; + + enum NameType { + DefaultName = 0, + LongName = 1, + ShortName = 2, + OffsetName = 3 + }; + + struct OffsetData { + QString abbreviation; + QDateTime atUtc; + int offsetFromUtc; + int standardTimeOffset; + int daylightTimeOffset; + }; + typedef QVector<OffsetData> OffsetDataList; + + QTimeZone() noexcept; + explicit QTimeZone(const QByteArray &ianaId); + explicit QTimeZone(int offsetSeconds); + /*implicit*/ QTimeZone(const QByteArray &zoneId, int offsetSeconds, const QString &name, + const QString &abbreviation, QLocale::Country country = QLocale::AnyCountry, + const QString &comment = QString()); + QTimeZone(const QTimeZone &other); + ~QTimeZone(); + + QTimeZone &operator=(const QTimeZone &other); + QTimeZone &operator=(QTimeZone &&other) noexcept { swap(other); return *this; } + + void swap(QTimeZone &other) noexcept + { d.swap(other.d); } + + bool operator==(const QTimeZone &other) const; + bool operator!=(const QTimeZone &other) const; + + bool isValid() const; + + QByteArray id() const; + QLocale::Country country() const; + QString comment() const; + + QString displayName(const QDateTime &atDateTime, + QTimeZone::NameType nameType = QTimeZone::DefaultName, + const QLocale &locale = QLocale()) const; + QString displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType = QTimeZone::DefaultName, + const QLocale &locale = QLocale()) const; + QString abbreviation(const QDateTime &atDateTime) const; + + int offsetFromUtc(const QDateTime &atDateTime) const; + int standardTimeOffset(const QDateTime &atDateTime) const; + int daylightTimeOffset(const QDateTime &atDateTime) const; + + bool hasDaylightTime() const; + bool isDaylightTime(const QDateTime &atDateTime) const; + + OffsetData offsetData(const QDateTime &forDateTime) const; + + bool hasTransitions() const; + OffsetData nextTransition(const QDateTime &afterDateTime) const; + OffsetData previousTransition(const QDateTime &beforeDateTime) const; + OffsetDataList transitions(const QDateTime &fromDateTime, const QDateTime &toDateTime) const; + + static QByteArray systemTimeZoneId(); + static QTimeZone systemTimeZone(); + static QTimeZone utc(); + + static bool isTimeZoneIdAvailable(const QByteArray &ianaId); + + static QList<QByteArray> availableTimeZoneIds(); + static QList<QByteArray> availableTimeZoneIds(QLocale::Country country); + static QList<QByteArray> availableTimeZoneIds(int offsetSeconds); + + static QByteArray ianaIdToWindowsId(const QByteArray &ianaId); + static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId); + static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId, + QLocale::Country country); + static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId); + static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId, + QLocale::Country country); + +#if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE) + static QTimeZone fromCFTimeZone(CFTimeZoneRef timeZone); + CFTimeZoneRef toCFTimeZone() const Q_DECL_CF_RETURNS_RETAINED; + static QTimeZone fromNSTimeZone(const NSTimeZone *timeZone); + NSTimeZone *toNSTimeZone() const Q_DECL_NS_RETURNS_AUTORELEASED; +#endif + +private: + QTimeZone(QTimeZonePrivate &dd); +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz); +#endif + friend class QTimeZonePrivate; + friend class QDateTime; + friend class QDateTimePrivate; + QSharedDataPointer<QTimeZonePrivate> d; +}; + +Q_DECLARE_TYPEINFO(QTimeZone::OffsetData, Q_MOVABLE_TYPE); +Q_DECLARE_SHARED(QTimeZone) + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &ds, QTimeZone &tz); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QTimeZone &tz); +#endif + +QT_END_NAMESPACE + +#endif // QTIMEZONE_H diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp new file mode 100644 index 0000000000..569b343187 --- /dev/null +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -0,0 +1,926 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" +#include "qtimezoneprivate_data_p.h" + +#include <qdatastream.h> +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/* + Static utilities for looking up Windows ID tables +*/ + +static const int windowsDataTableSize = sizeof(windowsDataTable) / sizeof(QWindowsData) - 1; +static const int zoneDataTableSize = sizeof(zoneDataTable) / sizeof(QZoneData) - 1; +static const int utcDataTableSize = sizeof(utcDataTable) / sizeof(QUtcData) - 1; + + +static const QZoneData *zoneData(quint16 index) +{ + Q_ASSERT(index < zoneDataTableSize); + return &zoneDataTable[index]; +} + +static const QWindowsData *windowsData(quint16 index) +{ + Q_ASSERT(index < windowsDataTableSize); + return &windowsDataTable[index]; +} + +static const QUtcData *utcData(quint16 index) +{ + Q_ASSERT(index < utcDataTableSize); + return &utcDataTable[index]; +} + +// Return the Windows ID literal for a given QWindowsData +static QByteArray windowsId(const QWindowsData *windowsData) +{ + return (windowsIdData + windowsData->windowsIdIndex); +} + +// Return the IANA ID literal for a given QWindowsData +static QByteArray ianaId(const QWindowsData *windowsData) +{ + return (ianaIdData + windowsData->ianaIdIndex); +} + +// Return the IANA ID literal for a given QZoneData +static QByteArray ianaId(const QZoneData *zoneData) +{ + return (ianaIdData + zoneData->ianaIdIndex); +} + +static QByteArray utcId(const QUtcData *utcData) +{ + return (ianaIdData + utcData->ianaIdIndex); +} + +static quint16 toWindowsIdKey(const QByteArray &winId) +{ + for (quint16 i = 0; i < windowsDataTableSize; ++i) { + const QWindowsData *data = windowsData(i); + if (windowsId(data) == winId) + return data->windowsIdKey; + } + return 0; +} + +static QByteArray toWindowsIdLiteral(quint16 windowsIdKey) +{ + for (quint16 i = 0; i < windowsDataTableSize; ++i) { + const QWindowsData *data = windowsData(i); + if (data->windowsIdKey == windowsIdKey) + return windowsId(data); + } + return QByteArray(); +} + +/* + Base class implementing common utility routines, only intantiate for a null tz. +*/ + +QTimeZonePrivate::QTimeZonePrivate() +{ +} + +QTimeZonePrivate::QTimeZonePrivate(const QTimeZonePrivate &other) + : QSharedData(other), m_id(other.m_id) +{ +} + +QTimeZonePrivate::~QTimeZonePrivate() +{ +} + +QTimeZonePrivate *QTimeZonePrivate::clone() const +{ + return new QTimeZonePrivate(*this); +} + +bool QTimeZonePrivate::operator==(const QTimeZonePrivate &other) const +{ + // TODO Too simple, but need to solve problem of comparing different derived classes + // Should work for all System and ICU classes as names guaranteed unique, but not for Simple. + // Perhaps once all classes have working transitions can compare full list? + return (m_id == other.m_id); +} + +bool QTimeZonePrivate::operator!=(const QTimeZonePrivate &other) const +{ + return !(*this == other); +} + +bool QTimeZonePrivate::isValid() const +{ + return !m_id.isEmpty(); +} + +QByteArray QTimeZonePrivate::id() const +{ + return m_id; +} + +QLocale::Country QTimeZonePrivate::country() const +{ + // Default fall-back mode, use the zoneTable to find Region of known Zones + for (int i = 0; i < zoneDataTableSize; ++i) { + const QZoneData *data = zoneData(i); + if (ianaId(data).split(' ').contains(m_id)) + return (QLocale::Country)data->country; + } + return QLocale::AnyCountry; +} + +QString QTimeZonePrivate::comment() const +{ + return QString(); +} + +QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + if (nameType == QTimeZone::OffsetName) + return isoOffsetFormat(offsetFromUtc(atMSecsSinceEpoch)); + + if (isDaylightTime(atMSecsSinceEpoch)) + return displayName(QTimeZone::DaylightTime, nameType, locale); + else + return displayName(QTimeZone::StandardTime, nameType, locale); +} + +QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + Q_UNUSED(timeType) + Q_UNUSED(nameType) + Q_UNUSED(locale) + return QString(); +} + +QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return QString(); +} + +int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + return standardTimeOffset(atMSecsSinceEpoch) + daylightTimeOffset(atMSecsSinceEpoch); +} + +int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return invalidSeconds(); +} + +int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return invalidSeconds(); +} + +bool QTimeZonePrivate::hasDaylightTime() const +{ + return false; +} + +bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return false; +} + +QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + Q_UNUSED(forMSecsSinceEpoch) + return invalidData(); +} + +// Private only method for use by QDateTime to convert local msecs to epoch msecs +QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const +{ + if (!hasDaylightTime()) // No DST means same offset for all local msecs + return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000); + + /* + We need a UTC time at which to ask for the offset, in order to be able to + add that offset to forLocalMSecs, to get the UTC time we + need. Fortunately, no time-zone offset is more than 14 hours; and DST + transitions happen (much) more than thirty-two hours apart. So sampling + offset sixteen hours each side gives us information we can be sure + brackets the correct time and at most one DST transition. + */ + const qint64 sixteenHoursInMSecs(16 * 3600 * 1000); + Q_STATIC_ASSERT(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs + && sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs); + const qint64 recent = forLocalMSecs - sixteenHoursInMSecs; + const qint64 imminent = forLocalMSecs + sixteenHoursInMSecs; + /* + Offsets are Local - UTC, positive to the east of Greenwich, negative to + the west; DST offset always exceeds standard offset, when DST applies. + When we have offsets on either side of a transition, the lower one is + standard, the higher is DST. + + Non-DST transitions (jurisdictions changing time-zone and time-zones + changing their standard offset, typically) are described below as if they + were DST transitions (since these are more usual and familiar); the code + mostly concerns itself with offsets from UTC, described in terms of the + common case for changes in that. If there is no actual change in offset + (e.g. a DST transition cancelled by a standard offset change), this code + should handle it gracefully; without transitions, it'll see early == late + and take the easy path; with transitions, tran and nextTran get the + correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects + the right one. In all other cases, the transition changes offset and the + reasoning that applies to DST applies just the same. Aside from hinting, + the only thing that looks at DST-ness at all, other than inferred from + offset changes, is the case without transition data handling an invalid + time in the gap that a transition passed over. + + The handling of hint (see below) is apt to go wrong in non-DST + transitions. There isn't really a great deal we can hope to do about that + without adding yet more unreliable complexity to the heuristics in use for + already obscure corner-cases. + */ + + /* + The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller + thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so + should have been handled above; if it slips through, it's wrong but we + should probably treat it as standard anyway (never-DST means + always-standard, after all). If the hint turns out to be wrong, fall back + on trying the other possibility: which makes it harmless to treat -1 + (meaning unknown) as standard (i.e. try standard first, then try DST). In + practice, away from a transition, the only difference hint makes is to + which candidate we try first: if the hint is wrong (or unknown and + standard fails), we'll try the other candidate and it'll work. + + For the obscure (and invalid) case where forLocalMSecs falls in a + spring-forward's missing hour, a common case is that we started with a + date/time for which the hint was valid and adjusted it naively; for that + case, we should correct the adjustment by shunting across the transition + into where hint is wrong. So half-way through the gap, arrived at from + the DST side, should be read as an hour earlier, in standard time; but, if + arrived at from the standard side, should be read as an hour later, in + DST. (This shall be wrong in some cases; for example, when a country + changes its transition dates and changing a date/time by more than six + months lands it on a transition. However, these cases are even more + obscure than those where the heuristic is good.) + */ + + if (hasTransitions()) { + /* + We have transitions. + + Each transition gives the offsets to use until the next; so we need the + most recent transition before the time forLocalMSecs describes. If it + describes a time *in* a transition, we'll need both that transition and + the one before it. So find one transition that's probably after (and not + much before, otherwise) and another that's definitely before, then work + out which one to use. When both or neither work on forLocalMSecs, use + hint to disambiguate. + */ + + // Get a transition definitely before the local MSecs; usually all we need. + // Only around the transition times might we need another. + Data tran = previousTransition(recent); + Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable + forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch); + Data nextTran = nextTransition(tran.atMSecsSinceEpoch); + /* + Now walk those forward until they bracket forLocalMSecs with transitions. + + One of the transitions should then be telling us the right offset to use. + In a transition, we need the transition before it (to describe the run-up + to the transition) and the transition itself; so we need to stop when + nextTran is that transition. + */ + while (nextTran.atMSecsSinceEpoch != invalidMSecs() + && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) { + Data newTran = nextTransition(nextTran.atMSecsSinceEpoch); + if (newTran.atMSecsSinceEpoch == invalidMSecs() + || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) { + // Definitely not a relevant tansition: too far in the future. + break; + } + tran = nextTran; + nextTran = newTran; + } + + // Check we do *really* have transitions for this zone: + if (tran.atMSecsSinceEpoch != invalidMSecs()) { + + /* + So now tran is definitely before and nextTran is either after or only + slightly before. One is standard time; we interpret the other as DST + (although the transition might in fact by a change in standard offset). Our + hint tells us which of those to use (defaulting to standard if no hint): try + it first; if that fails, try the other; if both fail, life's tricky. + */ + Q_ASSERT(forLocalMSecs < 0 + || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch); + const qint64 nextStart = nextTran.atMSecsSinceEpoch; + // Work out the UTC values it might make sense to return: + nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000; + tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000; + + // If both or neither have zero DST, treat the one with lower offset as standard: + const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset + ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset; + // If that agrees with hint > 0, our first guess is to use nextTran; else tran. + const bool nextFirst = nextIsDst == (hint > 0) && nextStart != invalidMSecs(); + for (int i = 0; i < 2; i++) { + /* + On the first pass, the case we consider is what hint told us to expect + (except when hint was -1 and didn't actually tell us what to expect), + so it's likely right. We only get a second pass if the first failed, + by which time the second case, that we're trying, is likely right. If + an overwhelming majority of calls have hint == -1, the Q_LIKELY here + shall be wrong half the time; otherwise, its errors shall be rarer + than that. + */ + if (nextFirst ? i == 0 : i) { + Q_ASSERT(nextStart != invalidMSecs()); + if (Q_LIKELY(nextStart <= nextTran.atMSecsSinceEpoch)) + return nextTran; + } else { + // If next is invalid, nextFirst is false, to route us here first: + if (nextStart == invalidMSecs() || Q_LIKELY(nextStart > tran.atMSecsSinceEpoch)) + return tran; + } + } + + /* + Neither is valid (e.g. in a spring-forward's gap) and + nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so + 0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch + = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000 + */ + int dstStep = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000; + Q_ASSERT(dstStep > 0); // How else could we get here ? + if (nextFirst) { // hint thought we needed nextTran, so use tran + tran.atMSecsSinceEpoch -= dstStep; + return tran; + } + nextTran.atMSecsSinceEpoch += dstStep; + return nextTran; + } + // System has transitions but not for this zone. + // Try falling back to offsetFromUtc + } + + /* Bracket and refine to discover offset. */ + qint64 utcEpochMSecs; + + int early = offsetFromUtc(recent); + int late = offsetFromUtc(imminent); + if (Q_LIKELY(early == late)) { // > 99% of the time + utcEpochMSecs = forLocalMSecs - early * 1000; + } else { + // Close to a DST transition: early > late is near a fall-back, + // early < late is near a spring-forward. + const int offsetInDst = qMax(early, late); + const int offsetInStd = qMin(early, late); + // Candidate values for utcEpochMSecs (if forLocalMSecs is valid): + const qint64 forDst = forLocalMSecs - offsetInDst * 1000; + const qint64 forStd = forLocalMSecs - offsetInStd * 1000; + // Best guess at the answer: + const qint64 hinted = hint > 0 ? forDst : forStd; + if (Q_LIKELY(offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd))) { + utcEpochMSecs = hinted; + } else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) { + utcEpochMSecs = forDst; + } else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) { + utcEpochMSecs = forStd; + } else { + // Invalid forLocalMSecs: in spring-forward gap. + const int dstStep = daylightTimeOffset(early < late ? imminent : recent) * 1000; + Q_ASSERT(dstStep); // There can't be a transition without it ! + utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep; + } + } + + return data(utcEpochMSecs); +} + +bool QTimeZonePrivate::hasTransitions() const +{ + return false; +} + +QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + Q_UNUSED(afterMSecsSinceEpoch) + return invalidData(); +} + +QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + Q_UNUSED(beforeMSecsSinceEpoch) + return invalidData(); +} + +QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch, + qint64 toMSecsSinceEpoch) const +{ + DataList list; + if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) { + // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec + Data next = nextTransition(fromMSecsSinceEpoch - 1); + while (next.atMSecsSinceEpoch != invalidMSecs() + && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) { + list.append(next); + next = nextTransition(next.atMSecsSinceEpoch); + } + } + return list; +} + +QByteArray QTimeZonePrivate::systemTimeZoneId() const +{ + return QByteArray(); +} + +bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const +{ + // Fall-back implementation, can be made faster in subclasses + const QList<QByteArray> tzIds = availableTimeZoneIds(); + return std::binary_search(tzIds.begin(), tzIds.end(), ianaId); +} + +QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const +{ + return QList<QByteArray>(); +} + +QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + // Default fall-back mode, use the zoneTable to find Region of know Zones + QList<QByteArray> regions; + + // First get all Zones in the Zones table belonging to the Region + for (int i = 0; i < zoneDataTableSize; ++i) { + if (zoneData(i)->country == country) + regions += ianaId(zoneData(i)).split(' '); + } + + std::sort(regions.begin(), regions.end()); + regions.erase(std::unique(regions.begin(), regions.end()), regions.end()); + + // Then select just those that are available + const QList<QByteArray> all = availableTimeZoneIds(); + QList<QByteArray> result; + result.reserve(qMin(all.size(), regions.size())); + std::set_intersection(all.begin(), all.end(), regions.cbegin(), regions.cend(), + std::back_inserter(result)); + return result; +} + +QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const +{ + // Default fall-back mode, use the zoneTable to find Offset of know Zones + QList<QByteArray> offsets; + // First get all Zones in the table using the Offset + for (int i = 0; i < windowsDataTableSize; ++i) { + const QWindowsData *winData = windowsData(i); + if (winData->offsetFromUtc == offsetFromUtc) { + for (int j = 0; j < zoneDataTableSize; ++j) { + const QZoneData *data = zoneData(j); + if (data->windowsIdKey == winData->windowsIdKey) + offsets += ianaId(data).split(' '); + } + } + } + + std::sort(offsets.begin(), offsets.end()); + offsets.erase(std::unique(offsets.begin(), offsets.end()), offsets.end()); + + // Then select just those that are available + const QList<QByteArray> all = availableTimeZoneIds(); + QList<QByteArray> result; + result.reserve(qMin(all.size(), offsets.size())); + std::set_intersection(all.begin(), all.end(), offsets.cbegin(), offsets.cend(), + std::back_inserter(result)); + return result; +} + +#ifndef QT_NO_DATASTREAM +void QTimeZonePrivate::serialize(QDataStream &ds) const +{ + ds << QString::fromUtf8(m_id); +} +#endif // QT_NO_DATASTREAM + +// Static Utility Methods + +QTimeZonePrivate::Data QTimeZonePrivate::invalidData() +{ + Data data; + data.atMSecsSinceEpoch = invalidMSecs(); + data.offsetFromUtc = invalidSeconds(); + data.standardTimeOffset = invalidSeconds(); + data.daylightTimeOffset = invalidSeconds(); + return data; +} + +QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData() +{ + QTimeZone::OffsetData offsetData; + offsetData.atUtc = QDateTime(); + offsetData.offsetFromUtc = invalidSeconds(); + offsetData.standardTimeOffset = invalidSeconds(); + offsetData.daylightTimeOffset = invalidSeconds(); + return offsetData; +} + +QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Data &data) +{ + QTimeZone::OffsetData offsetData = invalidOffsetData(); + if (data.atMSecsSinceEpoch != invalidMSecs()) { + offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, Qt::UTC); + offsetData.offsetFromUtc = data.offsetFromUtc; + offsetData.standardTimeOffset = data.standardTimeOffset; + offsetData.daylightTimeOffset = data.daylightTimeOffset; + offsetData.abbreviation = data.abbreviation; + } + return offsetData; +} + +// Is the format of the ID valid ? +bool QTimeZonePrivate::isValidId(const QByteArray &ianaId) +{ + /* + Main rules for defining TZ/IANA names as per ftp://ftp.iana.org/tz/code/Theory + 1. Use only valid POSIX file name components + 2. Within a file name component, use only ASCII letters, `.', `-' and `_'. + 3. Do not use digits (except in a [+-]\d+ suffix, when used). + 4. A file name component must not exceed 14 characters or start with `-' + However, the rules are really guidelines - a later one says + - Do not change established names if they only marginally violate the + above rules. + We may, therefore, need to be a bit slack in our check here, if we hit + legitimate exceptions in real time-zone databases. + + In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid + so we need to accept digits, ':', and '+'; aliases typically have the form + of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX + suffix starts with an offset (as in GMT+7) and may continue with another + name (as in EST5EDT, giving the DST name of the zone); a further offset is + allowed (for DST). The ("hard to describe and [...] error-prone in + practice") POSIX form even allows a suffix giving the dates (and + optionally times) of the annual DST transitions. Hopefully, no TZ aliases + go that far, but we at least need to accept an offset and (single + fragment) DST-name. + + But for the legacy complications, the following would be preferable if + QRegExp would work on QByteArrays directly: + const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}" + "(?:/[a-z+._][a-z+._-]{,13})*" + // Optional suffix: + "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset + // one name fragment (DST): + "(?:[a-z+._][a-z+._-]{,13})?)"), + Qt::CaseInsensitive); + return rx.exactMatch(ianaId); + */ + + // Somewhat slack hand-rolled version: + const int MinSectionLength = 1; + const int MaxSectionLength = 14; + int sectionLength = 0; + for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) { + const char ch = *it; + if (ch == '/') { + if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength) + return false; // violates (4) + sectionLength = -1; + } else if (ch == '-') { + if (sectionLength == 0) + return false; // violates (4) + } else if (!(ch >= 'a' && ch <= 'z') + && !(ch >= 'A' && ch <= 'Z') + && !(ch == '_') + && !(ch == '.') + // Should ideally check these only happen as an offset: + && !(ch >= '0' && ch <= '9') + && !(ch == '+') + && !(ch == ':')) { + return false; // violates (2) + } + } + if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength) + return false; // violates (4) + return true; +} + +QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc) +{ + const int mins = offsetFromUtc / 60; + return QString::fromUtf8("UTC%1%2:%3").arg(mins >= 0 ? QLatin1Char('+') : QLatin1Char('-')) + .arg(qAbs(mins) / 60, 2, 10, QLatin1Char('0')) + .arg(qAbs(mins) % 60, 2, 10, QLatin1Char('0')); +} + +QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id) +{ + for (int i = 0; i < zoneDataTableSize; ++i) { + const QZoneData *data = zoneData(i); + if (ianaId(data).split(' ').contains(id)) + return toWindowsIdLiteral(data->windowsIdKey); + } + return QByteArray(); +} + +QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId) +{ + const quint16 windowsIdKey = toWindowsIdKey(windowsId); + for (int i = 0; i < windowsDataTableSize; ++i) { + const QWindowsData *data = windowsData(i); + if (data->windowsIdKey == windowsIdKey) + return ianaId(data); + } + return QByteArray(); +} + +QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId, + QLocale::Country country) +{ + const QList<QByteArray> list = windowsIdToIanaIds(windowsId, country); + if (list.count() > 0) + return list.first(); + else + return QByteArray(); +} + +QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId) +{ + const quint16 windowsIdKey = toWindowsIdKey(windowsId); + QList<QByteArray> list; + + for (int i = 0; i < zoneDataTableSize; ++i) { + const QZoneData *data = zoneData(i); + if (data->windowsIdKey == windowsIdKey) + list << ianaId(data).split(' '); + } + + // Return the full list in alpha order + std::sort(list.begin(), list.end()); + return list; +} + +QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId, + QLocale::Country country) +{ + const quint16 windowsIdKey = toWindowsIdKey(windowsId); + for (int i = 0; i < zoneDataTableSize; ++i) { + const QZoneData *data = zoneData(i); + // Return the region matches in preference order + if (data->windowsIdKey == windowsIdKey && data->country == (quint16) country) + return ianaId(data).split(' '); + } + + return QList<QByteArray>(); +} + +// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly +template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone() +{ + return d->clone(); +} + +/* + UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used, + or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc. +*/ + +// Create default UTC time zone +QUtcTimeZonePrivate::QUtcTimeZonePrivate() +{ + const QString name = utcQString(); + init(utcQByteArray(), 0, name, name, QLocale::AnyCountry, name); +} + +// Create a named UTC time zone +QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id) +{ + // Look for the name in the UTC list, if found set the values + for (int i = 0; i < utcDataTableSize; ++i) { + const QUtcData *data = utcData(i); + const QByteArray uid = utcId(data); + if (uid == id) { + QString name = QString::fromUtf8(id); + init(id, data->offsetFromUtc, name, name, QLocale::AnyCountry, name); + break; + } + } +} + +// Create offset from UTC +QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds) +{ + QString utcId; + + if (offsetSeconds == 0) + utcId = utcQString(); + else + utcId = isoOffsetFormat(offsetSeconds); + + init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyCountry, utcId); +} + +QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds, + const QString &name, const QString &abbreviation, + QLocale::Country country, const QString &comment) +{ + init(zoneId, offsetSeconds, name, abbreviation, country, comment); +} + +QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other) + : QTimeZonePrivate(other), m_name(other.m_name), + m_abbreviation(other.m_abbreviation), + m_comment(other.m_comment), + m_country(other.m_country), + m_offsetFromUtc(other.m_offsetFromUtc) +{ +} + +QUtcTimeZonePrivate::~QUtcTimeZonePrivate() +{ +} + +QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const +{ + return new QUtcTimeZonePrivate(*this); +} + +QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + Data d; + d.abbreviation = m_abbreviation; + d.atMSecsSinceEpoch = forMSecsSinceEpoch; + d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc; + d.daylightTimeOffset = 0; + return d; +} + +void QUtcTimeZonePrivate::init(const QByteArray &zoneId) +{ + m_id = zoneId; +} + +void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name, + const QString &abbreviation, QLocale::Country country, + const QString &comment) +{ + m_id = zoneId; + m_offsetFromUtc = offsetSeconds; + m_name = name; + m_abbreviation = abbreviation; + m_country = country; + m_comment = comment; +} + +QLocale::Country QUtcTimeZonePrivate::country() const +{ + return m_country; +} + +QString QUtcTimeZonePrivate::comment() const +{ + return m_comment; +} + +QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + Q_UNUSED(timeType) + Q_UNUSED(locale) + if (nameType == QTimeZone::ShortName) + return m_abbreviation; + else if (nameType == QTimeZone::OffsetName) + return isoOffsetFormat(m_offsetFromUtc); + return m_name; +} + +QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return m_abbreviation; +} + +qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return m_offsetFromUtc; +} + +qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + Q_UNUSED(atMSecsSinceEpoch) + return 0; +} + +QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const +{ + return utcQByteArray(); +} + +bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const +{ + for (int i = 0; i < utcDataTableSize; ++i) { + const QUtcData *data = utcData(i); + if (utcId(data) == ianaId) { + return true; + } + } + return false; +} + +QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const +{ + QList<QByteArray> result; + result.reserve(utcDataTableSize); + for (int i = 0; i < utcDataTableSize; ++i) + result << utcId(utcData(i)); + std::sort(result.begin(), result.end()); // ### or already sorted?? + // ### assuming no duplicates + return result; +} + +QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + // If AnyCountry then is request for all non-region offset codes + if (country == QLocale::AnyCountry) + return availableTimeZoneIds(); + return QList<QByteArray>(); +} + +QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const +{ + QList<QByteArray> result; + for (int i = 0; i < utcDataTableSize; ++i) { + const QUtcData *data = utcData(i); + if (data->offsetFromUtc == offsetSeconds) + result << utcId(data); + } + std::sort(result.begin(), result.end()); // ### or already sorted?? + // ### assuming no duplicates + return result; +} + +#ifndef QT_NO_DATASTREAM +void QUtcTimeZonePrivate::serialize(QDataStream &ds) const +{ + ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name + << m_abbreviation << (qint32) m_country << m_comment; +} +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_android.cpp b/src/corelib/time/qtimezoneprivate_android.cpp new file mode 100644 index 0000000000..be4f374fdd --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_android.cpp @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Drew Parsons <dparsons@emerall.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QSet> +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +QT_BEGIN_NAMESPACE + +/* + Private + + Android implementation +*/ + +// Create the system default time zone +QAndroidTimeZonePrivate::QAndroidTimeZonePrivate() + : QTimeZonePrivate() +{ + // start with system time zone + androidTimeZone = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); + init("UTC"); +} + +// Create a named time zone +QAndroidTimeZonePrivate::QAndroidTimeZonePrivate(const QByteArray &ianaId) + : QTimeZonePrivate() +{ + init(ianaId); +} + +QAndroidTimeZonePrivate::QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &other) + : QTimeZonePrivate(other) +{ + androidTimeZone = other.androidTimeZone; + m_id = other.id(); +} + +QAndroidTimeZonePrivate::~QAndroidTimeZonePrivate() +{ +} + + +void QAndroidTimeZonePrivate::init(const QByteArray &ianaId) +{ + QJNIObjectPrivate jo_ianaId = QJNIObjectPrivate::fromString( QString::fromUtf8(ianaId) ); + androidTimeZone = QJNIObjectPrivate::callStaticObjectMethod( "java.util.TimeZone", "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;", static_cast<jstring>(jo_ianaId.object()) ); + + // Painfully, JNI gives us back a default zone object if it doesn't + // recognize the name; so check for whether ianaId is a recognized name of + // the zone object we got and ignore the zone if not. + // Try checking ianaId against getID(), getDisplayName(): + QJNIObjectPrivate jname = androidTimeZone.callObjectMethod("getID", "()Ljava/lang/String;"); + bool found = (jname.toString().toUtf8() == ianaId); + for (int style = 1; !found && style-- > 0;) { + for (int dst = 1; !found && dst-- > 0;) { + jname = androidTimeZone.callObjectMethod("getDisplayName", "(ZI;)Ljava/lang/String;", + bool(dst), style); + found = (jname.toString().toUtf8() == ianaId); + } + } + + if (!found) + m_id.clear(); + else if (ianaId.isEmpty()) + m_id = systemTimeZoneId(); + else + m_id = ianaId; +} + +QAndroidTimeZonePrivate *QAndroidTimeZonePrivate::clone() const +{ + return new QAndroidTimeZonePrivate(*this); +} + + +QString QAndroidTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const +{ + QString name; + + if (androidTimeZone.isValid()) { + jboolean daylightTime = (timeType == QTimeZone::DaylightTime); // treat QTimeZone::GenericTime as QTimeZone::StandardTime + + // treat all NameTypes as java TimeZone style LONG (value 1), except of course QTimeZone::ShortName which is style SHORT (value 0); + jint style = (nameType == QTimeZone::ShortName ? 0 : 1); + + QJNIObjectPrivate jlanguage = QJNIObjectPrivate::fromString(QLocale::languageToString(locale.language())); + QJNIObjectPrivate jcountry = QJNIObjectPrivate::fromString(QLocale::countryToString(locale.country())); + QJNIObjectPrivate jvariant = QJNIObjectPrivate::fromString(QLocale::scriptToString(locale.script())); + QJNIObjectPrivate jlocale("java.util.Locale", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", static_cast<jstring>(jlanguage.object()), static_cast<jstring>(jcountry.object()), static_cast<jstring>(jvariant.object())); + + QJNIObjectPrivate jname = androidTimeZone.callObjectMethod("getDisplayName", "(ZILjava/util/Locale;)Ljava/lang/String;", daylightTime, style, jlocale.object()); + + name = jname.toString(); + } + + return name; +} + +QString QAndroidTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + if ( isDaylightTime( atMSecsSinceEpoch ) ) + return displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QLocale() ); + else + return displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QLocale() ); +} + +int QAndroidTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + // offsetFromUtc( ) is invoked when androidTimeZone may not yet be set at startup, + // so a validity test is needed here + if ( androidTimeZone.isValid() ) + // the java method getOffset() returns milliseconds, but QTimeZone returns seconds + return androidTimeZone.callMethod<jint>( "getOffset", "(J)I", static_cast<jlong>(atMSecsSinceEpoch) ) / 1000; + else + return 0; +} + +int QAndroidTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + // the java method does not use a reference time + Q_UNUSED( atMSecsSinceEpoch ); + if ( androidTimeZone.isValid() ) + // the java method getRawOffset() returns milliseconds, but QTimeZone returns seconds + return androidTimeZone.callMethod<jint>( "getRawOffset" ) / 1000; + else + return 0; +} + +int QAndroidTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return ( offsetFromUtc(atMSecsSinceEpoch) - standardTimeOffset(atMSecsSinceEpoch) ); +} + +bool QAndroidTimeZonePrivate::hasDaylightTime() const +{ + if ( androidTimeZone.isValid() ) + /* note: the Java function only tests for future DST transtions, not past */ + return androidTimeZone.callMethod<jboolean>("useDaylightTime" ); + else + return false; +} + +bool QAndroidTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + if ( androidTimeZone.isValid() ) { + QJNIObjectPrivate jDate( "java/util/Date", "(J)V", static_cast<jlong>(atMSecsSinceEpoch) ); + return androidTimeZone.callMethod<jboolean>("inDaylightTime", "(Ljava/util/Date;)Z", jDate.object() ); + } + else + return false; +} + +QTimeZonePrivate::Data QAndroidTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + if (androidTimeZone.isValid()) { + Data data; + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + data.standardTimeOffset = standardTimeOffset(forMSecsSinceEpoch); + data.offsetFromUtc = offsetFromUtc(forMSecsSinceEpoch); + data.daylightTimeOffset = data.offsetFromUtc - data.standardTimeOffset; + data.abbreviation = abbreviation(forMSecsSinceEpoch); + return data; + } else { + return invalidData(); + } +} + +bool QAndroidTimeZonePrivate::hasTransitions() const +{ + // java.util.TimeZone does not directly provide transitions + return false; +} + +QTimeZonePrivate::Data QAndroidTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // transitions not available on Android, so return an invalid data object + Q_UNUSED( afterMSecsSinceEpoch ); + return invalidData(); +} + +QTimeZonePrivate::Data QAndroidTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // transitions not available on Android, so return an invalid data object + Q_UNUSED( beforeMSecsSinceEpoch ); + return invalidData(); +} + +QByteArray QAndroidTimeZonePrivate::systemTimeZoneId() const +{ + QJNIObjectPrivate androidSystemTimeZone = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); + QJNIObjectPrivate systemTZIdAndroid = androidSystemTimeZone.callObjectMethod<jstring>("getID"); + QByteArray systemTZid = systemTZIdAndroid.toString().toUtf8(); + + return systemTZid; +} + +QList<QByteArray> QAndroidTimeZonePrivate::availableTimeZoneIds() const +{ + QList<QByteArray> availableTimeZoneIdList; + QJNIObjectPrivate androidAvailableIdList = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getAvailableIDs", "()[Ljava/lang/String;"); + + QJNIEnvironmentPrivate jniEnv; + int androidTZcount = jniEnv->GetArrayLength( static_cast<jarray>(androidAvailableIdList.object()) ); + + // need separate jobject and QAndroidJniObject here so that we can delete (DeleteLocalRef) the reference to the jobject + // (or else the JNI reference table fills after 512 entries from GetObjectArrayElement) + jobject androidTZobject; + QJNIObjectPrivate androidTZ; + for (int i=0; i<androidTZcount; i++ ) { + androidTZobject = jniEnv->GetObjectArrayElement( static_cast<jobjectArray>( androidAvailableIdList.object() ), i ); + androidTZ = androidTZobject; + availableTimeZoneIdList.append( androidTZ.toString().toUtf8() ); + jniEnv->DeleteLocalRef(androidTZobject); + } + + return availableTimeZoneIdList; +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_data_p.h b/src/corelib/time/qtimezoneprivate_data_p.h new file mode 100644 index 0000000000..40d6c972c2 --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_data_p.h @@ -0,0 +1,1257 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QTIMEZONEPRIVATE_DATA_P_H +#define QTIMEZONEPRIVATE_DATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> + +QT_BEGIN_NAMESPACE + +/* + Windows Zone ID support, included in default base class build so can be used on all platforms, + e.g. an app running on Linux may need to communicate with a Windows Outlook server. These + tables can also be used to look-up Region Codes and UTC Offsets on platforms that don't directly + support them., e.g. Mac does not support availableTimeZones() filtering by region or offset. + + Another data table is provided for generic UTC+00:00 format time zones to be used as a + fall-back if no system time zones are available (QT_NO_SYSTEMLOCALE is set) or for QDateTimes + with a QT:Spec of OffsetFromUTC + + These tables are automatically adapted from the CLDR supplemental/windowsZones.xml data file + using a script in qtbase/util/locale_database. Please do not edit this data directly. In the + future if ICU is made a hard dependency then the ICU resource can be used directly and this + table removed +*/ + +struct QZoneData { + quint16 windowsIdKey; // Windows ID Key + quint16 country; // Country of IANA ID's, AnyCountry means No Country + quint16 ianaIdIndex; // All IANA ID's for the Windows ID and Country, space separated +}; + +struct QWindowsData { + quint16 windowsIdKey; // Windows ID Key + quint16 windowsIdIndex; // Windows ID Literal + quint16 ianaIdIndex; // Default IANA ID for the Windows ID + qint32 offsetFromUtc; // Standard Time Offset from UTC, used for quick look-ups +}; + +struct QUtcData { + quint16 ianaIdIndex; // IANA ID's + qint32 offsetFromUtc; // Offset form UTC is seconds +}; + +/* + COPYRIGHT AND PERMISSION NOTICE + + Copyright © 1991-2012 Unicode, Inc. All rights reserved. Distributed under + the Terms of Use in http://www.unicode.org/copyright.html. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of the Unicode data files and any associated documentation (the "Data + Files") or Unicode software and any associated documentation (the "Software") + to deal in the Data Files or Software without restriction, including without + limitation the rights to use, copy, modify, merge, publish, distribute, and/or + sell copies of the Data Files or Software, and to permit persons to whom the + Data Files or Software are furnished to do so, provided that (a) the above + copyright notice(s) and this permission notice appear with all copies of the + Data Files or Software, (b) both the above copyright notice(s) and this + permission notice appear in associated documentation, and (c) there is clear + notice in each modified Data File or in the Software as well as in the + documentation associated with the Data File(s) or Software that the data or + software has been modified. +*/ + +// GENERATED PART STARTS HERE + +/* + This part of the file was generated on 2019-05-28 from the + Common Locale Data Repository v35.1 supplemental/windowsZones.xml file $Revision: 14742 $ + + http://www.unicode.org/cldr/ + + Do not edit this code: run cldr2qtimezone.py on updated (or + edited) CLDR data; see qtbase/util/locale_database/. +*/ + +// Windows ID Key, Country Enum, IANA ID Index +static const QZoneData zoneDataTable[] = { + { 131, 143, 0 }, // W. Mongolia Standard Time / Mongolia + { 124, 112, 10 }, // UTC+12 / Kiribati + { 52, 94, 25 }, // Haiti Standard Time / Haiti + { 32, 44, 48 }, // China Standard Time / China + { 95, 244, 62 }, // SA Western Standard Time / Saint Barthelemy + { 25, 116, 84 }, // Central Asia Standard Time / Kyrgyzstan + { 36, 8, 97 }, // E. Africa Standard Time / Antarctica + { 33, 154, 114 }, // Chatham Islands Standard Time / New Zealand + { 95, 144, 130 }, // SA Western Standard Time / Montserrat + { 37, 13, 149 }, // E. Australia Standard Time / Australia + { 61, 0, 187 }, // Line Islands Standard Time / AnyCountry + { 132, 218, 198 }, // West Asia Standard Time / Turkmenistan + { 122, 30, 212 }, // UTC-02 / Brazil + { 24, 52, 228 }, // Central America Standard Time / Costa Rica + { 36, 67, 247 }, // E. Africa Standard Time / Eritrea + { 128, 8, 261 }, // W. Australia Standard Time / Antarctica + { 101, 101, 278 }, // SE Asia Standard Time / Indonesia + { 93, 8, 306 }, // SA Eastern Standard Time / Antarctica + { 4, 178, 325 }, // Altai Standard Time / Russia + { 95, 256, 338 }, // SA Western Standard Time / Sint Maarten + { 95, 60, 360 }, // SA Western Standard Time / Dominica + { 134, 167, 377 }, // West Pacific Standard Time / Papua New Guinea + { 13, 13, 398 }, // AUS Eastern Standard Time / Australia + { 69, 236, 435 }, // Morocco Standard Time / Western Sahara + { 39, 30, 451 }, // E. South America Standard Time / Brazil + { 124, 134, 469 }, // UTC+12 / Marshall Islands + { 125, 112, 502 }, // UTC+13 / Kiribati + { 103, 146, 520 }, // South Africa Standard Time / Mozambique + { 94, 30, 534 }, // SA Pacific Standard Time / Brazil + { 88, 74, 570 }, // Romance Standard Time / France + { 71, 38, 583 }, // Mountain Standard Time / Canada + { 72, 147, 657 }, // Myanmar Standard Time / Myanmar + { 26, 30, 670 }, // Central Brazilian Standard Time / Brazil + { 130, 123, 706 }, // W. Europe Standard Time / Liechtenstein + { 46, 73, 719 }, // FLE Standard Time / Finland + { 93, 70, 735 }, // SA Eastern Standard Time / Falkland Islands + { 78, 159, 752 }, // Norfolk Standard Time / Norfolk Island + { 53, 0, 768 }, // Hawaiian Standard Time / AnyCountry + { 28, 54, 779 }, // Central European Standard Time / Croatia + { 75, 150, 793 }, // Nepal Standard Time / Nepal + { 46, 33, 807 }, // FLE Standard Time / Bulgaria + { 6, 162, 820 }, // Arabian Standard Time / Oman + { 132, 131, 832 }, // West Asia Standard Time / Maldives + { 88, 197, 848 }, // Romance Standard Time / Spain + { 50, 91, 875 }, // Greenwich Standard Time / Guinea + { 5, 237, 890 }, // Arab Standard Time / Yemen + { 92, 222, 900 }, // Russian Standard Time / Ukraine + { 103, 204, 918 }, // South Africa Standard Time / Swaziland + { 130, 203, 933 }, // W. Europe Standard Time / Svalbard And Jan Mayen Islands + { 7, 103, 953 }, // Arabic Standard Time / Iraq + { 119, 226, 966 }, // UTC-11 / United States Minor Outlying Islands + { 5, 115, 981 }, // Arab Standard Time / Kuwait + { 50, 189, 993 }, // Greenwich Standard Time / Sierra Leone + { 31, 0, 1009 }, // Central Standard Time / AnyCountry + { 53, 51, 1017 }, // Hawaiian Standard Time / Cook Islands + { 129, 50, 1035 }, // W. Central Africa Standard Time / Congo Brazzaville + { 64, 43, 1054 }, // Magallanes Standard Time / Chile + { 119, 0, 1075 }, // UTC-11 / AnyCountry + { 84, 38, 1086 }, // Pacific Standard Time / Canada + { 22, 11, 1138 }, // Caucasus Standard Time / Armenia + { 130, 142, 1151 }, // W. Europe Standard Time / Monaco + { 103, 239, 1165 }, // South Africa Standard Time / Zambia + { 46, 222, 1179 }, // FLE Standard Time / Ukraine + { 87, 168, 1225 }, // Paraguay Standard Time / Paraguay + { 57, 109, 1242 }, // Jordan Standard Time / Jordan + { 109, 30, 1253 }, // Tocantins Standard Time / Brazil + { 55, 102, 1271 }, // Iran Standard Time / Iran + { 101, 8, 1283 }, // SE Asia Standard Time / Antarctica + { 27, 57, 1300 }, // Central Europe Standard Time / Czech Republic + { 95, 215, 1314 }, // SA Western Standard Time / Trinidad And Tobago + { 103, 28, 1336 }, // South Africa Standard Time / Botswana + { 132, 0, 1352 }, // West Asia Standard Time / AnyCountry + { 94, 63, 1362 }, // SA Pacific Standard Time / Ecuador + { 51, 85, 1380 }, // GTB Standard Time / Greece + { 36, 128, 1394 }, // E. Africa Standard Time / Madagascar + { 53, 226, 1414 }, // Hawaiian Standard Time / United States Minor Outlying Islands + { 94, 107, 1431 }, // SA Pacific Standard Time / Jamaica + { 104, 198, 1447 }, // Sri Lanka Standard Time / Sri Lanka + { 27, 243, 1460 }, // Central Europe Standard Time / Serbia + { 25, 110, 1476 }, // Central Asia Standard Time / Kazakhstan + { 125, 0, 1502 }, // UTC+13 / AnyCountry + { 94, 38, 1513 }, // SA Pacific Standard Time / Canada + { 25, 31, 1535 }, // Central Asia Standard Time / British Indian Ocean Territory + { 108, 13, 1549 }, // Tasmania Standard Time / Australia + { 95, 174, 1583 }, // SA Western Standard Time / Puerto Rico + { 95, 180, 1603 }, // SA Western Standard Time / Saint Kitts And Nevis + { 130, 206, 1620 }, // W. Europe Standard Time / Switzerland + { 117, 225, 1634 }, // US Eastern Standard Time / United States + { 29, 140, 1701 }, // Central Pacific Standard Time / Micronesia + { 120, 77, 1731 }, // UTC-09 / French Polynesia + { 129, 156, 1747 }, // W. Central Africa Standard Time / Niger + { 118, 139, 1761 }, // US Mountain Standard Time / Mexico + { 36, 194, 1780 }, // E. Africa Standard Time / Somalia + { 118, 0, 1797 }, // US Mountain Standard Time / AnyCountry + { 10, 24, 1807 }, // Atlantic Standard Time / Bermuda + { 103, 240, 1824 }, // South Africa Standard Time / Zimbabwe + { 32, 126, 1838 }, // China Standard Time / Macau + { 129, 66, 1849 }, // W. Central Africa Standard Time / Equatorial Guinea + { 66, 137, 1863 }, // Mauritius Standard Time / Mauritius + { 46, 68, 1880 }, // FLE Standard Time / Estonia + { 50, 187, 1895 }, // Greenwich Standard Time / Senegal + { 132, 110, 1908 }, // West Asia Standard Time / Kazakhstan + { 25, 44, 1968 }, // Central Asia Standard Time / China + { 130, 106, 1980 }, // W. Europe Standard Time / Italy + { 48, 251, 1992 }, // GMT Standard Time / Isle Of Man + { 36, 210, 2011 }, // E. Africa Standard Time / Tanzania + { 10, 86, 2032 }, // Atlantic Standard Time / Greenland + { 123, 86, 2046 }, // UTC / Greenland + { 20, 38, 2067 }, // Canada Central Standard Time / Canada + { 15, 86, 2104 }, // Azores Standard Time / Greenland + { 69, 145, 2125 }, // Morocco Standard Time / Morocco + { 115, 219, 2143 }, // Turks And Caicos Standard Time / Turks And Caicos Islands + { 50, 80, 2162 }, // Greenwich Standard Time / Gambia + { 129, 42, 2176 }, // W. Central Africa Standard Time / Chad + { 56, 105, 2192 }, // Israel Standard Time / Israel + { 64, 8, 2207 }, // Magallanes Standard Time / Antarctica + { 12, 13, 2225 }, // Aus Central W. Standard Time / Australia + { 24, 155, 2241 }, // Central America Standard Time / Nicaragua + { 102, 170, 2257 }, // Singapore Standard Time / Philippines + { 134, 160, 2269 }, // West Pacific Standard Time / Northern Mariana Islands + { 43, 64, 2284 }, // Egypt Standard Time / Egypt + { 88, 21, 2297 }, // Romance Standard Time / Belgium + { 76, 8, 2313 }, // New Zealand Standard Time / Antarctica + { 51, 177, 2332 }, // GTB Standard Time / Romania + { 103, 0, 2349 }, // South Africa Standard Time / AnyCountry + { 41, 225, 2359 }, // Eastern Standard Time / United States + { 129, 23, 2516 }, // W. Central Africa Standard Time / Benin + { 79, 178, 2534 }, // North Asia East Standard Time / Russia + { 116, 143, 2547 }, // Ulaanbaatar Standard Time / Mongolia + { 130, 14, 2580 }, // W. Europe Standard Time / Austria + { 41, 38, 2594 }, // Eastern Standard Time / Canada + { 95, 255, 2699 }, // SA Western Standard Time / Bonaire + { 124, 149, 2718 }, // UTC+12 / Nauru + { 134, 8, 2732 }, // West Pacific Standard Time / Antarctica + { 63, 178, 2758 }, // Magadan Standard Time / Russia + { 130, 161, 2771 }, // W. Europe Standard Time / Norway + { 110, 0, 2783 }, // Tokyo Standard Time / AnyCountry + { 24, 63, 2793 }, // Central America Standard Time / Ecuador + { 103, 35, 2811 }, // South Africa Standard Time / Burundi + { 10, 38, 2828 }, // Atlantic Standard Time / Canada + { 29, 0, 2896 }, // Central Pacific Standard Time / AnyCountry + { 95, 87, 2907 }, // SA Western Standard Time / Grenada + { 29, 153, 2923 }, // Central Pacific Standard Time / New Caledonia + { 42, 139, 2938 }, // Eastern Standard Time (Mexico) / Mexico + { 2, 225, 2953 }, // Alaskan Standard Time / United States + { 49, 86, 3029 }, // Greenland Standard Time / Greenland + { 50, 92, 3045 }, // Greenwich Standard Time / Guinea Bissau + { 130, 184, 3059 }, // W. Europe Standard Time / San Marino + { 27, 98, 3077 }, // Central Europe Standard Time / Hungary + { 24, 96, 3093 }, // Central America Standard Time / Honduras + { 62, 13, 3113 }, // Lord Howe Standard Time / Australia + { 36, 0, 3133 }, // E. Africa Standard Time / AnyCountry + { 129, 79, 3143 }, // W. Central Africa Standard Time / Gabon + { 95, 182, 3161 }, // SA Western Standard Time / Saint Vincent And The Grenadines + { 48, 224, 3180 }, // GMT Standard Time / United Kingdom + { 68, 227, 3194 }, // Montevideo Standard Time / Uruguay + { 124, 0, 3213 }, // UTC+12 / AnyCountry + { 130, 230, 3224 }, // W. Europe Standard Time / Vatican City State + { 50, 99, 3239 }, // Greenwich Standard Time / Iceland + { 34, 55, 3258 }, // Cuba Standard Time / Cuba + { 41, 16, 3273 }, // Eastern Standard Time / Bahamas + { 122, 196, 3288 }, // UTC-02 / South Georgia And The South Sandwich Islands + { 24, 65, 3311 }, // Central America Standard Time / El Salvador + { 31, 225, 3331 }, // Central Standard Time / United States + { 95, 0, 3499 }, // SA Western Standard Time / AnyCountry + { 94, 166, 3509 }, // SA Pacific Standard Time / Panama + { 94, 47, 3524 }, // SA Pacific Standard Time / Colombia + { 70, 139, 3539 }, // Mountain Standard Time (Mexico) / Mexico + { 124, 220, 3574 }, // UTC+12 / Tuvalu + { 130, 84, 3591 }, // W. Europe Standard Time / Gibraltar + { 82, 178, 3608 }, // Omsk Standard Time / Russia + { 60, 122, 3618 }, // Libya Standard Time / Libya + { 25, 8, 3633 }, // Central Asia Standard Time / Antarctica + { 95, 12, 3651 }, // SA Western Standard Time / Aruba + { 67, 119, 3665 }, // Middle East Standard Time / Lebanon + { 102, 0, 3677 }, // Singapore Standard Time / AnyCountry + { 74, 148, 3687 }, // Namibia Standard Time / Namibia + { 126, 231, 3703 }, // Venezuela Standard Time / Venezuela + { 95, 234, 3719 }, // SA Western Standard Time / United States Virgin Islands + { 21, 0, 3737 }, // Cape Verde Standard Time / AnyCountry + { 95, 9, 3747 }, // SA Western Standard Time / Antigua And Barbuda + { 94, 169, 3763 }, // SA Pacific Standard Time / Peru + { 46, 248, 3776 }, // FLE Standard Time / Aland Islands + { 50, 199, 3793 }, // Greenwich Standard Time / Saint Helena + { 134, 140, 3812 }, // West Pacific Standard Time / Micronesia + { 102, 190, 3825 }, // Singapore Standard Time / Singapore + { 95, 61, 3840 }, // SA Western Standard Time / Dominican Republic + { 103, 129, 3862 }, // South Africa Standard Time / Malawi + { 30, 139, 3878 }, // Central Standard Time (Mexico) / Mexico + { 102, 130, 3954 }, // Singapore Standard Time / Malaysia + { 45, 72, 3985 }, // Fiji Standard Time / Fiji + { 118, 225, 3998 }, // US Mountain Standard Time / United States + { 17, 25, 4014 }, // Bangladesh Standard Time / Bhutan + { 130, 133, 4027 }, // W. Europe Standard Time / Malta + { 92, 178, 4040 }, // Russian Standard Time / Russia + { 95, 135, 4084 }, // SA Western Standard Time / Martinique + { 35, 0, 4103 }, // Dateline Standard Time / AnyCountry + { 135, 178, 4114 }, // Yakutsk Standard Time / Russia + { 1, 1, 4141 }, // Afghanistan Standard Time / Afghanistan + { 123, 0, 4152 }, // UTC / AnyCountry + { 31, 139, 4168 }, // Central Standard Time / Mexico + { 6, 0, 4186 }, // Arabian Standard Time / AnyCountry + { 101, 45, 4196 }, // SE Asia Standard Time / Christmas Island + { 15, 173, 4213 }, // Azores Standard Time / Portugal + { 129, 0, 4229 }, // W. Central Africa Standard Time / AnyCountry + { 17, 18, 4239 }, // Bangladesh Standard Time / Bangladesh + { 31, 38, 4250 }, // Central Standard Time / Canada + { 94, 0, 4325 }, // SA Pacific Standard Time / AnyCountry + { 125, 213, 4335 }, // UTC+13 / Tokelau + { 73, 178, 4351 }, // N. Central Asia Standard Time / Russia + { 133, 165, 4368 }, // West Bank Standard Time / Palestinian Territories + { 114, 217, 4390 }, // Turkey Standard Time / Turkey + { 3, 225, 4406 }, // Aleutian Standard Time / United States + { 101, 0, 4419 }, // SE Asia Standard Time / AnyCountry + { 71, 225, 4429 }, // Mountain Standard Time / United States + { 36, 69, 4458 }, // E. Africa Standard Time / Ethiopia + { 130, 151, 4477 }, // W. Europe Standard Time / Netherlands + { 95, 245, 4494 }, // SA Western Standard Time / Saint Martin + { 48, 173, 4510 }, // GMT Standard Time / Portugal + { 46, 124, 4541 }, // FLE Standard Time / Lithuania + { 130, 82, 4556 }, // W. Europe Standard Time / Germany + { 65, 77, 4586 }, // Marquesas Standard Time / French Polynesia + { 80, 178, 4604 }, // North Asia Standard Time / Russia + { 61, 112, 4639 }, // Line Islands Standard Time / Kiribati + { 96, 200, 4658 }, // Saint Pierre Standard Time / Saint Pierre And Miquelon + { 48, 104, 4675 }, // GMT Standard Time / Ireland + { 5, 186, 4689 }, // Arab Standard Time / Saudi Arabia + { 83, 43, 4701 }, // Pacific SA Standard Time / Chile + { 91, 178, 4718 }, // Russia Time Zone 11 / Russia + { 36, 48, 4745 }, // E. Africa Standard Time / Comoros + { 95, 152, 4759 }, // SA Western Standard Time / Cura Sao + { 38, 141, 4775 }, // E. Europe Standard Time / Moldova + { 24, 22, 4791 }, // Central America Standard Time / Belize + { 103, 195, 4806 }, // South Africa Standard Time / South Africa + { 127, 178, 4826 }, // Vladivostok Standard Time / Russia + { 122, 0, 4857 }, // UTC-02 / AnyCountry + { 106, 207, 4867 }, // Syria Standard Time / Syria + { 93, 76, 4881 }, // SA Eastern Standard Time / French Guiana + { 50, 136, 4897 }, // Greenwich Standard Time / Mauritania + { 41, 0, 4915 }, // Eastern Standard Time / AnyCountry + { 16, 30, 4923 }, // Bahia Standard Time / Brazil + { 40, 43, 4937 }, // Easter Island Standard Time / Chile + { 93, 0, 4952 }, // SA Eastern Standard Time / AnyCountry + { 9, 178, 4962 }, // Astrakhan Standard Time / Russia + { 95, 30, 4996 }, // SA Western Standard Time / Brazil + { 18, 20, 5049 }, // Belarus Standard Time / Belarus + { 95, 181, 5062 }, // SA Western Standard Time / Saint Lucia + { 129, 6, 5079 }, // W. Central Africa Standard Time / Angola + { 129, 157, 5093 }, // W. Central Africa Standard Time / Nigeria + { 130, 5, 5106 }, // W. Europe Standard Time / Andorra + { 58, 178, 5121 }, // Kaliningrad Standard Time / Russia + { 71, 0, 5140 }, // Mountain Standard Time / AnyCountry + { 95, 7, 5148 }, // SA Western Standard Time / Anguilla + { 124, 235, 5165 }, // UTC+12 / Wallis And Futuna Islands + { 6, 223, 5180 }, // Arabian Standard Time / United Arab Emirates + { 94, 40, 5191 }, // SA Pacific Standard Time / Cayman Islands + { 101, 211, 5206 }, // SE Asia Standard Time / Thailand + { 29, 193, 5219 }, // Central Pacific Standard Time / Solomon Islands + { 47, 81, 5239 }, // Georgian Standard Time / Georgia + { 101, 36, 5252 }, // SE Asia Standard Time / Cambodia + { 132, 228, 5268 }, // West Asia Standard Time / Uzbekistan + { 51, 56, 5297 }, // GTB Standard Time / Cyprus + { 95, 88, 5325 }, // SA Western Standard Time / Guadeloupe + { 101, 232, 5344 }, // SE Asia Standard Time / Vietnam + { 113, 178, 5356 }, // Transbaikal Standard Time / Russia + { 50, 121, 5367 }, // Greenwich Standard Time / Liberia + { 95, 233, 5383 }, // SA Western Standard Time / British Virgin Islands + { 129, 49, 5399 }, // W. Central Africa Standard Time / Congo Kinshasa + { 97, 178, 5415 }, // Sakhalin Standard Time / Russia + { 124, 226, 5429 }, // UTC+12 / United States Minor Outlying Islands + { 50, 83, 5442 }, // Greenwich Standard Time / Ghana + { 76, 154, 5455 }, // New Zealand Standard Time / New Zealand + { 23, 13, 5472 }, // Cen. Australia Standard Time / Australia + { 53, 77, 5513 }, // Hawaiian Standard Time / French Polynesia + { 50, 34, 5528 }, // Greenwich Standard Time / Burkina Faso + { 132, 78, 5547 }, // West Asia Standard Time / French Southern Territories + { 121, 0, 5564 }, // UTC-08 / AnyCountry + { 27, 2, 5574 }, // Central Europe Standard Time / Albania + { 107, 208, 5588 }, // Taipei Standard Time / Taiwan + { 88, 58, 5600 }, // Romance Standard Time / Denmark + { 36, 221, 5618 }, // E. Africa Standard Time / Uganda + { 95, 19, 5633 }, // SA Western Standard Time / Barbados + { 14, 15, 5650 }, // Azerbaijan Standard Time / Azerbaijan + { 32, 97, 5660 }, // China Standard Time / Hong Kong + { 110, 101, 5675 }, // Tokyo Standard Time / Indonesia + { 53, 225, 5689 }, // Hawaiian Standard Time / United States + { 36, 111, 5706 }, // E. Africa Standard Time / Kenya + { 134, 89, 5721 }, // West Pacific Standard Time / Guam + { 36, 254, 5734 }, // E. Africa Standard Time / South Sudan + { 48, 71, 5746 }, // GMT Standard Time / Faroe Islands + { 90, 178, 5762 }, // Russia Time Zone 10 / Russia + { 119, 158, 5781 }, // UTC-11 / Niue + { 129, 3, 5794 }, // W. Central Africa Standard Time / Algeria + { 110, 62, 5809 }, // Tokyo Standard Time / East Timor + { 93, 30, 5819 }, // SA Eastern Standard Time / Brazil + { 27, 242, 5898 }, // Central Europe Standard Time / Montenegro + { 129, 37, 5915 }, // W. Central Africa Standard Time / Cameroon + { 101, 117, 5929 }, // SE Asia Standard Time / Laos + { 85, 139, 5944 }, // Pacific Standard Time (Mexico) / Mexico + { 50, 212, 5981 }, // Greenwich Standard Time / Togo + { 46, 118, 5993 }, // FLE Standard Time / Latvia + { 95, 38, 6005 }, // SA Western Standard Time / Canada + { 132, 209, 6026 }, // West Asia Standard Time / Tajikistan + { 77, 38, 6040 }, // Newfoundland Standard Time / Canada + { 110, 108, 6057 }, // Tokyo Standard Time / Japan + { 25, 0, 6068 }, // Central Asia Standard Time / AnyCountry + { 28, 27, 6078 }, // Central European Standard Time / Bosnia And Herzegowina + { 27, 191, 6094 }, // Central Europe Standard Time / Slovakia + { 95, 93, 6112 }, // SA Western Standard Time / Guyana + { 48, 197, 6127 }, // GMT Standard Time / Spain + { 19, 167, 6143 }, // Bougainville Standard Time / Papua New Guinea + { 5, 17, 6164 }, // Arab Standard Time / Bahrain + { 24, 90, 6177 }, // Central America Standard Time / Guatemala + { 95, 26, 6195 }, // SA Western Standard Time / Bolivia + { 81, 113, 6210 }, // North Korea Standard Time / North Korea + { 119, 4, 6225 }, // UTC-11 / American Samoa + { 66, 176, 6243 }, // Mauritius Standard Time / Reunion + { 103, 120, 6258 }, // South Africa Standard Time / Lesotho + { 84, 0, 6272 }, // Pacific Standard Time / AnyCountry + { 120, 0, 6280 }, // UTC-09 / AnyCountry + { 129, 216, 6290 }, // W. Central Africa Standard Time / Tunisia + { 99, 185, 6303 }, // Sao Tome Standard Time / Sao Tome And Principe + { 100, 178, 6319 }, // Saratov Standard Time / Russia + { 105, 201, 6334 }, // Sudan Standard Time / Sudan + { 48, 252, 6350 }, // GMT Standard Time / Jersey + { 29, 13, 6364 }, // Central Pacific Standard Time / Australia + { 71, 139, 6385 }, // Mountain Standard Time / Mexico + { 21, 39, 6401 }, // Cape Verde Standard Time / Cape Verde + { 102, 101, 6421 }, // Singapore Standard Time / Indonesia + { 27, 192, 6435 }, // Central Europe Standard Time / Slovenia + { 48, 75, 6452 }, // GMT Standard Time / Guernsey + { 132, 8, 6468 }, // West Asia Standard Time / Antarctica + { 8, 10, 6486 }, // Argentina Standard Time / Argentina + { 98, 183, 6759 }, // Samoa Standard Time / Samoa + { 129, 41, 6772 }, // W. Central Africa Standard Time / Central African Republic + { 111, 178, 6786 }, // Tomsk Standard Time / Russia + { 110, 164, 6797 }, // Tokyo Standard Time / Palau + { 11, 13, 6811 }, // AUS Central Standard Time / Australia + { 121, 171, 6828 }, // UTC-08 / Pitcairn + { 102, 32, 6845 }, // Singapore Standard Time / Brunei + { 112, 214, 6857 }, // Tonga Standard Time / Tonga + { 89, 178, 6875 }, // Russia Time Zone 3 / Russia + { 128, 13, 6889 }, // W. Australia Standard Time / Australia + { 28, 172, 6905 }, // Central European Standard Time / Poland + { 72, 46, 6919 }, // Myanmar Standard Time / Cocos Islands + { 66, 188, 6932 }, // Mauritius Standard Time / Seychelles + { 84, 225, 6944 }, // Pacific Standard Time / United States + { 54, 100, 6983 }, // India Standard Time / India + { 50, 53, 6997 }, // Greenwich Standard Time / Ivory Coast + { 24, 0, 7012 }, // Central America Standard Time / AnyCountry + { 29, 229, 7022 }, // Central Pacific Standard Time / Vanuatu + { 130, 125, 7036 }, // W. Europe Standard Time / Luxembourg + { 50, 132, 7054 }, // Greenwich Standard Time / Mali + { 103, 179, 7068 }, // South Africa Standard Time / Rwanda + { 5, 175, 7082 }, // Arab Standard Time / Qatar + { 86, 163, 7093 }, // Pakistan Standard Time / Pakistan + { 134, 0, 7106 }, // West Pacific Standard Time / AnyCountry + { 36, 59, 7117 }, // E. Africa Standard Time / Djibouti + { 44, 178, 7133 }, // Ekaterinburg Standard Time / Russia + { 118, 38, 7152 }, // US Mountain Standard Time / Canada + { 36, 138, 7209 }, // E. Africa Standard Time / Mayotte + { 28, 127, 7224 }, // Central European Standard Time / Macedonia + { 59, 114, 7238 }, // Korea Standard Time / South Korea + { 93, 202, 7249 }, // SA Eastern Standard Time / Suriname + { 130, 205, 7268 }, // W. Europe Standard Time / Sweden + { 103, 49, 7285 }, // South Africa Standard Time / Congo Kinshasa + { 0, 0, 0 } // Trailing zeroes +}; + +// Windows ID Key, Windows ID Index, IANA ID Index, UTC Offset +static const QWindowsData windowsDataTable[] = { + { 1, 0, 4141, 16200 }, // Afghanistan Standard Time + { 2, 26, 7303,-32400 }, // Alaskan Standard Time + { 3, 48, 4406,-36000 }, // Aleutian Standard Time + { 4, 71, 325, 25200 }, // Altai Standard Time + { 5, 91, 4689, 10800 }, // Arab Standard Time + { 6, 110, 5180, 14400 }, // Arabian Standard Time + { 7, 132, 953, 10800 }, // Arabic Standard Time + { 8, 153, 7321,-10800 }, // Argentina Standard Time + { 9, 177, 7342, 14400 }, // Astrakhan Standard Time + { 10, 201, 7359,-14400 }, // Atlantic Standard Time + { 11, 224, 6811, 34200 }, // AUS Central Standard Time + { 12, 250, 2225, 31500 }, // Aus Central W. Standard Time + { 13, 279, 7375, 36000 }, // AUS Eastern Standard Time + { 14, 305, 5650, 14400 }, // Azerbaijan Standard Time + { 15, 330, 4213, -3600 }, // Azores Standard Time + { 16, 351, 4923,-10800 }, // Bahia Standard Time + { 17, 371, 4239, 21600 }, // Bangladesh Standard Time + { 18, 396, 5049, 10800 }, // Belarus Standard Time + { 19, 418, 6143, 39600 }, // Bougainville Standard Time + { 20, 445, 7392,-21600 }, // Canada Central Standard Time + { 21, 474, 6401, -3600 }, // Cape Verde Standard Time + { 22, 499, 1138, 14400 }, // Caucasus Standard Time + { 23, 522, 7407, 34200 }, // Cen. Australia Standard Time + { 24, 551, 6177,-21600 }, // Central America Standard Time + { 25, 581, 7426, 21600 }, // Central Asia Standard Time + { 26, 608, 7438,-14400 }, // Central Brazilian Standard Time + { 27, 640, 3077, 3600 }, // Central Europe Standard Time + { 28, 669, 6905, 3600 }, // Central European Standard Time + { 29, 700, 5219, 39600 }, // Central Pacific Standard Time + { 30, 730, 7453,-21600 }, // Central Standard Time (Mexico) + { 31, 761, 7473,-21600 }, // Central Standard Time + { 32, 783, 48, 28800 }, // China Standard Time + { 33, 803, 114, 45900 }, // Chatham Islands Standard Time + { 34, 833, 3258,-18000 }, // Cuba Standard Time + { 35, 852, 4103,-43200 }, // Dateline Standard Time + { 36, 875, 5706, 10800 }, // E. Africa Standard Time + { 37, 899, 7489, 36000 }, // E. Australia Standard Time + { 38, 926, 4775, 7200 }, // E. Europe Standard Time + { 39, 950, 451,-10800 }, // E. South America Standard Time + { 40, 981, 4937,-21600 }, // Easter Island Standard Time + { 41, 1009, 7508,-18000 }, // Eastern Standard Time + { 42, 1031, 2938,-18000 }, // Eastern Standard Time (Mexico) + { 43, 1062, 2284, 7200 }, // Egypt Standard Time + { 44, 1082, 7133, 18000 }, // Ekaterinburg Standard Time + { 45, 1109, 3985, 43200 }, // Fiji Standard Time + { 46, 1128, 7525, 7200 }, // FLE Standard Time + { 47, 1146, 5239, 14400 }, // Georgian Standard Time + { 48, 1169, 3180, 0 }, // GMT Standard Time + { 49, 1187, 3029,-10800 }, // Greenland Standard Time + { 50, 1211, 3239, 0 }, // Greenwich Standard Time + { 51, 1235, 2332, 7200 }, // GTB Standard Time + { 52, 1253, 25,-18000 }, // Haiti Standard Time + { 53, 1273, 5689,-36000 }, // Hawaiian Standard Time + { 54, 1296, 6983, 19800 }, // India Standard Time + { 55, 1316, 1271, 12600 }, // Iran Standard Time + { 56, 1335, 2192, 7200 }, // Israel Standard Time + { 57, 1356, 1242, 7200 }, // Jordan Standard Time + { 58, 1377, 5121, 7200 }, // Kaliningrad Standard Time + { 59, 1403, 7238, 32400 }, // Korea Standard Time + { 60, 1423, 3618, 7200 }, // Libya Standard Time + { 61, 1443, 4639, 50400 }, // Line Islands Standard Time + { 62, 1470, 3113, 37800 }, // Lord Howe Standard Time + { 63, 1494, 2758, 36000 }, // Magadan Standard Time + { 64, 1516, 1054,-10800 }, // Magallanes Standard Time + { 65, 1541, 4586,-34200 }, // Marquesas Standard Time + { 66, 1565, 1863, 14400 }, // Mauritius Standard Time + { 67, 1589, 3665, 7200 }, // Middle East Standard Time + { 68, 1615, 3194,-10800 }, // Montevideo Standard Time + { 69, 1640, 2125, 0 }, // Morocco Standard Time + { 70, 1662, 7537,-25200 }, // Mountain Standard Time (Mexico) + { 71, 1694, 7555,-25200 }, // Mountain Standard Time + { 72, 1717, 657, 23400 }, // Myanmar Standard Time + { 73, 1739, 4351, 21600 }, // N. Central Asia Standard Time + { 74, 1769, 3687, 3600 }, // Namibia Standard Time + { 75, 1791, 793, 20700 }, // Nepal Standard Time + { 76, 1811, 5455, 43200 }, // New Zealand Standard Time + { 77, 1837, 6040,-12600 }, // Newfoundland Standard Time + { 78, 1864, 752, 39600 }, // Norfolk Standard Time + { 79, 1886, 2534, 28800 }, // North Asia East Standard Time + { 80, 1916, 7570, 25200 }, // North Asia Standard Time + { 81, 1941, 6210, 30600 }, // North Korea Standard Time + { 82, 1967, 3608, 21600 }, // Omsk Standard Time + { 83, 1986, 4701,-10800 }, // Pacific SA Standard Time + { 84, 2011, 7587,-28800 }, // Pacific Standard Time + { 85, 2033, 7607,-28800 }, // Pacific Standard Time (Mexico) + { 86, 2064, 7093, 18000 }, // Pakistan Standard Time + { 87, 2087, 1225,-14400 }, // Paraguay Standard Time + { 88, 2110, 570, 3600 }, // Romance Standard Time + { 89, 2132, 6875, 14400 }, // Russia Time Zone 3 + { 90, 2151, 5762, 39600 }, // Russia Time Zone 10 + { 91, 2171, 7623, 43200 }, // Russia Time Zone 11 + { 92, 2191, 7638, 10800 }, // Russian Standard Time + { 93, 2213, 4881,-10800 }, // SA Eastern Standard Time + { 94, 2238, 3524,-18000 }, // SA Pacific Standard Time + { 95, 2263, 6195,-14400 }, // SA Western Standard Time + { 96, 2288, 4658,-10800 }, // Saint Pierre Standard Time + { 97, 2315, 5415, 39600 }, // Sakhalin Standard Time + { 98, 2338, 6759, 46800 }, // Samoa Standard Time + { 99, 2358, 6303, 0 }, // Sao Tome Standard Time + { 100, 2381, 6319, 14400 }, // Saratov Standard Time + { 101, 2403, 5206, 25200 }, // SE Asia Standard Time + { 102, 2425, 3825, 28800 }, // Singapore Standard Time + { 103, 2449, 4806, 7200 }, // South Africa Standard Time + { 104, 2476, 1447, 19800 }, // Sri Lanka Standard Time + { 105, 2500, 6334, 7200 }, // Sudan Standard Time + { 106, 2520, 4867, 7200 }, // Syria Standard Time + { 107, 2540, 5588, 28800 }, // Taipei Standard Time + { 108, 2561, 7652, 36000 }, // Tasmania Standard Time + { 109, 2584, 1253,-10800 }, // Tocantins Standard Time + { 110, 2608, 6057, 32400 }, // Tokyo Standard Time + { 111, 2628, 6786, 25200 }, // Tomsk Standard Time + { 112, 2648, 6857, 46800 }, // Tonga Standard Time + { 113, 2668, 5356, 32400 }, // Transbaikal Standard Time + { 114, 2694, 4390, 7200 }, // Turkey Standard Time + { 115, 2715, 2143,-14400 }, // Turks And Caicos Standard Time + { 116, 2746, 7669, 28800 }, // Ulaanbaatar Standard Time + { 117, 2772, 7686,-18000 }, // US Eastern Standard Time + { 118, 2797, 3998,-25200 }, // US Mountain Standard Time + { 119, 2823, 1075,-39600 }, // UTC-11 + { 120, 2830, 6280,-32400 }, // UTC-09 + { 121, 2837, 5564,-28800 }, // UTC-08 + { 122, 2844, 4857, -7200 }, // UTC-02 + { 123, 2851, 7707, 0 }, // UTC + { 124, 2855, 3213, 43200 }, // UTC+12 + { 125, 2862, 1502, 46800 }, // UTC+13 + { 126, 2869, 3703,-16200 }, // Venezuela Standard Time + { 127, 2893, 7715, 36000 }, // Vladivostok Standard Time + { 128, 2919, 6889, 28800 }, // W. Australia Standard Time + { 129, 2946, 5093, 3600 }, // W. Central Africa Standard Time + { 130, 2978, 7732, 3600 }, // W. Europe Standard Time + { 131, 3002, 0, 25200 }, // W. Mongolia Standard Time + { 132, 3028, 7746, 18000 }, // West Asia Standard Time + { 133, 3052, 7760, 7200 }, // West Bank Standard Time + { 134, 3076, 377, 36000 }, // West Pacific Standard Time + { 135, 3103, 7772, 32400 }, // Yakutsk Standard Time + { 0, 0, 0, 0 } // Trailing zeroes +}; + +// IANA ID Index, UTC Offset +static const QUtcData utcDataTable[] = { + { 7785, 0 }, // UTC + { 7789,-50400 }, // UTC-14:00 + { 7799,-46800 }, // UTC-13:00 + { 7809,-43200 }, // UTC-12:00 + { 7819,-39600 }, // UTC-11:00 + { 7829,-36000 }, // UTC-10:00 + { 7839,-32400 }, // UTC-09:00 + { 7849,-28800 }, // UTC-08:00 + { 7859,-25200 }, // UTC-07:00 + { 7869,-21600 }, // UTC-06:00 + { 7879,-18000 }, // UTC-05:00 + { 7889,-16200 }, // UTC-04:30 + { 7899,-14400 }, // UTC-04:00 + { 7909,-12600 }, // UTC-03:30 + { 7919,-10800 }, // UTC-03:00 + { 7929, -7200 }, // UTC-02:00 + { 7939, -3600 }, // UTC-01:00 + { 7949, 0 }, // UTC-00:00 + { 7959, 0 }, // UTC+00:00 + { 7969, 3600 }, // UTC+01:00 + { 7979, 7200 }, // UTC+02:00 + { 7989, 10800 }, // UTC+03:00 + { 7999, 12600 }, // UTC+03:30 + { 8009, 14400 }, // UTC+04:00 + { 8019, 16200 }, // UTC+04:30 + { 8029, 18000 }, // UTC+05:00 + { 8039, 19800 }, // UTC+05:30 + { 8049, 20700 }, // UTC+05:45 + { 8059, 21600 }, // UTC+06:00 + { 8069, 23400 }, // UTC+06:30 + { 8079, 25200 }, // UTC+07:00 + { 8089, 28800 }, // UTC+08:00 + { 8099, 30600 }, // UTC+08:30 + { 8109, 32400 }, // UTC+09:00 + { 8119, 34200 }, // UTC+09:30 + { 8129, 36000 }, // UTC+10:00 + { 8139, 39600 }, // UTC+11:00 + { 8149, 43200 }, // UTC+12:00 + { 8159, 46800 }, // UTC+13:00 + { 8169, 50400 }, // UTC+14:00 + { 0, 0 } // Trailing zeroes +}; + +static const char windowsIdData[] = { +0x41, 0x66, 0x67, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x6c, 0x61, 0x73, 0x6b, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x6c, 0x65, 0x75, 0x74, 0x69, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x6c, 0x74, 0x61, 0x69, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x72, 0x61, 0x62, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x72, 0x61, 0x62, 0x69, 0x61, 0x6e, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x72, 0x61, 0x62, 0x69, 0x63, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, +0x6e, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x73, 0x74, +0x72, 0x61, 0x6b, 0x68, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, +0x69, 0x6d, 0x65, 0x0, 0x41, 0x55, 0x53, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x75, 0x73, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, +0x6c, 0x20, 0x57, 0x2e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, +0x55, 0x53, 0x20, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x7a, 0x65, 0x72, 0x62, 0x61, 0x69, 0x6a, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x41, 0x7a, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x42, 0x61, 0x68, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x42, 0x61, 0x6e, 0x67, 0x6c, 0x61, 0x64, 0x65, 0x73, +0x68, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x42, 0x65, 0x6c, 0x61, +0x72, 0x75, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x42, 0x6f, +0x75, 0x67, 0x61, 0x69, 0x6e, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, +0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x61, 0x70, 0x65, 0x20, 0x56, +0x65, 0x72, 0x64, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, +0x61, 0x75, 0x63, 0x61, 0x73, 0x75, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, +0x65, 0x0, 0x43, 0x65, 0x6e, 0x2e, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x42, 0x72, 0x61, 0x7a, +0x69, 0x6c, 0x69, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x45, 0x75, 0x72, +0x6f, 0x70, 0x65, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x28, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x29, +0x0, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x43, 0x68, 0x69, 0x6e, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x43, 0x68, 0x61, 0x74, 0x68, 0x61, 0x6d, 0x20, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x43, 0x75, 0x62, 0x61, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x44, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6e, 0x65, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, 0x2e, 0x20, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, +0x2e, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, 0x2e, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, 0x2e, 0x20, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x28, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, +0x29, 0x0, 0x45, 0x67, 0x79, 0x70, 0x74, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, +0x65, 0x0, 0x45, 0x6b, 0x61, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x62, 0x75, 0x72, 0x67, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x46, 0x69, 0x6a, 0x69, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x46, 0x4c, 0x45, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x47, 0x65, 0x6f, 0x72, 0x67, 0x69, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x47, 0x4d, 0x54, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, +0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x6c, 0x61, 0x6e, 0x64, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x77, 0x69, 0x63, 0x68, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x47, 0x54, 0x42, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x48, 0x61, 0x69, 0x74, 0x69, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x48, 0x61, 0x77, 0x61, 0x69, 0x69, 0x61, +0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x49, 0x6e, 0x64, 0x69, +0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x49, 0x72, 0x61, 0x6e, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x49, 0x73, 0x72, 0x61, 0x65, +0x6c, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4a, 0x6f, 0x72, 0x64, +0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4b, 0x61, 0x6c, +0x69, 0x6e, 0x69, 0x6e, 0x67, 0x72, 0x61, 0x64, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x4b, 0x6f, 0x72, 0x65, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x4c, 0x69, 0x62, 0x79, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x4c, 0x69, 0x6e, 0x65, 0x20, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4c, 0x6f, 0x72, 0x64, 0x20, 0x48, 0x6f, 0x77, 0x65, 0x20, +0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x61, 0x67, 0x61, 0x64, 0x61, +0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x61, 0x67, 0x61, +0x6c, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x4d, 0x61, 0x72, 0x71, 0x75, 0x65, 0x73, 0x61, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x61, 0x75, 0x72, 0x69, 0x74, 0x69, 0x75, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x20, 0x45, 0x61, 0x73, 0x74, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x6f, 0x6e, 0x74, 0x65, +0x76, 0x69, 0x64, 0x65, 0x6f, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x4d, 0x6f, 0x72, 0x6f, 0x63, 0x63, 0x6f, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, +0x65, 0x0, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x20, 0x28, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x29, 0x0, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, +0x69, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4d, 0x79, 0x61, +0x6e, 0x6d, 0x61, 0x72, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, +0x2e, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x61, 0x6d, 0x69, 0x62, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x65, 0x70, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x65, 0x77, 0x20, 0x5a, 0x65, 0x61, 0x6c, 0x61, +0x6e, 0x64, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x65, 0x77, +0x66, 0x6f, 0x75, 0x6e, 0x64, 0x6c, 0x61, 0x6e, 0x64, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, +0x69, 0x6d, 0x65, 0x0, 0x4e, 0x6f, 0x72, 0x66, 0x6f, 0x6c, 0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x45, 0x61, 0x73, +0x74, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4e, 0x6f, 0x72, 0x74, +0x68, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x20, 0x4b, 0x6f, 0x72, 0x65, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, +0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x4f, 0x6d, 0x73, 0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x53, 0x41, 0x20, 0x53, 0x74, 0x61, +0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x28, 0x4d, 0x65, 0x78, 0x69, +0x63, 0x6f, 0x29, 0x0, 0x50, 0x61, 0x6b, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, +0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x50, 0x61, 0x72, 0x61, 0x67, 0x75, 0x61, 0x79, 0x20, 0x53, 0x74, 0x61, 0x6e, +0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x52, 0x6f, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x52, 0x75, 0x73, 0x73, 0x69, 0x61, 0x20, 0x54, +0x69, 0x6d, 0x65, 0x20, 0x5a, 0x6f, 0x6e, 0x65, 0x20, 0x33, 0x0, 0x52, 0x75, 0x73, 0x73, 0x69, 0x61, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x20, 0x5a, 0x6f, 0x6e, 0x65, 0x20, 0x31, 0x30, 0x0, 0x52, 0x75, 0x73, 0x73, 0x69, 0x61, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x20, 0x5a, 0x6f, 0x6e, 0x65, 0x20, 0x31, 0x31, 0x0, 0x52, 0x75, 0x73, 0x73, 0x69, 0x61, 0x6e, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x41, 0x20, 0x45, 0x61, 0x73, 0x74, +0x65, 0x72, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x41, +0x20, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x53, 0x41, 0x20, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x61, 0x69, 0x6e, 0x74, 0x20, 0x50, 0x69, 0x65, 0x72, 0x72, 0x65, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x61, 0x6b, 0x68, 0x61, +0x6c, 0x69, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x61, +0x6d, 0x6f, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x61, +0x6f, 0x20, 0x54, 0x6f, 0x6d, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x53, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x76, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x53, 0x45, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, +0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x20, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x53, 0x72, 0x69, 0x20, +0x4c, 0x61, 0x6e, 0x6b, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x53, 0x75, 0x64, 0x61, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x53, 0x79, 0x72, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, +0x54, 0x61, 0x69, 0x70, 0x65, 0x69, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, +0x0, 0x54, 0x61, 0x73, 0x6d, 0x61, 0x6e, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, +0x69, 0x6d, 0x65, 0x0, 0x54, 0x6f, 0x63, 0x61, 0x6e, 0x74, 0x69, 0x6e, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x6f, 0x6d, 0x73, 0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x6f, 0x6e, 0x67, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x62, 0x61, 0x69, 0x6b, 0x61, 0x6c, 0x20, +0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x75, 0x72, 0x6b, 0x65, 0x79, +0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54, 0x75, 0x72, 0x6b, 0x73, +0x20, 0x41, 0x6e, 0x64, 0x20, 0x43, 0x61, 0x69, 0x63, 0x6f, 0x73, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x55, 0x6c, 0x61, 0x61, 0x6e, 0x62, 0x61, 0x61, 0x74, 0x61, 0x72, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x55, 0x53, 0x20, 0x45, 0x61, 0x73, 0x74, 0x65, +0x72, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x55, 0x53, 0x20, +0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x31, 0x31, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x39, 0x0, 0x55, 0x54, 0x43, +0x2d, 0x30, 0x38, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x32, 0x0, 0x55, 0x54, 0x43, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x31, +0x32, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x31, 0x33, 0x0, 0x56, 0x65, 0x6e, 0x65, 0x7a, 0x75, 0x65, 0x6c, 0x61, 0x20, 0x53, +0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x56, 0x6c, 0x61, 0x64, 0x69, 0x76, 0x6f, +0x73, 0x74, 0x6f, 0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, +0x2e, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, +0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, 0x2e, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x41, 0x66, 0x72, +0x69, 0x63, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, 0x2e, +0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, +0x65, 0x0, 0x57, 0x2e, 0x20, 0x4d, 0x6f, 0x6e, 0x67, 0x6f, 0x6c, 0x69, 0x61, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, +0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, 0x65, 0x73, 0x74, 0x20, 0x41, 0x73, 0x69, 0x61, 0x20, 0x53, 0x74, +0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, 0x65, 0x73, 0x74, 0x20, 0x42, 0x61, 0x6e, +0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x57, 0x65, 0x73, 0x74, +0x20, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x54, 0x69, +0x6d, 0x65, 0x0, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, +0x54, 0x69, 0x6d, 0x65, 0x0 +}; + +static const char ianaIdData[] = { +0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x6f, 0x76, 0x64, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x54, 0x61, +0x72, 0x61, 0x77, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x2d, 0x61, 0x75, +0x2d, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x68, 0x61, 0x6e, 0x67, 0x68, 0x61, +0x69, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x42, 0x61, 0x72, 0x74, 0x68, 0x65, 0x6c, +0x65, 0x6d, 0x79, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x68, 0x6b, 0x65, 0x6b, 0x0, 0x41, 0x6e, 0x74, +0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x79, 0x6f, 0x77, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, +0x63, 0x2f, 0x43, 0x68, 0x61, 0x74, 0x68, 0x61, 0x6d, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, +0x6e, 0x74, 0x73, 0x65, 0x72, 0x72, 0x61, 0x74, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x42, +0x72, 0x69, 0x73, 0x62, 0x61, 0x6e, 0x65, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4c, 0x69, +0x6e, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x34, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x41, 0x73, 0x68, 0x67, 0x61, 0x62, 0x61, 0x74, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x4e, 0x6f, 0x72, 0x6f, 0x6e, 0x68, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x6f, 0x73, 0x74, +0x61, 0x5f, 0x52, 0x69, 0x63, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x73, 0x6d, 0x65, 0x72, 0x61, +0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x73, 0x65, 0x79, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x4a, 0x61, 0x6b, 0x61, 0x72, 0x74, 0x61, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x6f, 0x6e, 0x74, +0x69, 0x61, 0x6e, 0x61, 0x6b, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x6f, 0x74, +0x68, 0x65, 0x72, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x72, 0x6e, 0x61, 0x75, 0x6c, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x65, 0x73, 0x0, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x69, 0x63, 0x61, 0x0, 0x50, 0x61, 0x63, +0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x4d, 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, 0x0, 0x41, 0x75, +0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x79, 0x64, 0x6e, 0x65, 0x79, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, +0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4d, 0x65, 0x6c, 0x62, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x45, 0x6c, 0x5f, 0x41, 0x61, 0x69, 0x75, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, +0x61, 0x6f, 0x5f, 0x50, 0x61, 0x75, 0x6c, 0x6f, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x6a, +0x75, 0x72, 0x6f, 0x20, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4b, 0x77, 0x61, 0x6a, 0x61, 0x6c, 0x65, 0x69, +0x6e, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, 0x6e, 0x64, 0x65, 0x72, 0x62, 0x75, 0x72, 0x79, 0x0, +0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x70, 0x75, 0x74, 0x6f, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x6f, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x45, 0x69, 0x72, 0x75, 0x6e, 0x65, 0x70, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x50, 0x61, 0x72, +0x69, 0x73, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x64, 0x6d, 0x6f, 0x6e, 0x74, 0x6f, 0x6e, 0x20, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x42, 0x61, +0x79, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x75, 0x76, 0x69, 0x6b, 0x20, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x59, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0x0, 0x41, 0x73, 0x69, +0x61, 0x2f, 0x52, 0x61, 0x6e, 0x67, 0x6f, 0x6f, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, +0x69, 0x61, 0x62, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x70, 0x6f, 0x5f, 0x47, +0x72, 0x61, 0x6e, 0x64, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, 0x61, 0x64, 0x75, 0x7a, 0x0, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x48, 0x65, 0x6c, 0x73, 0x69, 0x6e, 0x6b, 0x69, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, +0x74, 0x69, 0x63, 0x2f, 0x53, 0x74, 0x61, 0x6e, 0x6c, 0x65, 0x79, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, +0x4e, 0x6f, 0x72, 0x66, 0x6f, 0x6c, 0x6b, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x30, 0x0, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x5a, 0x61, 0x67, 0x72, 0x65, 0x62, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, +0x74, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x6f, 0x66, 0x69, 0x61, 0x0, +0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x75, 0x73, 0x63, 0x61, 0x74, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, +0x61, 0x6c, 0x64, 0x69, 0x76, 0x65, 0x73, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x64, 0x72, 0x69, +0x64, 0x20, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x65, 0x75, 0x74, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x43, 0x6f, 0x6e, 0x61, 0x6b, 0x72, 0x79, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, 0x6e, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x69, 0x6d, 0x66, 0x65, 0x72, 0x6f, 0x70, 0x6f, 0x6c, 0x0, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x62, 0x61, 0x62, 0x61, 0x6e, 0x65, 0x0, 0x41, 0x72, 0x63, 0x74, 0x69, 0x63, 0x2f, +0x4c, 0x6f, 0x6e, 0x67, 0x79, 0x65, 0x61, 0x72, 0x62, 0x79, 0x65, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, +0x67, 0x68, 0x64, 0x61, 0x64, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x69, 0x64, 0x77, 0x61, 0x79, +0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x75, 0x77, 0x61, 0x69, 0x74, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x46, 0x72, 0x65, 0x65, 0x74, 0x6f, 0x77, 0x6e, 0x0, 0x43, 0x53, 0x54, 0x36, 0x43, 0x44, 0x54, 0x0, 0x50, 0x61, 0x63, +0x69, 0x66, 0x69, 0x63, 0x2f, 0x52, 0x61, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x67, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x42, 0x72, 0x61, 0x7a, 0x7a, 0x61, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x50, 0x75, 0x6e, 0x74, 0x61, 0x5f, 0x41, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, +0x4d, 0x54, 0x2b, 0x31, 0x31, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x56, 0x61, 0x6e, 0x63, 0x6f, 0x75, +0x76, 0x65, 0x72, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x77, 0x73, 0x6f, 0x6e, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x57, 0x68, 0x69, 0x74, 0x65, 0x68, 0x6f, 0x72, 0x73, 0x65, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x59, 0x65, 0x72, 0x65, 0x76, 0x61, 0x6e, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, +0x6e, 0x61, 0x63, 0x6f, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x73, 0x61, 0x6b, 0x61, 0x0, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x65, 0x76, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x55, 0x7a, +0x68, 0x67, 0x6f, 0x72, 0x6f, 0x64, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x5a, 0x61, 0x70, 0x6f, 0x72, 0x6f, +0x7a, 0x68, 0x79, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x73, 0x75, 0x6e, 0x63, 0x69, 0x6f, +0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6d, 0x6d, 0x61, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x41, 0x72, 0x61, 0x67, 0x75, 0x61, 0x69, 0x6e, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x65, 0x68, 0x72, +0x61, 0x6e, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x76, 0x69, 0x73, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x50, 0x72, 0x61, 0x67, 0x75, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x53, 0x70, 0x61, 0x69, 0x6e, 0x0, 0x41, 0x66, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x47, 0x61, 0x62, 0x6f, 0x72, 0x6f, 0x6e, 0x65, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, +0x35, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, 0x79, 0x61, 0x71, 0x75, 0x69, 0x6c, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x74, 0x68, 0x65, 0x6e, 0x73, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, +0x2f, 0x41, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x6e, 0x61, 0x72, 0x69, 0x76, 0x6f, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, +0x63, 0x2f, 0x4a, 0x6f, 0x68, 0x6e, 0x73, 0x74, 0x6f, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, +0x61, 0x6d, 0x61, 0x69, 0x63, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x43, 0x6f, 0x6c, 0x6f, 0x6d, 0x62, 0x6f, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x6c, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, +0x2f, 0x41, 0x6c, 0x6d, 0x61, 0x74, 0x79, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x51, 0x6f, 0x73, 0x74, 0x61, 0x6e, 0x61, +0x79, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x33, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x43, 0x6f, 0x72, 0x61, 0x6c, 0x5f, 0x48, 0x61, 0x72, 0x62, 0x6f, 0x75, 0x72, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, +0x6e, 0x2f, 0x43, 0x68, 0x61, 0x67, 0x6f, 0x73, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x48, +0x6f, 0x62, 0x61, 0x72, 0x74, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x72, +0x69, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x75, 0x65, 0x72, 0x74, 0x6f, 0x5f, 0x52, 0x69, +0x63, 0x6f, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x4b, 0x69, 0x74, 0x74, 0x73, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x5a, 0x75, 0x72, 0x69, 0x63, 0x68, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, 0x6f, 0x6c, 0x69, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x65, 0x6e, 0x67, 0x6f, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x56, 0x65, 0x76, 0x61, 0x79, +0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x6e, 0x61, 0x70, 0x65, 0x20, 0x50, 0x61, 0x63, 0x69, +0x66, 0x69, 0x63, 0x2f, 0x4b, 0x6f, 0x73, 0x72, 0x61, 0x65, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, +0x61, 0x6d, 0x62, 0x69, 0x65, 0x72, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x61, 0x6d, 0x65, 0x79, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x65, 0x72, 0x6d, 0x6f, 0x73, 0x69, 0x6c, 0x6c, 0x6f, 0x0, +0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x67, 0x61, 0x64, 0x69, 0x73, 0x68, 0x75, 0x0, 0x45, 0x74, 0x63, +0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x37, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x42, 0x65, 0x72, 0x6d, +0x75, 0x64, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x72, 0x61, 0x72, 0x65, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x61, 0x75, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6c, 0x61, +0x62, 0x6f, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x75, 0x72, 0x69, 0x74, 0x69, 0x75, 0x73, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54, 0x61, 0x6c, 0x6c, 0x69, 0x6e, 0x6e, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x44, 0x61, 0x6b, 0x61, 0x72, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x72, 0x61, 0x6c, 0x20, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x41, 0x71, 0x74, 0x61, 0x75, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x71, 0x74, 0x6f, 0x62, 0x65, +0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x74, 0x79, 0x72, 0x61, 0x75, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x51, 0x79, +0x7a, 0x79, 0x6c, 0x6f, 0x72, 0x64, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x72, 0x75, 0x6d, 0x71, 0x69, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x52, 0x6f, 0x6d, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x49, +0x73, 0x6c, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x4d, 0x61, 0x6e, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, +0x72, 0x5f, 0x65, 0x73, 0x5f, 0x53, 0x61, 0x6c, 0x61, 0x61, 0x6d, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x54, 0x68, 0x75, 0x6c, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x72, +0x6b, 0x73, 0x68, 0x61, 0x76, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x67, 0x69, 0x6e, +0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x77, 0x69, 0x66, 0x74, 0x5f, 0x43, 0x75, 0x72, 0x72, +0x65, 0x6e, 0x74, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, +0x73, 0x75, 0x6e, 0x64, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x73, 0x61, 0x62, 0x6c, 0x61, 0x6e, +0x63, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x61, 0x6e, 0x64, 0x5f, 0x54, 0x75, 0x72, +0x6b, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x6e, 0x6a, 0x75, 0x6c, 0x0, 0x41, 0x66, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x4e, 0x64, 0x6a, 0x61, 0x6d, 0x65, 0x6e, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x65, 0x72, +0x75, 0x73, 0x61, 0x6c, 0x65, 0x6d, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, +0x6c, 0x6d, 0x65, 0x72, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x45, 0x75, 0x63, 0x6c, 0x61, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x75, 0x61, 0x0, 0x41, 0x73, 0x69, +0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x69, 0x6c, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x53, 0x61, 0x69, +0x70, 0x61, 0x6e, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x69, 0x72, 0x6f, 0x0, 0x45, 0x75, 0x72, +0x6f, 0x70, 0x65, 0x2f, 0x42, 0x72, 0x75, 0x73, 0x73, 0x65, 0x6c, 0x73, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, +0x69, 0x63, 0x61, 0x2f, 0x4d, 0x63, 0x4d, 0x75, 0x72, 0x64, 0x6f, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, +0x75, 0x63, 0x68, 0x61, 0x72, 0x65, 0x73, 0x74, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x32, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, 0x59, 0x6f, 0x72, 0x6b, 0x20, 0x41, 0x6d, 0x65, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x44, 0x65, 0x74, 0x72, 0x6f, 0x69, 0x74, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x50, 0x65, 0x74, 0x65, 0x72, 0x73, 0x62, 0x75, 0x72, 0x67, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x56, 0x69, 0x6e, 0x63, 0x65, +0x6e, 0x6e, 0x65, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, +0x2f, 0x57, 0x69, 0x6e, 0x61, 0x6d, 0x61, 0x63, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x65, 0x6e, +0x74, 0x75, 0x63, 0x6b, 0x79, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x69, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x75, 0x69, 0x73, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0, 0x41, 0x66, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x6f, 0x2d, 0x4e, 0x6f, 0x76, 0x6f, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x49, +0x72, 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x61, 0x6e, 0x62, 0x61, 0x61, +0x74, 0x61, 0x72, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x43, 0x68, 0x6f, 0x69, 0x62, 0x61, 0x6c, 0x73, 0x61, 0x6e, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, 0x69, 0x65, 0x6e, 0x6e, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x54, 0x6f, 0x72, 0x6f, 0x6e, 0x74, 0x6f, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x71, +0x61, 0x6c, 0x75, 0x69, 0x74, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x72, 0x65, +0x61, 0x6c, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x70, 0x69, 0x67, 0x6f, 0x6e, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e, 0x67, 0x6e, 0x69, 0x72, 0x74, 0x75, 0x6e, 0x67, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x42, 0x61, 0x79, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x72, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6a, 0x6b, 0x0, 0x50, 0x61, +0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x61, 0x75, 0x72, 0x75, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, +0x63, 0x61, 0x2f, 0x44, 0x75, 0x6d, 0x6f, 0x6e, 0x74, 0x44, 0x55, 0x72, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x4d, 0x61, 0x67, 0x61, 0x64, 0x61, 0x6e, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4f, 0x73, +0x6c, 0x6f, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x39, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, +0x2f, 0x47, 0x61, 0x6c, 0x61, 0x70, 0x61, 0x67, 0x6f, 0x73, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x75, +0x6a, 0x75, 0x6d, 0x62, 0x75, 0x72, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x6c, 0x69, +0x66, 0x61, 0x78, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x42, 0x61, +0x79, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x6f, 0x6f, 0x73, 0x65, 0x5f, 0x42, 0x61, 0x79, 0x20, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x63, 0x74, 0x6f, 0x6e, 0x0, 0x45, 0x74, 0x63, 0x2f, +0x47, 0x4d, 0x54, 0x2d, 0x31, 0x31, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x65, 0x6e, 0x61, +0x64, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x6f, 0x75, 0x6d, 0x65, 0x61, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6e, 0x63, 0x75, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, +0x75, 0x6e, 0x65, 0x61, 0x75, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x6d, 0x65, 0x20, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x69, 0x74, 0x6b, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x61, 0x74, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x6f, 0x64, +0x74, 0x68, 0x61, 0x62, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x73, 0x61, 0x75, 0x0, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4d, 0x61, 0x72, 0x69, 0x6e, 0x6f, 0x0, 0x45, 0x75, 0x72, +0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x64, 0x61, 0x70, 0x65, 0x73, 0x74, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x54, 0x65, 0x67, 0x75, 0x63, 0x69, 0x67, 0x61, 0x6c, 0x70, 0x61, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, +0x69, 0x61, 0x2f, 0x4c, 0x6f, 0x72, 0x64, 0x5f, 0x48, 0x6f, 0x77, 0x65, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, +0x2d, 0x33, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x62, 0x72, 0x65, 0x76, 0x69, 0x6c, 0x6c, 0x65, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x56, 0x69, 0x6e, 0x63, 0x65, 0x6e, 0x74, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, +0x2d, 0x31, 0x32, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x0, 0x41, +0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x52, 0x65, 0x79, 0x6b, 0x6a, 0x61, 0x76, 0x69, 0x6b, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x76, 0x61, 0x6e, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x4e, 0x61, 0x73, 0x73, 0x61, 0x75, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x53, 0x6f, 0x75, +0x74, 0x68, 0x5f, 0x47, 0x65, 0x6f, 0x72, 0x67, 0x69, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, +0x6c, 0x5f, 0x53, 0x61, 0x6c, 0x76, 0x61, 0x64, 0x6f, 0x72, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, +0x68, 0x69, 0x63, 0x61, 0x67, 0x6f, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, +0x6e, 0x61, 0x2f, 0x4b, 0x6e, 0x6f, 0x78, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, +0x61, 0x6e, 0x61, 0x2f, 0x54, 0x65, 0x6c, 0x6c, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x4d, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x65, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, 0x2f, 0x42, 0x65, 0x75, 0x6c, 0x61, 0x68, 0x20, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, +0x2f, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, +0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, 0x53, 0x61, 0x6c, 0x65, 0x6d, 0x0, 0x45, +0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x34, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e, +0x61, 0x6d, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, 0x67, 0x6f, 0x74, 0x61, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x68, 0x75, 0x61, 0x68, 0x75, 0x61, 0x20, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x7a, 0x61, 0x74, 0x6c, 0x61, 0x6e, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, +0x63, 0x2f, 0x46, 0x75, 0x6e, 0x61, 0x66, 0x75, 0x74, 0x69, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x47, 0x69, +0x62, 0x72, 0x61, 0x6c, 0x74, 0x61, 0x72, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x6d, 0x73, 0x6b, 0x0, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x72, 0x69, 0x70, 0x6f, 0x6c, 0x69, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, +0x69, 0x63, 0x61, 0x2f, 0x56, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, +0x72, 0x75, 0x62, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x65, 0x69, 0x72, 0x75, 0x74, 0x0, 0x45, 0x74, 0x63, +0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x38, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x64, 0x68, 0x6f, +0x65, 0x6b, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x72, 0x61, 0x63, 0x61, 0x73, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x54, 0x68, 0x6f, 0x6d, 0x61, 0x73, 0x0, 0x45, 0x74, 0x63, +0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x74, 0x69, 0x67, +0x75, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x6d, 0x61, 0x0, 0x45, 0x75, 0x72, 0x6f, +0x70, 0x65, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x65, 0x68, 0x61, 0x6d, 0x6e, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, +0x63, 0x2f, 0x53, 0x74, 0x5f, 0x48, 0x65, 0x6c, 0x65, 0x6e, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, +0x54, 0x72, 0x75, 0x6b, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x0, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x6f, 0x5f, 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x67, +0x6f, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x74, 0x79, 0x72, 0x65, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x20, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x69, 0x61, 0x5f, 0x42, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x61, 0x73, +0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x72, 0x69, 0x64, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x65, 0x79, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, +0x75, 0x61, 0x6c, 0x61, 0x5f, 0x4c, 0x75, 0x6d, 0x70, 0x75, 0x72, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x75, 0x63, +0x68, 0x69, 0x6e, 0x67, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x46, 0x69, 0x6a, 0x69, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x68, 0x6f, 0x65, 0x6e, 0x69, 0x78, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, +0x68, 0x69, 0x6d, 0x70, 0x68, 0x75, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x6c, 0x74, 0x61, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x73, 0x63, 0x6f, 0x77, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, +0x2f, 0x4b, 0x69, 0x72, 0x6f, 0x76, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, 0x6f, 0x6c, 0x67, 0x6f, 0x67, +0x72, 0x61, 0x64, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x69, 0x71, +0x75, 0x65, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x32, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x59, +0x61, 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x68, 0x61, 0x6e, 0x64, 0x79, 0x67, 0x61, +0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x62, 0x75, 0x6c, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x20, +0x45, 0x74, 0x63, 0x2f, 0x55, 0x54, 0x43, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x74, 0x61, +0x6d, 0x6f, 0x72, 0x6f, 0x73, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x34, 0x0, 0x49, 0x6e, 0x64, 0x69, +0x61, 0x6e, 0x2f, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, +0x63, 0x2f, 0x41, 0x7a, 0x6f, 0x72, 0x65, 0x73, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x0, 0x41, +0x73, 0x69, 0x61, 0x2f, 0x44, 0x68, 0x61, 0x6b, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x57, 0x69, +0x6e, 0x6e, 0x69, 0x70, 0x65, 0x67, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x61, 0x69, 0x6e, 0x79, +0x5f, 0x52, 0x69, 0x76, 0x65, 0x72, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x61, 0x6e, 0x6b, 0x69, +0x6e, 0x5f, 0x49, 0x6e, 0x6c, 0x65, 0x74, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x73, 0x6f, +0x6c, 0x75, 0x74, 0x65, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x35, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, +0x69, 0x63, 0x2f, 0x46, 0x61, 0x6b, 0x61, 0x6f, 0x66, 0x6f, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f, +0x73, 0x69, 0x62, 0x69, 0x72, 0x73, 0x6b, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x65, 0x62, 0x72, 0x6f, 0x6e, 0x20, +0x41, 0x73, 0x69, 0x61, 0x2f, 0x47, 0x61, 0x7a, 0x61, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x49, 0x73, 0x74, +0x61, 0x6e, 0x62, 0x75, 0x6c, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x61, 0x6b, 0x0, 0x45, +0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x37, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x65, 0x6e, +0x76, 0x65, 0x72, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, 0x69, 0x73, 0x65, 0x0, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x64, 0x69, 0x73, 0x5f, 0x41, 0x62, 0x61, 0x62, 0x61, 0x0, 0x45, 0x75, 0x72, +0x6f, 0x70, 0x65, 0x2f, 0x41, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x64, 0x61, 0x6d, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x67, 0x6f, 0x74, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x69, 0x73, +0x62, 0x6f, 0x6e, 0x20, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x64, 0x65, 0x69, 0x72, 0x61, +0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, 0x69, 0x6c, 0x6e, 0x69, 0x75, 0x73, 0x0, 0x45, 0x75, 0x72, 0x6f, +0x70, 0x65, 0x2f, 0x42, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x20, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x73, +0x69, 0x6e, 0x67, 0x65, 0x6e, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x72, 0x71, 0x75, 0x65, +0x73, 0x61, 0x73, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x72, 0x61, 0x73, 0x6e, 0x6f, 0x79, 0x61, 0x72, 0x73, 0x6b, +0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f, 0x6b, 0x75, 0x7a, 0x6e, 0x65, 0x74, 0x73, 0x6b, 0x0, 0x50, +0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4b, 0x69, 0x72, 0x69, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x69, 0x71, 0x75, 0x65, 0x6c, 0x6f, 0x6e, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, +0x65, 0x2f, 0x44, 0x75, 0x62, 0x6c, 0x69, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x67, 0x6f, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x63, 0x68, 0x61, 0x74, 0x6b, 0x61, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6e, +0x61, 0x64, 0x79, 0x72, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x72, 0x6f, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x61, 0x63, 0x61, 0x6f, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, +0x65, 0x2f, 0x43, 0x68, 0x69, 0x73, 0x69, 0x6e, 0x61, 0x75, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, +0x65, 0x6c, 0x69, 0x7a, 0x65, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x6f, 0x68, 0x61, 0x6e, 0x6e, 0x65, +0x73, 0x62, 0x75, 0x72, 0x67, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x56, 0x6c, 0x61, 0x64, 0x69, 0x76, 0x6f, 0x73, 0x74, +0x6f, 0x6b, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x73, 0x74, 0x2d, 0x4e, 0x65, 0x72, 0x61, 0x0, 0x45, 0x74, 0x63, +0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x32, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x61, 0x6d, 0x61, 0x73, 0x63, 0x75, 0x73, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x79, 0x65, 0x6e, 0x6e, 0x65, 0x0, 0x41, 0x66, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x75, 0x61, 0x6b, 0x63, 0x68, 0x6f, 0x74, 0x74, 0x0, 0x45, 0x53, 0x54, 0x35, 0x45, +0x44, 0x54, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x69, 0x61, 0x0, 0x50, 0x61, 0x63, +0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, +0x33, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x73, 0x74, 0x72, 0x61, 0x6b, 0x68, 0x61, 0x6e, 0x20, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x55, 0x6c, 0x79, 0x61, 0x6e, 0x6f, 0x76, 0x73, 0x6b, 0x0, 0x41, 0x6d, 0x65, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x75, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, +0x6f, 0x61, 0x5f, 0x56, 0x69, 0x73, 0x74, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, +0x74, 0x6f, 0x5f, 0x56, 0x65, 0x6c, 0x68, 0x6f, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x73, +0x6b, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x4c, 0x75, 0x63, 0x69, 0x61, 0x0, 0x41, +0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x61, 0x6e, 0x64, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x4c, 0x61, 0x67, 0x6f, 0x73, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x6e, 0x64, 0x6f, 0x72, 0x72, 0x61, +0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x61, 0x6c, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x72, 0x61, 0x64, 0x0, +0x4d, 0x53, 0x54, 0x37, 0x4d, 0x44, 0x54, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x67, 0x75, +0x69, 0x6c, 0x6c, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x57, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x0, +0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x75, 0x62, 0x61, 0x69, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, +0x61, 0x79, 0x6d, 0x61, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x6e, 0x67, 0x6b, 0x6f, 0x6b, 0x0, 0x50, +0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x61, 0x6c, 0x63, 0x61, 0x6e, 0x61, 0x6c, 0x0, 0x41, +0x73, 0x69, 0x61, 0x2f, 0x54, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x69, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x68, 0x6e, +0x6f, 0x6d, 0x5f, 0x50, 0x65, 0x6e, 0x68, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x68, 0x6b, 0x65, 0x6e, +0x74, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x6b, 0x61, 0x6e, 0x64, 0x0, 0x41, 0x73, 0x69, +0x61, 0x2f, 0x46, 0x61, 0x6d, 0x61, 0x67, 0x75, 0x73, 0x74, 0x61, 0x20, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x69, 0x63, +0x6f, 0x73, 0x69, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x65, 0x6c, 0x6f, +0x75, 0x70, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x69, 0x67, 0x6f, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, +0x2f, 0x43, 0x68, 0x69, 0x74, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x72, 0x6f, 0x76, +0x69, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x6f, 0x72, 0x74, 0x6f, 0x6c, 0x61, 0x0, 0x41, +0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x6e, 0x73, 0x68, 0x61, 0x73, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, +0x53, 0x61, 0x6b, 0x68, 0x61, 0x6c, 0x69, 0x6e, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x57, 0x61, 0x6b, +0x65, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x63, 0x63, 0x72, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, +0x69, 0x63, 0x2f, 0x41, 0x75, 0x63, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, +0x61, 0x2f, 0x41, 0x64, 0x65, 0x6c, 0x61, 0x69, 0x64, 0x65, 0x20, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, +0x2f, 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x48, 0x69, 0x6c, 0x6c, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, +0x2f, 0x54, 0x61, 0x68, 0x69, 0x74, 0x69, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x75, 0x61, 0x67, 0x61, +0x64, 0x6f, 0x75, 0x67, 0x6f, 0x75, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4b, 0x65, 0x72, 0x67, 0x75, 0x65, +0x6c, 0x65, 0x6e, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x38, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, +0x2f, 0x54, 0x69, 0x72, 0x61, 0x6e, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x69, 0x70, 0x65, 0x69, 0x0, +0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x43, 0x6f, 0x70, 0x65, 0x6e, 0x68, 0x61, 0x67, 0x65, 0x6e, 0x0, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x70, 0x61, 0x6c, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x42, 0x61, 0x72, 0x62, 0x61, 0x64, 0x6f, 0x73, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x6b, 0x75, 0x0, +0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x6f, 0x6e, 0x67, 0x5f, 0x4b, 0x6f, 0x6e, 0x67, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, +0x4a, 0x61, 0x79, 0x61, 0x70, 0x75, 0x72, 0x61, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x48, 0x6f, 0x6e, +0x6f, 0x6c, 0x75, 0x6c, 0x75, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x61, 0x69, 0x72, 0x6f, 0x62, 0x69, +0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x6d, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x4a, 0x75, 0x62, 0x61, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x46, 0x61, 0x65, 0x72, 0x6f, +0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x72, 0x65, 0x64, 0x6e, 0x65, 0x6b, 0x6f, 0x6c, 0x79, 0x6d, 0x73, 0x6b, +0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x69, 0x75, 0x65, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x41, 0x6c, 0x67, 0x69, 0x65, 0x72, 0x73, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x69, 0x6c, 0x69, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x65, 0x7a, 0x61, 0x20, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x65, 0x6c, 0x65, 0x6d, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, +0x61, 0x63, 0x65, 0x69, 0x6f, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x63, 0x69, 0x66, 0x65, +0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x61, 0x72, 0x65, 0x6d, 0x0, 0x45, 0x75, +0x72, 0x6f, 0x70, 0x65, 0x2f, 0x50, 0x6f, 0x64, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x44, 0x6f, 0x75, 0x61, 0x6c, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x56, 0x69, 0x65, 0x6e, 0x74, 0x69, +0x61, 0x6e, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6a, 0x75, 0x61, 0x6e, 0x61, 0x20, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x61, 0x5f, 0x49, 0x73, 0x61, 0x62, 0x65, 0x6c, +0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x6d, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, +0x52, 0x69, 0x67, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x63, 0x2d, 0x53, +0x61, 0x62, 0x6c, 0x6f, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x75, 0x73, 0x68, 0x61, 0x6e, 0x62, 0x65, 0x0, +0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x4a, 0x6f, 0x68, 0x6e, 0x73, 0x0, 0x41, 0x73, 0x69, +0x61, 0x2f, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x36, 0x0, 0x45, 0x75, +0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72, 0x61, 0x6a, 0x65, 0x76, 0x6f, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, +0x2f, 0x42, 0x72, 0x61, 0x74, 0x69, 0x73, 0x6c, 0x61, 0x76, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x47, 0x75, 0x79, 0x61, 0x6e, 0x61, 0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x6e, 0x61, +0x72, 0x79, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x42, 0x6f, 0x75, 0x67, 0x61, 0x69, 0x6e, 0x76, 0x69, +0x6c, 0x6c, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x72, 0x61, 0x69, 0x6e, 0x0, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, 0x74, 0x65, 0x6d, 0x61, 0x6c, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x50, 0x61, 0x7a, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x79, 0x6f, 0x6e, 0x67, +0x79, 0x61, 0x6e, 0x67, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x67, 0x6f, 0x5f, 0x50, 0x61, +0x67, 0x6f, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x52, 0x65, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x0, 0x41, 0x66, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x73, 0x65, 0x72, 0x75, 0x0, 0x50, 0x53, 0x54, 0x38, 0x50, 0x44, 0x54, 0x0, +0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x39, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x75, 0x6e, +0x69, 0x73, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6f, 0x5f, 0x54, 0x6f, 0x6d, 0x65, 0x0, 0x45, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x76, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x4b, 0x68, 0x61, 0x72, 0x74, 0x6f, 0x75, 0x6d, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4a, 0x65, 0x72, +0x73, 0x65, 0x79, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x71, 0x75, +0x61, 0x72, 0x69, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x6a, 0x69, 0x6e, 0x61, 0x67, 0x61, +0x0, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x70, 0x65, 0x5f, 0x56, 0x65, 0x72, 0x64, 0x65, +0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x6b, 0x61, 0x73, 0x73, 0x61, 0x72, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, +0x65, 0x2f, 0x4c, 0x6a, 0x75, 0x62, 0x6c, 0x6a, 0x61, 0x6e, 0x61, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x47, +0x75, 0x65, 0x72, 0x6e, 0x73, 0x65, 0x79, 0x0, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, +0x61, 0x77, 0x73, 0x6f, 0x6e, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x75, 0x65, 0x6e, 0x6f, 0x73, +0x5f, 0x41, 0x69, 0x72, 0x65, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, +0x74, 0x69, 0x6e, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x52, 0x69, 0x6f, 0x6a, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, +0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x47, 0x61, 0x6c, 0x6c, +0x65, 0x67, 0x6f, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, +0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6c, 0x74, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, +0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4a, 0x75, 0x61, 0x6e, 0x20, 0x41, 0x6d, 0x65, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4c, 0x75, +0x69, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, +0x2f, 0x54, 0x75, 0x63, 0x75, 0x6d, 0x61, 0x6e, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, +0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x55, 0x73, 0x68, 0x75, 0x61, 0x69, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x43, 0x61, 0x74, 0x61, 0x6d, 0x61, 0x72, 0x63, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x43, 0x6f, 0x72, 0x64, 0x6f, 0x62, 0x61, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x75, 0x6a, +0x75, 0x79, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x6e, 0x64, 0x6f, 0x7a, 0x61, 0x0, 0x50, +0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x41, 0x70, 0x69, 0x61, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, +0x61, 0x6e, 0x67, 0x75, 0x69, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x6f, 0x6d, 0x73, 0x6b, 0x0, 0x50, 0x61, 0x63, +0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x6c, 0x61, 0x75, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, +0x2f, 0x44, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x69, 0x74, 0x63, +0x61, 0x69, 0x72, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x72, 0x75, 0x6e, 0x65, 0x69, 0x0, 0x50, 0x61, 0x63, +0x69, 0x66, 0x69, 0x63, 0x2f, 0x54, 0x6f, 0x6e, 0x67, 0x61, 0x74, 0x61, 0x70, 0x75, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, +0x65, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x61, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x50, +0x65, 0x72, 0x74, 0x68, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x57, 0x61, 0x72, 0x73, 0x61, 0x77, 0x0, 0x49, +0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x63, 0x6f, 0x73, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, +0x61, 0x68, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x73, 0x5f, 0x41, 0x6e, 0x67, 0x65, +0x6c, 0x65, 0x73, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x74, 0x6c, 0x61, 0x6b, 0x61, 0x74, +0x6c, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x74, 0x74, 0x61, 0x0, 0x41, 0x66, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x41, 0x62, 0x69, 0x64, 0x6a, 0x61, 0x6e, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, +0x36, 0x0, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, 0x66, 0x61, 0x74, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, +0x70, 0x65, 0x2f, 0x4c, 0x75, 0x78, 0x65, 0x6d, 0x62, 0x6f, 0x75, 0x72, 0x67, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x42, 0x61, 0x6d, 0x61, 0x6b, 0x6f, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x67, 0x61, 0x6c, +0x69, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x51, 0x61, 0x74, 0x61, 0x72, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, +0x72, 0x61, 0x63, 0x68, 0x69, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x30, 0x0, 0x41, 0x66, 0x72, +0x69, 0x63, 0x61, 0x2f, 0x44, 0x6a, 0x69, 0x62, 0x6f, 0x75, 0x74, 0x69, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x59, 0x65, +0x6b, 0x61, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x62, 0x75, 0x72, 0x67, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x44, 0x61, 0x77, 0x73, 0x6f, 0x6e, 0x5f, 0x43, 0x72, 0x65, 0x65, 0x6b, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x43, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, +0x74, 0x5f, 0x4e, 0x65, 0x6c, 0x73, 0x6f, 0x6e, 0x0, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x79, 0x6f, +0x74, 0x74, 0x65, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x6b, 0x6f, 0x70, 0x6a, 0x65, 0x0, 0x41, 0x73, +0x69, 0x61, 0x2f, 0x53, 0x65, 0x6f, 0x75, 0x6c, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x72, +0x61, 0x6d, 0x61, 0x72, 0x69, 0x62, 0x6f, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x74, 0x6f, 0x63, 0x6b, +0x68, 0x6f, 0x6c, 0x6d, 0x0, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x62, 0x75, 0x6d, 0x62, 0x61, 0x73, +0x68, 0x69, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x61, 0x67, 0x65, +0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x75, 0x65, 0x6e, 0x6f, 0x73, 0x5f, 0x41, 0x69, 0x72, 0x65, +0x73, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x73, 0x74, 0x72, 0x61, 0x6b, 0x68, 0x61, 0x6e, 0x0, 0x41, +0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x6c, 0x69, 0x66, 0x61, 0x78, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, +0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x79, 0x64, 0x6e, 0x65, 0x79, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, +0x52, 0x65, 0x67, 0x69, 0x6e, 0x61, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, +0x6c, 0x61, 0x69, 0x64, 0x65, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6c, 0x6d, 0x61, 0x74, 0x79, 0x0, 0x41, 0x6d, +0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x69, 0x61, 0x62, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x2f, 0x43, 0x68, 0x69, 0x63, 0x61, 0x67, 0x6f, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x42, +0x72, 0x69, 0x73, 0x62, 0x61, 0x6e, 0x65, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, +0x59, 0x6f, 0x72, 0x6b, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x65, 0x76, 0x0, 0x41, 0x6d, 0x65, +0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x68, 0x75, 0x61, 0x68, 0x75, 0x61, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, +0x63, 0x61, 0x2f, 0x44, 0x65, 0x6e, 0x76, 0x65, 0x72, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x72, 0x61, 0x73, 0x6e, +0x6f, 0x79, 0x61, 0x72, 0x73, 0x6b, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x73, 0x5f, 0x41, +0x6e, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6a, 0x75, 0x61, +0x6e, 0x61, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x63, 0x68, 0x61, 0x74, 0x6b, 0x61, 0x0, 0x45, 0x75, +0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x73, 0x63, 0x6f, 0x77, 0x0, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, +0x61, 0x2f, 0x48, 0x6f, 0x62, 0x61, 0x72, 0x74, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x61, 0x6e, 0x62, +0x61, 0x61, 0x74, 0x61, 0x72, 0x0, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, +0x61, 0x70, 0x6f, 0x6c, 0x69, 0x73, 0x0, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, +0x56, 0x6c, 0x61, 0x64, 0x69, 0x76, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, +0x65, 0x72, 0x6c, 0x69, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x68, 0x6b, 0x65, 0x6e, 0x74, 0x0, +0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x65, 0x62, 0x72, 0x6f, 0x6e, 0x0, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x59, 0x61, 0x6b, +0x75, 0x74, 0x73, 0x6b, 0x0, 0x55, 0x54, 0x43, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x31, 0x34, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x31, 0x33, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x31, 0x31, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x31, 0x30, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x39, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x37, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x36, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x35, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x34, 0x3a, 0x33, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x34, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x33, 0x3a, 0x33, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x33, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2d, 0x30, 0x31, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2d, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x31, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x33, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x33, 0x3a, 0x33, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x34, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x34, 0x3a, 0x33, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x35, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x35, 0x3a, 0x33, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x35, 0x3a, 0x34, 0x35, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x36, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x36, 0x3a, 0x33, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x37, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x38, 0x3a, 0x33, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x30, 0x39, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x30, 0x39, 0x3a, 0x33, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x31, 0x30, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x31, 0x31, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x31, 0x32, 0x3a, 0x30, 0x30, 0x0, 0x55, +0x54, 0x43, 0x2b, 0x31, 0x33, 0x3a, 0x30, 0x30, 0x0, 0x55, 0x54, 0x43, 0x2b, 0x31, 0x34, 0x3a, 0x30, 0x30, 0x0 +}; +// GENERATED PART ENDS HERE + +QT_END_NAMESPACE + +#endif // QTIMEZONEPRIVATE_DATA_P_H diff --git a/src/corelib/time/qtimezoneprivate_icu.cpp b/src/corelib/time/qtimezoneprivate_icu.cpp new file mode 100644 index 0000000000..5570ce7571 --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_icu.cpp @@ -0,0 +1,508 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include <unicode/ucal.h> + +#include <qdebug.h> +#include <qlist.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/* + Private + + ICU implementation +*/ + +// ICU utilities + +// Convert TimeType and NameType into ICU UCalendarDisplayNameType +static UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType, QTimeZone::NameType nameType) +{ + // TODO ICU C UCalendarDisplayNameType does not support full set of C++ TimeZone::EDisplayType + switch (nameType) { + case QTimeZone::ShortName : + case QTimeZone::OffsetName : + if (timeType == QTimeZone::DaylightTime) + return UCAL_SHORT_DST; + // Includes GenericTime + return UCAL_SHORT_STANDARD; + case QTimeZone::DefaultName : + case QTimeZone::LongName : + if (timeType == QTimeZone::DaylightTime) + return UCAL_DST; + // Includes GenericTime + return UCAL_STANDARD; + } + return UCAL_STANDARD; +} + +// Qt wrapper around ucal_getDefaultTimeZone() +static QByteArray ucalDefaultTimeZoneId() +{ + int32_t size = 30; + QString result(size, Qt::Uninitialized); + UErrorCode status = U_ZERO_ERROR; + + // size = ucal_getDefaultTimeZone(result, resultLength, status) + size = ucal_getDefaultTimeZone(reinterpret_cast<UChar *>(result.data()), size, &status); + + // If overflow, then resize and retry + if (status == U_BUFFER_OVERFLOW_ERROR) { + result.resize(size); + status = U_ZERO_ERROR; + size = ucal_getDefaultTimeZone(reinterpret_cast<UChar *>(result.data()), size, &status); + } + + // If successful on first or second go, resize and return + if (U_SUCCESS(status)) { + result.resize(size); + return std::move(result).toUtf8(); + } + + return QByteArray(); +} + +// Qt wrapper around ucal_getTimeZoneDisplayName() +static QString ucalTimeZoneDisplayName(UCalendar *ucal, QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QString &localeCode) +{ + int32_t size = 50; + QString result(size, Qt::Uninitialized); + UErrorCode status = U_ZERO_ERROR; + + // size = ucal_getTimeZoneDisplayName(cal, type, locale, result, resultLength, status) + size = ucal_getTimeZoneDisplayName(ucal, + ucalDisplayNameType(timeType, nameType), + localeCode.toUtf8(), + reinterpret_cast<UChar *>(result.data()), + size, + &status); + + // If overflow, then resize and retry + if (status == U_BUFFER_OVERFLOW_ERROR) { + result.resize(size); + status = U_ZERO_ERROR; + size = ucal_getTimeZoneDisplayName(ucal, + ucalDisplayNameType(timeType, nameType), + localeCode.toUtf8(), + reinterpret_cast<UChar *>(result.data()), + size, + &status); + } + + // If successful on first or second go, resize and return + if (U_SUCCESS(status)) { + result.resize(size); + return result; + } + + return QString(); +} + +// Qt wrapper around ucal_get() for offsets +static bool ucalOffsetsAtTime(UCalendar *m_ucal, qint64 atMSecsSinceEpoch, + int *utcOffset, int *dstOffset) +{ + *utcOffset = 0; + *dstOffset = 0; + + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return false; + + // Set the date to find the offset for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + int32_t utc = 0; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + // Returns msecs + utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; + } + + int32_t dst = 0; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + // Returns msecs + dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; + } + + ucal_close(ucal); + if (U_SUCCESS(status)) { + *utcOffset = utc; + *dstOffset = dst; + return true; + } + return false; +} + +// ICU Draft api in v50, should be stable in ICU v51. Available in C++ api from ICU v3.8 +#if U_ICU_VERSION_MAJOR_NUM == 50 +// Qt wrapper around qt_ucal_getTimeZoneTransitionDate & ucal_get +static QTimeZonePrivate::Data ucalTimeZoneTransition(UCalendar *m_ucal, + UTimeZoneTransitionType type, + qint64 atMSecsSinceEpoch) +{ + QTimeZonePrivate::Data tran = QTimeZonePrivate::invalidData(); + + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return tran; + + // Set the date to find the transition for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + // Find the transition time + UDate tranMSecs = 0; + status = U_ZERO_ERROR; + bool ok = ucal_getTimeZoneTransitionDate(ucal, type, &tranMSecs, &status); + + // Set the transition time to find the offsets for + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + ucal_setMillis(ucal, tranMSecs, &status); + } + + int32_t utc = 0; + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; + } + + int32_t dst = 0; + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; + } + + ucal_close(ucal); + if (!U_SUCCESS(status) || !ok) + return tran; + tran.atMSecsSinceEpoch = tranMSecs; + tran.offsetFromUtc = utc + dst; + tran.standardTimeOffset = utc; + tran.daylightTimeOffset = dst; + // TODO No ICU API, use short name instead + if (dst == 0) + tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::StandardTime, + QTimeZone::ShortName, QLocale().name()); + else + tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::DaylightTime, + QTimeZone::ShortName, QLocale().name()); + return tran; +} +#endif // U_ICU_VERSION_SHORT + +// Convert a uenum to a QList<QByteArray> +static QList<QByteArray> uenumToIdList(UEnumeration *uenum) +{ + QList<QByteArray> list; + int32_t size = 0; + UErrorCode status = U_ZERO_ERROR; + // TODO Perhaps use uenum_unext instead? + QByteArray result = uenum_next(uenum, &size, &status); + while (U_SUCCESS(status) && !result.isEmpty()) { + list << result; + status = U_ZERO_ERROR; + result = uenum_next(uenum, &size, &status); + } + std::sort(list.begin(), list.end()); + list.erase(std::unique(list.begin(), list.end()), list.end()); + return list; +} + +// Qt wrapper around ucal_getDSTSavings() +static int ucalDaylightOffset(const QByteArray &id) +{ + UErrorCode status = U_ZERO_ERROR; + const int32_t dstMSecs = ucal_getDSTSavings(reinterpret_cast<const UChar *>(id.data()), &status); + if (U_SUCCESS(status)) + return (dstMSecs / 1000); + else + return 0; +} + +// Create the system default time zone +QIcuTimeZonePrivate::QIcuTimeZonePrivate() + : m_ucal(0) +{ + // TODO No ICU C API to obtain sysem tz, assume default hasn't been changed + init(ucalDefaultTimeZoneId()); +} + +// Create a named time zone +QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QByteArray &ianaId) + : m_ucal(0) +{ + // Need to check validity here as ICu will create a GMT tz if name is invalid + if (availableTimeZoneIds().contains(ianaId)) + init(ianaId); +} + +QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other) + : QTimeZonePrivate(other), m_ucal(0) +{ + // Clone the ucal so we don't close the shared object + UErrorCode status = U_ZERO_ERROR; + m_ucal = ucal_clone(other.m_ucal, &status); + if (!U_SUCCESS(status)) { + m_id.clear(); + m_ucal = 0; + } +} + +QIcuTimeZonePrivate::~QIcuTimeZonePrivate() +{ + ucal_close(m_ucal); +} + +QIcuTimeZonePrivate *QIcuTimeZonePrivate::clone() const +{ + return new QIcuTimeZonePrivate(*this); +} + +void QIcuTimeZonePrivate::init(const QByteArray &ianaId) +{ + m_id = ianaId; + + const QString id = QString::fromUtf8(m_id); + UErrorCode status = U_ZERO_ERROR; + //TODO Use UCAL_GREGORIAN for now to match QLocale, change to UCAL_DEFAULT once full ICU support + m_ucal = ucal_open(reinterpret_cast<const UChar *>(id.data()), id.size(), + QLocale().name().toUtf8(), UCAL_GREGORIAN, &status); + + if (!U_SUCCESS(status)) { + m_id.clear(); + m_ucal = 0; + } +} + +QString QIcuTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + // Return standard offset format name as ICU C api doesn't support it yet + if (nameType == QTimeZone::OffsetName) { + const Data nowData = data(QDateTime::currentMSecsSinceEpoch()); + // We can't use transitions reliably to find out right dst offset + // Instead use dst offset api to try get it if needed + if (timeType == QTimeZone::DaylightTime) + return isoOffsetFormat(nowData.standardTimeOffset + ucalDaylightOffset(m_id)); + else + return isoOffsetFormat(nowData.standardTimeOffset); + } + return ucalTimeZoneDisplayName(m_ucal, timeType, nameType, locale.name()); +} + +QString QIcuTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + // TODO No ICU API, use short name instead + if (isDaylightTime(atMSecsSinceEpoch)) + return displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QString()); + else + return displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QString()); +} + +int QIcuTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return stdOffset + dstOffset; +} + +int QIcuTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return stdOffset; +} + +int QIcuTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return dstOffset; +} + +bool QIcuTimeZonePrivate::hasDaylightTime() const +{ + // TODO No direct ICU C api, work-around below not reliable? Find a better way? + return (ucalDaylightOffset(m_id) != 0); +} + +bool QIcuTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return false; + + // Set the date to find the offset for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + bool result = false; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + result = ucal_inDaylightTime(ucal, &status); + } + + ucal_close(ucal); + return result; +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable + QTimeZonePrivate::Data data = invalidData(); +#if U_ICU_VERSION_MAJOR_NUM == 50 + data = ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE, + forMSecsSinceEpoch); +#else + ucalOffsetsAtTime(m_ucal, forMSecsSinceEpoch, &data.standardTimeOffset, + &data.daylightTimeOffset); + data.offsetFromUtc = data.standardTimeOffset + data.daylightTimeOffset; + data.abbreviation = abbreviation(forMSecsSinceEpoch); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; +} + +bool QIcuTimeZonePrivate::hasTransitions() const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return true; +#else + return false; +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_NEXT, afterMSecsSinceEpoch); +#else + Q_UNUSED(afterMSecsSinceEpoch) + return invalidData(); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS, beforeMSecsSinceEpoch); +#else + Q_UNUSED(beforeMSecsSinceEpoch) + return invalidData(); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QByteArray QIcuTimeZonePrivate::systemTimeZoneId() const +{ + // No ICU C API to obtain sysem tz + // TODO Assume default hasn't been changed and is the latests system + return ucalDefaultTimeZoneId(); +} + +QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds() const +{ + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openTimeZones(&status); + QList<QByteArray> result; + if (U_SUCCESS(status)) + result = uenumToIdList(uenum); + uenum_close(uenum); + return result; +} + +QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + const QLatin1String regionCode = QLocalePrivate::countryToCode(country); + const QByteArray regionCodeUtf8 = QString(regionCode).toUtf8(); + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openCountryTimeZones(regionCodeUtf8.data(), &status); + QList<QByteArray> result; + if (U_SUCCESS(status)) + result = uenumToIdList(uenum); + uenum_close(uenum); + return result; +} + +QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const +{ +// TODO Available directly in C++ api but not C api, from 4.8 onwards new filter method works +#if U_ICU_VERSION_MAJOR_NUM >= 49 || (U_ICU_VERSION_MAJOR_NUM == 4 && U_ICU_VERSION_MINOR_NUM == 8) + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, 0, + &offsetFromUtc, &status); + QList<QByteArray> result; + if (U_SUCCESS(status)) + result = uenumToIdList(uenum); + uenum_close(uenum); + return result; +#else + return QTimeZonePrivate::availableTimeZoneIds(offsetFromUtc); +#endif +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_mac.mm b/src/corelib/time/qtimezoneprivate_mac.mm new file mode 100644 index 0000000000..d3c4fbe5da --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_mac.mm @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include "private/qcore_mac_p.h" +#include "qstringlist.h" + +#include <Foundation/NSTimeZone.h> + +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/* + Private + + OS X system implementation +*/ + +// Create the system default time zone +QMacTimeZonePrivate::QMacTimeZonePrivate() + : m_nstz(0) +{ + init(systemTimeZoneId()); +} + +// Create a named time zone +QMacTimeZonePrivate::QMacTimeZonePrivate(const QByteArray &ianaId) + : m_nstz(0) +{ + init(ianaId); +} + +QMacTimeZonePrivate::QMacTimeZonePrivate(const QMacTimeZonePrivate &other) + : QTimeZonePrivate(other), m_nstz(0) +{ + m_nstz = [other.m_nstz copy]; +} + +QMacTimeZonePrivate::~QMacTimeZonePrivate() +{ + [m_nstz release]; +} + +QMacTimeZonePrivate *QMacTimeZonePrivate::clone() const +{ + return new QMacTimeZonePrivate(*this); +} + +void QMacTimeZonePrivate::init(const QByteArray &ianaId) +{ + if (availableTimeZoneIds().contains(ianaId)) { + m_nstz = [[NSTimeZone timeZoneWithName:QString::fromUtf8(ianaId).toNSString()] retain]; + if (m_nstz) + m_id = ianaId; + } +} + +QString QMacTimeZonePrivate::comment() const +{ + return QString::fromNSString([m_nstz description]); +} + +QString QMacTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + // TODO Mac doesn't support OffsetName yet so use standard offset name + if (nameType == QTimeZone::OffsetName) { + const Data nowData = data(QDateTime::currentMSecsSinceEpoch()); + // TODO Cheat for now, assume if has dst the offset if 1 hour + if (timeType == QTimeZone::DaylightTime && hasDaylightTime()) + return isoOffsetFormat(nowData.standardTimeOffset + 3600); + else + return isoOffsetFormat(nowData.standardTimeOffset); + } + + NSTimeZoneNameStyle style = NSTimeZoneNameStyleStandard; + + switch (nameType) { + case QTimeZone::ShortName : + if (timeType == QTimeZone::DaylightTime) + style = NSTimeZoneNameStyleShortDaylightSaving; + else if (timeType == QTimeZone::GenericTime) + style = NSTimeZoneNameStyleShortGeneric; + else + style = NSTimeZoneNameStyleShortStandard; + break; + case QTimeZone::DefaultName : + case QTimeZone::LongName : + if (timeType == QTimeZone::DaylightTime) + style = NSTimeZoneNameStyleDaylightSaving; + else if (timeType == QTimeZone::GenericTime) + style = NSTimeZoneNameStyleGeneric; + else + style = NSTimeZoneNameStyleStandard; + break; + case QTimeZone::OffsetName : + // Unreachable + break; + } + + NSString *macLocaleCode = locale.name().toNSString(); + NSLocale *macLocale = [[NSLocale alloc] initWithLocaleIdentifier:macLocaleCode]; + const QString result = QString::fromNSString([m_nstz localizedName:style locale:macLocale]); + [macLocale release]; + return result; +} + +QString QMacTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0; + return QString::fromNSString([m_nstz abbreviationForDate:[NSDate dateWithTimeIntervalSince1970:seconds]]); +} + +int QMacTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0; + return [m_nstz secondsFromGMTForDate:[NSDate dateWithTimeIntervalSince1970:seconds]]; +} + +int QMacTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return offsetFromUtc(atMSecsSinceEpoch) - daylightTimeOffset(atMSecsSinceEpoch); +} + +int QMacTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0; + return [m_nstz daylightSavingTimeOffsetForDate:[NSDate dateWithTimeIntervalSince1970:seconds]]; +} + +bool QMacTimeZonePrivate::hasDaylightTime() const +{ + // TODO No Mac API, assume if has transitions + return hasTransitions(); +} + +bool QMacTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0; + return [m_nstz isDaylightSavingTimeForDate:[NSDate dateWithTimeIntervalSince1970:seconds]]; +} + +QTimeZonePrivate::Data QMacTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + const NSTimeInterval seconds = forMSecsSinceEpoch / 1000.0; + NSDate *date = [NSDate dateWithTimeIntervalSince1970:seconds]; + Data data; + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + data.offsetFromUtc = [m_nstz secondsFromGMTForDate:date]; + data.daylightTimeOffset = [m_nstz daylightSavingTimeOffsetForDate:date]; + data.standardTimeOffset = data.offsetFromUtc - data.daylightTimeOffset; + data.abbreviation = QString::fromNSString([m_nstz abbreviationForDate:date]); + return data; +} + +bool QMacTimeZonePrivate::hasTransitions() const +{ + // TODO No direct Mac API, so return if has next after 1970, i.e. since start of tz + // TODO Not sure what is returned in event of no transitions, assume will be before requested date + NSDate *epoch = [NSDate dateWithTimeIntervalSince1970:0]; + const NSDate *date = [m_nstz nextDaylightSavingTimeTransitionAfterDate:epoch]; + const bool result = ([date timeIntervalSince1970] > [epoch timeIntervalSince1970]); + return result; +} + +QTimeZonePrivate::Data QMacTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + QTimeZonePrivate::Data tran; + const NSTimeInterval seconds = afterMSecsSinceEpoch / 1000.0; + NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:seconds]; + nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate]; + const NSTimeInterval nextSecs = [nextDate timeIntervalSince1970]; + if (nextDate == nil || nextSecs <= seconds) { + [nextDate release]; + return invalidData(); + } + tran.atMSecsSinceEpoch = nextSecs * 1000; + tran.offsetFromUtc = [m_nstz secondsFromGMTForDate:nextDate]; + tran.daylightTimeOffset = [m_nstz daylightSavingTimeOffsetForDate:nextDate]; + tran.standardTimeOffset = tran.offsetFromUtc - tran.daylightTimeOffset; + tran.abbreviation = QString::fromNSString([m_nstz abbreviationForDate:nextDate]); + return tran; +} + +QTimeZonePrivate::Data QMacTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // The native API only lets us search forward, so we need to find an early-enough start: + const NSTimeInterval lowerBound = std::numeric_limits<NSTimeInterval>::lowest(); + const qint64 endSecs = beforeMSecsSinceEpoch / 1000; + const int year = 366 * 24 * 3600; // a (long) year, in seconds + NSTimeInterval prevSecs = endSecs; // sentinel for later check + NSTimeInterval nextSecs = prevSecs - year; + NSTimeInterval tranSecs = lowerBound; // time at a transition; may be > endSecs + + NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:nextSecs]; + nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate]; + if (nextDate != nil + && (tranSecs = [nextDate timeIntervalSince1970]) < endSecs) { + // There's a transition within the last year before endSecs: + nextSecs = tranSecs; + } else { + // Need to start our search earlier: + nextDate = [NSDate dateWithTimeIntervalSince1970:lowerBound]; + nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate]; + if (nextDate != nil) { + NSTimeInterval lateSecs = nextSecs; + nextSecs = [nextDate timeIntervalSince1970]; + Q_ASSERT(nextSecs <= endSecs - year || nextSecs == tranSecs); + /* + We're looking at the first ever transition for our zone, at + nextSecs (and our zone *does* have at least one transition). If + it's later than endSecs - year, then we must have found it on the + initial check and therefore set tranSecs to the same transition + time (which, we can infer here, is >= endSecs). In this case, we + won't enter the binary-chop loop, below. + + In the loop, nextSecs < lateSecs < endSecs: we have a transition + at nextSecs and there is no transition between lateSecs and + endSecs. The loop narrows the interval between nextSecs and + lateSecs by looking for a transition after their mid-point; if it + finds one < endSecs, nextSecs moves to this transition; otherwise, + lateSecs moves to the mid-point. This soon enough narrows the gap + to within a year, after which walking forward one transition at a + time (the "Wind through" loop, below) is good enough. + */ + + // Binary chop to within a year of last transition before endSecs: + while (nextSecs + year < lateSecs) { + // Careful about overflow, not fussy about rounding errors: + NSTimeInterval middle = nextSecs / 2 + lateSecs / 2; + NSDate *split = [NSDate dateWithTimeIntervalSince1970:middle]; + split = [m_nstz nextDaylightSavingTimeTransitionAfterDate:split]; + if (split != nil + && (tranSecs = [split timeIntervalSince1970]) < endSecs) { + nextDate = split; + nextSecs = tranSecs; + } else { + lateSecs = middle; + } + } + Q_ASSERT(nextDate != nil); + // ... and nextSecs < endSecs unless first transition ever was >= endSecs. + } // else: we have no data - prevSecs is still endSecs, nextDate is still nil + } + // Either nextDate is nil or nextSecs is at its transition. + + // Wind through remaining transitions (spanning at most a year), one at a time: + while (nextDate != nil && nextSecs < endSecs) { + prevSecs = nextSecs; + nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate]; + nextSecs = [nextDate timeIntervalSince1970]; + if (nextSecs <= prevSecs) // presumably no later data available + break; + } + if (prevSecs < endSecs) // i.e. we did make it into that while loop + return data(qint64(prevSecs * 1e3)); + + // No transition data; or first transition later than requested time. + return invalidData(); +} + +QByteArray QMacTimeZonePrivate::systemTimeZoneId() const +{ + // Reset the cached system tz then return the name + [NSTimeZone resetSystemTimeZone]; + return QString::fromNSString([[NSTimeZone systemTimeZone] name]).toUtf8(); +} + +QList<QByteArray> QMacTimeZonePrivate::availableTimeZoneIds() const +{ + NSEnumerator *enumerator = [[NSTimeZone knownTimeZoneNames] objectEnumerator]; + QByteArray tzid = QString::fromNSString([enumerator nextObject]).toUtf8(); + + QList<QByteArray> list; + while (!tzid.isEmpty()) { + list << tzid; + tzid = QString::fromNSString([enumerator nextObject]).toUtf8(); + } + + std::sort(list.begin(), list.end()); + list.erase(std::unique(list.begin(), list.end()), list.end()); + + return list; +} + +NSTimeZone *QMacTimeZonePrivate::nsTimeZone() const +{ + return m_nstz; +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h new file mode 100644 index 0000000000..5f6491ef81 --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QTIMEZONEPRIVATE_P_H +#define QTIMEZONEPRIVATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "qtimezone.h" +#include "private/qlocale_p.h" +#include "qvector.h" + +#if QT_CONFIG(icu) +#include <unicode/ucal.h> +#endif + +#ifdef Q_OS_DARWIN +Q_FORWARD_DECLARE_OBJC_CLASS(NSTimeZone); +#endif // Q_OS_DARWIN + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#endif // Q_OS_WIN + +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) +#include <QtCore/private/qjni_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QTimeZonePrivate : public QSharedData +{ +public: + //Version of QTimeZone::OffsetData struct using msecs for efficiency + struct Data { + QString abbreviation; + qint64 atMSecsSinceEpoch; + int offsetFromUtc; + int standardTimeOffset; + int daylightTimeOffset; + }; + typedef QVector<Data> DataList; + + // Create null time zone + QTimeZonePrivate(); + QTimeZonePrivate(const QTimeZonePrivate &other); + virtual ~QTimeZonePrivate(); + + virtual QTimeZonePrivate *clone() const; + + bool operator==(const QTimeZonePrivate &other) const; + bool operator!=(const QTimeZonePrivate &other) const; + + bool isValid() const; + + QByteArray id() const; + virtual QLocale::Country country() const; + virtual QString comment() const; + + virtual QString displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const; + virtual QString displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const; + virtual QString abbreviation(qint64 atMSecsSinceEpoch) const; + + virtual int offsetFromUtc(qint64 atMSecsSinceEpoch) const; + virtual int standardTimeOffset(qint64 atMSecsSinceEpoch) const; + virtual int daylightTimeOffset(qint64 atMSecsSinceEpoch) const; + + virtual bool hasDaylightTime() const; + virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const; + + virtual Data data(qint64 forMSecsSinceEpoch) const; + Data dataForLocalTime(qint64 forLocalMSecs, int hint) const; + + virtual bool hasTransitions() const; + virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const; + virtual Data previousTransition(qint64 beforeMSecsSinceEpoch) const; + DataList transitions(qint64 fromMSecsSinceEpoch, qint64 toMSecsSinceEpoch) const; + + virtual QByteArray systemTimeZoneId() const; + + virtual bool isTimeZoneIdAvailable(const QByteArray &ianaId) const; + virtual QList<QByteArray> availableTimeZoneIds() const; + virtual QList<QByteArray> availableTimeZoneIds(QLocale::Country country) const; + virtual QList<QByteArray> availableTimeZoneIds(int utcOffset) const; + + virtual void serialize(QDataStream &ds) const; + + // Static Utility Methods + static inline qint64 maxMSecs() { return std::numeric_limits<qint64>::max(); } + static inline qint64 minMSecs() { return std::numeric_limits<qint64>::min() + 1; } + static inline qint64 invalidMSecs() { return std::numeric_limits<qint64>::min(); } + static inline qint64 invalidSeconds() { return std::numeric_limits<int>::min(); } + static Data invalidData(); + static QTimeZone::OffsetData invalidOffsetData(); + static QTimeZone::OffsetData toOffsetData(const Data &data); + static bool isValidId(const QByteArray &ianaId); + static QString isoOffsetFormat(int offsetFromUtc); + + static QByteArray ianaIdToWindowsId(const QByteArray &ianaId); + static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId); + static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId, + QLocale::Country country); + static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId); + static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId, + QLocale::Country country); + + // returns "UTC" QString and QByteArray + Q_REQUIRED_RESULT static inline QString utcQString() + { + return QStringLiteral("UTC"); + } + + Q_REQUIRED_RESULT static inline QByteArray utcQByteArray() + { + return QByteArrayLiteral("UTC"); + } + +protected: + QByteArray m_id; +}; +Q_DECLARE_TYPEINFO(QTimeZonePrivate::Data, Q_MOVABLE_TYPE); + +template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone(); + +class Q_AUTOTEST_EXPORT QUtcTimeZonePrivate final : public QTimeZonePrivate +{ +public: + // Create default UTC time zone + QUtcTimeZonePrivate(); + // Create named time zone + QUtcTimeZonePrivate(const QByteArray &utcId); + // Create offset from UTC + QUtcTimeZonePrivate(int offsetSeconds); + // Create custom offset from UTC + QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds, const QString &name, + const QString &abbreviation, QLocale::Country country, + const QString &comment); + QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other); + virtual ~QUtcTimeZonePrivate(); + + QUtcTimeZonePrivate *clone() const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + QLocale::Country country() const override; + QString comment() const override; + + QString displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + bool isTimeZoneIdAvailable(const QByteArray &ianaId) const override; + QList<QByteArray> availableTimeZoneIds() const override; + QList<QByteArray> availableTimeZoneIds(QLocale::Country country) const override; + QList<QByteArray> availableTimeZoneIds(int utcOffset) const override; + + void serialize(QDataStream &ds) const override; + +private: + void init(const QByteArray &zoneId); + void init(const QByteArray &zoneId, int offsetSeconds, const QString &name, + const QString &abbreviation, QLocale::Country country, + const QString &comment); + + QString m_name; + QString m_abbreviation; + QString m_comment; + QLocale::Country m_country; + int m_offsetFromUtc; +}; + +#if QT_CONFIG(icu) +class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate final : public QTimeZonePrivate +{ +public: + // Create default time zone + QIcuTimeZonePrivate(); + // Create named time zone + QIcuTimeZonePrivate(const QByteArray &ianaId); + QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other); + ~QIcuTimeZonePrivate(); + + QIcuTimeZonePrivate *clone() const override; + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + QList<QByteArray> availableTimeZoneIds() const override; + QList<QByteArray> availableTimeZoneIds(QLocale::Country country) const override; + QList<QByteArray> availableTimeZoneIds(int offsetFromUtc) const override; + +private: + void init(const QByteArray &ianaId); + + UCalendar *m_ucal; +}; +#endif + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID_EMBEDDED)) +struct QTzTransitionTime +{ + qint64 atMSecsSinceEpoch; + quint8 ruleIndex; +}; +Q_DECLARE_TYPEINFO(QTzTransitionTime, Q_PRIMITIVE_TYPE); +struct QTzTransitionRule +{ + int stdOffset; + int dstOffset; + quint8 abbreviationIndex; +}; +Q_DECLARE_TYPEINFO(QTzTransitionRule, Q_PRIMITIVE_TYPE); +Q_DECL_CONSTEXPR inline bool operator==(const QTzTransitionRule &lhs, const QTzTransitionRule &rhs) noexcept +{ return lhs.stdOffset == rhs.stdOffset && lhs.dstOffset == rhs.dstOffset && lhs.abbreviationIndex == rhs.abbreviationIndex; } +Q_DECL_CONSTEXPR inline bool operator!=(const QTzTransitionRule &lhs, const QTzTransitionRule &rhs) noexcept +{ return !operator==(lhs, rhs); } + +class Q_AUTOTEST_EXPORT QTzTimeZonePrivate final : public QTimeZonePrivate +{ + QTzTimeZonePrivate(const QTzTimeZonePrivate &) = default; +public: + // Create default time zone + QTzTimeZonePrivate(); + // Create named time zone + QTzTimeZonePrivate(const QByteArray &ianaId); + ~QTzTimeZonePrivate(); + + QTzTimeZonePrivate *clone() const override; + + QLocale::Country country() const override; + QString comment() const override; + + QString displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + bool isTimeZoneIdAvailable(const QByteArray &ianaId) const override; + QList<QByteArray> availableTimeZoneIds() const override; + QList<QByteArray> availableTimeZoneIds(QLocale::Country country) const override; + +private: + void init(const QByteArray &ianaId); + QVector<QTimeZonePrivate::Data> getPosixTransitions(qint64 msNear) const; + + Data dataForTzTransition(QTzTransitionTime tran) const; + QVector<QTzTransitionTime> m_tranTimes; + QVector<QTzTransitionRule> m_tranRules; + QList<QByteArray> m_abbreviations; +#if QT_CONFIG(icu) + mutable QSharedDataPointer<QTimeZonePrivate> m_icu; +#endif + QByteArray m_posixRule; +}; +#endif // Q_OS_UNIX + +#ifdef Q_OS_MAC +class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate +{ +public: + // Create default time zone + QMacTimeZonePrivate(); + // Create named time zone + QMacTimeZonePrivate(const QByteArray &ianaId); + QMacTimeZonePrivate(const QMacTimeZonePrivate &other); + ~QMacTimeZonePrivate(); + + QMacTimeZonePrivate *clone() const override; + + QString comment() const override; + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + QList<QByteArray> availableTimeZoneIds() const override; + + NSTimeZone *nsTimeZone() const; + +private: + void init(const QByteArray &zoneId); + + NSTimeZone *m_nstz; +}; +#endif // Q_OS_MAC + +#ifdef Q_OS_WIN +class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate +{ +public: + struct QWinTransitionRule { + int startYear; + int standardTimeBias; + int daylightTimeBias; + SYSTEMTIME standardTimeRule; + SYSTEMTIME daylightTimeRule; + }; + + // Create default time zone + QWinTimeZonePrivate(); + // Create named time zone + QWinTimeZonePrivate(const QByteArray &ianaId); + QWinTimeZonePrivate(const QWinTimeZonePrivate &other); + ~QWinTimeZonePrivate(); + + QWinTimeZonePrivate *clone() const override; + + QString comment() const override; + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + QList<QByteArray> availableTimeZoneIds() const override; + +private: + void init(const QByteArray &ianaId); + QTimeZonePrivate::Data ruleToData(const QWinTransitionRule &rule, qint64 atMSecsSinceEpoch, + QTimeZone::TimeType type, bool fakeDst = false) const; + + QByteArray m_windowsId; + QString m_displayName; + QString m_standardName; + QString m_daylightName; + QList<QWinTransitionRule> m_tranRules; +}; +#endif // Q_OS_WIN + +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) +class QAndroidTimeZonePrivate final : public QTimeZonePrivate +{ +public: + // Create default time zone + QAndroidTimeZonePrivate(); + // Create named time zone + QAndroidTimeZonePrivate(const QByteArray &ianaId); + QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &other); + ~QAndroidTimeZonePrivate(); + + QAndroidTimeZonePrivate *clone() const override; + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const override; + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + + QByteArray systemTimeZoneId() const override; + + QList<QByteArray> availableTimeZoneIds() const override; + +private: + void init(const QByteArray &zoneId); + + QJNIObjectPrivate androidTimeZone; + +}; +#endif // Q_OS_ANDROID + +QT_END_NAMESPACE + +#endif // QTIMEZONEPRIVATE_P_H diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp new file mode 100644 index 0000000000..2c845b1bce --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -0,0 +1,1154 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" +#include "qdatetime_p.h" // ### Qt 5.14: remove once YearRange is on QDateTime +#include "private/qlocale_tools_p.h" + +#include <QtCore/QFile> +#include <QtCore/QHash> +#include <QtCore/QDataStream> +#include <QtCore/QDateTime> + +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/* + Private + + tz file implementation +*/ + +struct QTzTimeZone { + QLocale::Country country; + QByteArray comment; +}; + +// Define as a type as Q_GLOBAL_STATIC doesn't like it +typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash; + +// Parse zone.tab table, assume lists all installed zones, if not will need to read directories +static QTzTimeZoneHash loadTzTimeZones() +{ + QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab"); + if (!QFile::exists(path)) + path = QStringLiteral("/usr/lib/zoneinfo/zone.tab"); + + QFile tzif(path); + if (!tzif.open(QIODevice::ReadOnly)) + return QTzTimeZoneHash(); + + QTzTimeZoneHash zonesHash; + // TODO QTextStream inefficient, replace later + QTextStream ts(&tzif); + while (!ts.atEnd()) { + const QString line = ts.readLine(); + // Comment lines are prefixed with a # + if (!line.isEmpty() && line.at(0) != '#') { + // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments + const auto parts = line.splitRef(QLatin1Char('\t')); + QTzTimeZone zone; + zone.country = QLocalePrivate::codeToCountry(parts.at(0)); + if (parts.size() > 3) + zone.comment = parts.at(3).toUtf8(); + zonesHash.insert(parts.at(2).toUtf8(), zone); + } + } + return zonesHash; +} + +// Hash of available system tz files as loaded by loadTzTimeZones() +Q_GLOBAL_STATIC_WITH_ARGS(const QTzTimeZoneHash, tzZones, (loadTzTimeZones())); + +/* + The following is copied and modified from tzfile.h which is in the public domain. + Copied as no compatibility guarantee and is never system installed. + See https://github.com/eggert/tz/blob/master/tzfile.h +*/ + +#define TZ_MAGIC "TZif" +#define TZ_MAX_TIMES 1200 +#define TZ_MAX_TYPES 256 // Limited by what (unsigned char)'s can hold +#define TZ_MAX_CHARS 50 // Maximum number of abbreviation characters +#define TZ_MAX_LEAPS 50 // Maximum number of leap second corrections + +struct QTzHeader { + char tzh_magic[4]; // TZ_MAGIC + char tzh_version; // '\0' or '2' as of 2005 + char tzh_reserved[15]; // reserved--must be zero + quint32 tzh_ttisgmtcnt; // number of trans. time flags + quint32 tzh_ttisstdcnt; // number of trans. time flags + quint32 tzh_leapcnt; // number of leap seconds + quint32 tzh_timecnt; // number of transition times + quint32 tzh_typecnt; // number of local time types + quint32 tzh_charcnt; // number of abbr. chars +}; + +struct QTzTransition { + qint64 tz_time; // Transition time + quint8 tz_typeind; // Type Index +}; +Q_DECLARE_TYPEINFO(QTzTransition, Q_PRIMITIVE_TYPE); + +struct QTzType { + int tz_gmtoff; // UTC offset in seconds + bool tz_isdst; // Is DST + quint8 tz_abbrind; // abbreviation list index +}; +Q_DECLARE_TYPEINFO(QTzType, Q_PRIMITIVE_TYPE); + + +// TZ File parsing + +static QTzHeader parseTzHeader(QDataStream &ds, bool *ok) +{ + QTzHeader hdr; + quint8 ch; + *ok = false; + + // Parse Magic, 4 bytes + ds.readRawData(hdr.tzh_magic, 4); + + if (memcmp(hdr.tzh_magic, TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok) + return hdr; + + // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2', since 2013 a '3' + ds >> ch; + hdr.tzh_version = ch; + if (ds.status() != QDataStream::Ok + || (hdr.tzh_version != '2' && hdr.tzh_version != '\0' && hdr.tzh_version != '3')) { + return hdr; + } + + // Parse reserved space, 15 bytes + ds.readRawData(hdr.tzh_reserved, 15); + if (ds.status() != QDataStream::Ok) + return hdr; + + // Parse rest of header, 6 x 4-byte transition counts + ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt + >> hdr.tzh_typecnt >> hdr.tzh_charcnt; + + // Check defined maximums + if (ds.status() != QDataStream::Ok + || hdr.tzh_timecnt > TZ_MAX_TIMES + || hdr.tzh_typecnt > TZ_MAX_TYPES + || hdr.tzh_charcnt > TZ_MAX_CHARS + || hdr.tzh_leapcnt > TZ_MAX_LEAPS + || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt + || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) { + return hdr; + } + + *ok = true; + return hdr; +} + +static QVector<QTzTransition> parseTzTransitions(QDataStream &ds, int tzh_timecnt, bool longTran) +{ + QVector<QTzTransition> transitions(tzh_timecnt); + + if (longTran) { + // Parse tzh_timecnt x 8-byte transition times + for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) { + ds >> transitions[i].tz_time; + if (ds.status() != QDataStream::Ok) + transitions.resize(i); + } + } else { + // Parse tzh_timecnt x 4-byte transition times + qint32 val; + for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) { + ds >> val; + transitions[i].tz_time = val; + if (ds.status() != QDataStream::Ok) + transitions.resize(i); + } + } + + // Parse tzh_timecnt x 1-byte transition type index + for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) { + quint8 typeind; + ds >> typeind; + if (ds.status() == QDataStream::Ok) + transitions[i].tz_typeind = typeind; + } + + return transitions; +} + +static QVector<QTzType> parseTzTypes(QDataStream &ds, int tzh_typecnt) +{ + QVector<QTzType> types(tzh_typecnt); + + // Parse tzh_typecnt x transition types + for (int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) { + QTzType &type = types[i]; + // Parse UTC Offset, 4 bytes + ds >> type.tz_gmtoff; + // Parse Is DST flag, 1 byte + if (ds.status() == QDataStream::Ok) + ds >> type.tz_isdst; + // Parse Abbreviation Array Index, 1 byte + if (ds.status() == QDataStream::Ok) + ds >> type.tz_abbrind; + if (ds.status() != QDataStream::Ok) + types.resize(i); + } + + return types; +} + +static QMap<int, QByteArray> parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QVector<QTzType> &types) +{ + // Parse the abbreviation list which is tzh_charcnt long with '\0' separated strings. The + // QTzType.tz_abbrind index points to the first char of the abbreviation in the array, not the + // occurrence in the list. It can also point to a partial string so we need to use the actual typeList + // index values when parsing. By using a map with tz_abbrind as ordered key we get both index + // methods in one data structure and can convert the types afterwards. + QMap<int, QByteArray> map; + quint8 ch; + QByteArray input; + // First parse the full abbrev string + for (int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) { + ds >> ch; + if (ds.status() == QDataStream::Ok) + input.append(char(ch)); + else + return map; + } + // Then extract all the substrings pointed to by types + for (const QTzType &type : types) { + QByteArray abbrev; + for (int i = type.tz_abbrind; input.at(i) != '\0'; ++i) + abbrev.append(input.at(i)); + // Have reached end of an abbreviation, so add to map + map[type.tz_abbrind] = abbrev; + } + return map; +} + +static void parseTzLeapSeconds(QDataStream &ds, int tzh_leapcnt, bool longTran) +{ + // Parse tzh_leapcnt x pairs of leap seconds + // We don't use leap seconds, so only read and don't store + qint32 val; + if (longTran) { + // v2 file format, each entry is 12 bytes long + qint64 time; + for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) { + // Parse Leap Occurrence Time, 8 bytes + ds >> time; + // Parse Leap Seconds To Apply, 4 bytes + if (ds.status() == QDataStream::Ok) + ds >> val; + } + } else { + // v0 file format, each entry is 8 bytes long + for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) { + // Parse Leap Occurrence Time, 4 bytes + ds >> val; + // Parse Leap Seconds To Apply, 4 bytes + if (ds.status() == QDataStream::Ok) + ds >> val; + } + } +} + +static QVector<QTzType> parseTzIndicators(QDataStream &ds, const QVector<QTzType> &types, int tzh_ttisstdcnt, int tzh_ttisgmtcnt) +{ + QVector<QTzType> result = types; + bool temp; + /* + Scan and discard indicators. + + These indicators are only of use (by the date program) when "handling + POSIX-style time zone environment variables". The flags here say whether + the *specification* of the zone gave the time in UTC, local standard time + or local wall time; but whatever was specified has been digested for us, + already, by the zone-info compiler (zic), so that the tz_time values read + from the file (by parseTzTransitions) are all in UTC. + */ + + // Scan tzh_ttisstdcnt x 1-byte standard/wall indicators + for (int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i) + ds >> temp; + + // Scan tzh_ttisgmtcnt x 1-byte UTC/local indicators + for (int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i) + ds >> temp; + + return result; +} + +static QByteArray parseTzPosixRule(QDataStream &ds) +{ + // Parse POSIX rule, variable length '\n' enclosed + QByteArray rule; + + quint8 ch; + ds >> ch; + if (ch != '\n' || ds.status() != QDataStream::Ok) + return rule; + ds >> ch; + while (ch != '\n' && ds.status() == QDataStream::Ok) { + rule.append((char)ch); + ds >> ch; + } + + return rule; +} + +static QDate calculateDowDate(int year, int month, int dayOfWeek, int week) +{ + QDate date(year, month, 1); + int startDow = date.dayOfWeek(); + if (startDow <= dayOfWeek) + date = date.addDays(dayOfWeek - startDow - 7); + else + date = date.addDays(dayOfWeek - startDow); + date = date.addDays(week * 7); + while (date.month() != month) + date = date.addDays(-7); + return date; +} + +static QDate calculatePosixDate(const QByteArray &dateRule, int year) +{ + // Can start with M, J, or a digit + if (dateRule.at(0) == 'M') { + // nth week in month format "Mmonth.week.dow" + QList<QByteArray> dateParts = dateRule.split('.'); + int month = dateParts.at(0).mid(1).toInt(); + int week = dateParts.at(1).toInt(); + int dow = dateParts.at(2).toInt(); + if (dow == 0) + ++dow; + return calculateDowDate(year, month, dow, week); + } else if (dateRule.at(0) == 'J') { + // Day of Year ignores Feb 29 + int doy = dateRule.mid(1).toInt(); + QDate date = QDate(year, 1, 1).addDays(doy - 1); + if (QDate::isLeapYear(date.year())) + date = date.addDays(-1); + return date; + } else { + // Day of Year includes Feb 29 + int doy = dateRule.toInt(); + return QDate(year, 1, 1).addDays(doy - 1); + } +} + +// returns the time in seconds, INT_MIN if we failed to parse +static int parsePosixTime(const char *begin, const char *end) +{ + // Format "hh[:mm[:ss]]" + int hour, min = 0, sec = 0; + + // Note that the calls to qstrtoll do *not* check the end pointer, which + // means they proceed until they find a non-digit. We check that we're + // still in range at the end, but we may have read from past end. It's the + // caller's responsibility to ensure that begin is part of a + // null-terminated string. + + bool ok = false; + hour = qstrtoll(begin, &begin, 10, &ok); + if (!ok || hour < 0) + return INT_MIN; + if (begin < end && *begin == ':') { + // minutes + ++begin; + min = qstrtoll(begin, &begin, 10, &ok); + if (!ok || min < 0) + return INT_MIN; + + if (begin < end && *begin == ':') { + // seconds + ++begin; + sec = qstrtoll(begin, &begin, 10, &ok); + if (!ok || sec < 0) + return INT_MIN; + } + } + + // we must have consumed everything + if (begin != end) + return INT_MIN; + + return (hour * 60 + min) * 60 + sec; +} + +static QTime parsePosixTransitionTime(const QByteArray &timeRule) +{ + // Format "hh[:mm[:ss]]" + int value = parsePosixTime(timeRule.constBegin(), timeRule.constEnd()); + if (value == INT_MIN) { + // if we failed to parse, return 02:00 + return QTime(2, 0, 0); + } + return QTime::fromMSecsSinceStartOfDay(value * 1000); +} + +static int parsePosixOffset(const char *begin, const char *end) +{ + // Format "[+|-]hh[:mm[:ss]]" + // note that the sign is inverted because POSIX counts in hours West of GMT + bool negate = true; + if (*begin == '+') { + ++begin; + } else if (*begin == '-') { + negate = false; + ++begin; + } + + int value = parsePosixTime(begin, end); + if (value == INT_MIN) + return value; + return negate ? -value : value; +} + +static inline bool asciiIsLetter(char ch) +{ + ch |= 0x20; // lowercases if it is a letter, otherwise just corrupts ch + return ch >= 'a' && ch <= 'z'; +} + +namespace { + +struct PosixZone +{ + enum { + InvalidOffset = INT_MIN, + }; + + QString name; + int offset; + + static PosixZone invalid() { return {QString(), InvalidOffset}; } + static PosixZone parse(const char *&pos, const char *end); + + bool hasValidOffset() const noexcept { return offset != InvalidOffset; } +}; + +} // unnamed namespace + +// Returns the zone name, the offset (in seconds) and advances \a begin to +// where the parsing ended. Returns a zone of INT_MIN in case an offset +// couldn't be read. +PosixZone PosixZone::parse(const char *&pos, const char *end) +{ + static const char offsetChars[] = "0123456789:"; + + const char *nameBegin = pos; + const char *nameEnd; + Q_ASSERT(pos < end); + + if (*pos == '<') { + nameBegin = pos + 1; // skip the '<' + nameEnd = nameBegin; + while (nameEnd < end && *nameEnd != '>') { + // POSIX says only alphanumeric, but we allow anything + ++nameEnd; + } + pos = nameEnd + 1; // skip the '>' + } else { + nameBegin = pos; + nameEnd = nameBegin; + while (nameEnd < end && asciiIsLetter(*nameEnd)) + ++nameEnd; + pos = nameEnd; + } + if (nameEnd - nameBegin < 3) + return invalid(); // name must be at least 3 characters long + + // zone offset, form [+-]hh:mm:ss + const char *zoneBegin = pos; + const char *zoneEnd = pos; + if (zoneEnd < end && (zoneEnd[0] == '+' || zoneEnd[0] == '-')) + ++zoneEnd; + while (zoneEnd < end) { + if (strchr(offsetChars, char(*zoneEnd)) == NULL) + break; + ++zoneEnd; + } + + QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin); + const int offset = zoneEnd > zoneBegin ? parsePosixOffset(zoneBegin, zoneEnd) : InvalidOffset; + pos = zoneEnd; + // UTC+hh:mm:ss or GMT+hh:mm:ss should be read as offsets from UTC, not as a + // POSIX rule naming a zone as UTC or GMT and specifying a non-zero offset. + if (offset != 0 && (name == QLatin1String("UTC") || name == QLatin1String("GMT"))) + return invalid(); + return {std::move(name), offset}; +} + +static QVector<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray &posixRule, + int startYear, int endYear, + qint64 lastTranMSecs) +{ + QVector<QTimeZonePrivate::Data> result; + + // POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" + // i.e. "std offset dst [offset],start[/time],end[/time]" + // See the section about TZ at + // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + QList<QByteArray> parts = posixRule.split(','); + + PosixZone stdZone, dstZone = PosixZone::invalid(); + { + const QByteArray &zoneinfo = parts.at(0); + const char *begin = zoneinfo.constBegin(); + + stdZone = PosixZone::parse(begin, zoneinfo.constEnd()); + if (!stdZone.hasValidOffset()) { + stdZone.offset = 0; // reset to UTC if we failed to parse + } else if (begin < zoneinfo.constEnd()) { + dstZone = PosixZone::parse(begin, zoneinfo.constEnd()); + if (!dstZone.hasValidOffset()) { + // if the dst offset isn't provided, it is 1 hour ahead of the standard offset + dstZone.offset = stdZone.offset + (60 * 60); + } + } + } + + // If only the name part then no transitions + if (parts.count() == 1) { + QTimeZonePrivate::Data data; + data.atMSecsSinceEpoch = lastTranMSecs; + data.offsetFromUtc = stdZone.offset; + data.standardTimeOffset = stdZone.offset; + data.daylightTimeOffset = 0; + data.abbreviation = stdZone.name; + result << data; + return result; + } + + + // Get the std to dst transtion details + QList<QByteArray> dstParts = parts.at(1).split('/'); + QByteArray dstDateRule = dstParts.at(0); + QTime dstTime; + if (dstParts.count() > 1) + dstTime = parsePosixTransitionTime(dstParts.at(1)); + else + dstTime = QTime(2, 0, 0); + + // Get the dst to std transtion details + QList<QByteArray> stdParts = parts.at(2).split('/'); + QByteArray stdDateRule = stdParts.at(0); + QTime stdTime; + if (stdParts.count() > 1) + stdTime = parsePosixTransitionTime(stdParts.at(1)); + else + stdTime = QTime(2, 0, 0); + + // Limit year to the range QDateTime can represent: + const int minYear = int(QDateTimePrivate::YearRange::First); + const int maxYear = int(QDateTimePrivate::YearRange::Last); + startYear = qBound(minYear, startYear, maxYear); + endYear = qBound(minYear, endYear, maxYear); + Q_ASSERT(startYear <= endYear); + + for (int year = startYear; year <= endYear; ++year) { + QTimeZonePrivate::Data dstData; + QDateTime dst(calculatePosixDate(dstDateRule, year), dstTime, Qt::UTC); + dstData.atMSecsSinceEpoch = dst.toMSecsSinceEpoch() - (stdZone.offset * 1000); + dstData.offsetFromUtc = dstZone.offset; + dstData.standardTimeOffset = stdZone.offset; + dstData.daylightTimeOffset = dstZone.offset - stdZone.offset; + dstData.abbreviation = dstZone.name; + QTimeZonePrivate::Data stdData; + QDateTime std(calculatePosixDate(stdDateRule, year), stdTime, Qt::UTC); + stdData.atMSecsSinceEpoch = std.toMSecsSinceEpoch() - (dstZone.offset * 1000); + stdData.offsetFromUtc = stdZone.offset; + stdData.standardTimeOffset = stdZone.offset; + stdData.daylightTimeOffset = 0; + stdData.abbreviation = stdZone.name; + // Part of maxYear will overflow (likewise for minYear, below): + if (year == maxYear && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) { + if (dstData.atMSecsSinceEpoch > 0) { + result << dstData; + } else if (stdData.atMSecsSinceEpoch > 0) { + result << stdData; + } + } else if (year < 1970) { // We ignore DST before the epoch. + if (year > minYear || stdData.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()) + result << stdData; + } else if (dst < std) { + result << dstData << stdData; + } else { + result << stdData << dstData; + } + } + return result; +} + +// Create the system default time zone +QTzTimeZonePrivate::QTzTimeZonePrivate() +{ + init(systemTimeZoneId()); +} + +// Create a named time zone +QTzTimeZonePrivate::QTzTimeZonePrivate(const QByteArray &ianaId) +{ + init(ianaId); +} + +QTzTimeZonePrivate::~QTzTimeZonePrivate() +{ +} + +QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const +{ + return new QTzTimeZonePrivate(*this); +} + +void QTzTimeZonePrivate::init(const QByteArray &ianaId) +{ + QFile tzif; + if (ianaId.isEmpty()) { + // Open system tz + tzif.setFileName(QStringLiteral("/etc/localtime")); + if (!tzif.open(QIODevice::ReadOnly)) + return; + } else { + // Open named tz, try modern path first, if fails try legacy path + tzif.setFileName(QLatin1String("/usr/share/zoneinfo/") + QString::fromLocal8Bit(ianaId)); + if (!tzif.open(QIODevice::ReadOnly)) { + tzif.setFileName(QLatin1String("/usr/lib/zoneinfo/") + QString::fromLocal8Bit(ianaId)); + if (!tzif.open(QIODevice::ReadOnly)) { + // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ + const QByteArray zoneInfo = ianaId.split(',').at(0); + const char *begin = zoneInfo.constBegin(); + if (PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset() + && (begin == zoneInfo.constEnd() + || PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset())) { + m_id = m_posixRule = ianaId; + } + return; + } + } + } + + QDataStream ds(&tzif); + + // Parse the old version block of data + bool ok = false; + QTzHeader hdr = parseTzHeader(ds, &ok); + if (!ok || ds.status() != QDataStream::Ok) + return; + QVector<QTzTransition> tranList = parseTzTransitions(ds, hdr.tzh_timecnt, false); + if (ds.status() != QDataStream::Ok) + return; + QVector<QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt); + if (ds.status() != QDataStream::Ok) + return; + QMap<int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList); + if (ds.status() != QDataStream::Ok) + return; + parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false); + if (ds.status() != QDataStream::Ok) + return; + typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt); + if (ds.status() != QDataStream::Ok) + return; + + // If version 2 then parse the second block of data + if (hdr.tzh_version == '2' || hdr.tzh_version == '3') { + ok = false; + QTzHeader hdr2 = parseTzHeader(ds, &ok); + if (!ok || ds.status() != QDataStream::Ok) + return; + tranList = parseTzTransitions(ds, hdr2.tzh_timecnt, true); + if (ds.status() != QDataStream::Ok) + return; + typeList = parseTzTypes(ds, hdr2.tzh_typecnt); + if (ds.status() != QDataStream::Ok) + return; + abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList); + if (ds.status() != QDataStream::Ok) + return; + parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true); + if (ds.status() != QDataStream::Ok) + return; + typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt); + if (ds.status() != QDataStream::Ok) + return; + m_posixRule = parseTzPosixRule(ds); + if (ds.status() != QDataStream::Ok) + return; + } + + // Translate the TZ file into internal format + + // Translate the array index based tz_abbrind into list index + const int size = abbrevMap.size(); + m_abbreviations.clear(); + m_abbreviations.reserve(size); + QVector<int> abbrindList; + abbrindList.reserve(size); + for (auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) { + m_abbreviations.append(it.value()); + abbrindList.append(it.key()); + } + for (int i = 0; i < typeList.size(); ++i) + typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind); + + // Offsets are stored as total offset, want to know separate UTC and DST offsets + // so find the first non-dst transition to use as base UTC Offset + int utcOffset = 0; + for (const QTzTransition &tran : qAsConst(tranList)) { + if (!typeList.at(tran.tz_typeind).tz_isdst) { + utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff; + break; + } + } + + // Now for each transition time calculate and store our rule: + const int tranCount = tranList.count();; + m_tranTimes.reserve(tranCount); + // The DST offset when in effect: usually stable, usually an hour: + int lastDstOff = 3600; + for (int i = 0; i < tranCount; i++) { + const QTzTransition &tz_tran = tranList.at(i); + QTzTransitionTime tran; + QTzTransitionRule rule; + const QTzType tz_type = typeList.at(tz_tran.tz_typeind); + + // Calculate the associated Rule + if (!tz_type.tz_isdst) { + utcOffset = tz_type.tz_gmtoff; + } else if (Q_UNLIKELY(tz_type.tz_gmtoff != utcOffset + lastDstOff)) { + /* + This might be a genuine change in DST offset, but could also be + DST starting at the same time as the standard offset changed. See + if DST's end gives a more plausible utcOffset (i.e. one closer to + the last we saw, or a simple whole hour): + */ + // Standard offset inferred from net offset and expected DST offset: + const int inferStd = tz_type.tz_gmtoff - lastDstOff; // != utcOffset + for (int j = i + 1; j < tranCount; j++) { + const QTzType new_type = typeList.at(tranList.at(j).tz_typeind); + if (!new_type.tz_isdst) { + const int newUtc = new_type.tz_gmtoff; + if (newUtc == utcOffset) { + // DST-end can't help us, avoid lots of messy checks. + // else: See if the end matches the familiar DST offset: + } else if (newUtc == inferStd) { + utcOffset = newUtc; + // else: let either end shift us to one hour as DST offset: + } else if (tz_type.tz_gmtoff - 3600 == utcOffset) { + // Start does it + } else if (tz_type.tz_gmtoff - 3600 == newUtc) { + utcOffset = newUtc; // End does it + // else: prefer whichever end gives DST offset closer to + // last, but consider any offset > 0 "closer" than any <= 0: + } else if (newUtc < tz_type.tz_gmtoff + ? (utcOffset >= tz_type.tz_gmtoff + || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd)) + : (utcOffset >= tz_type.tz_gmtoff + && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) { + utcOffset = newUtc; + } + break; + } + } + lastDstOff = tz_type.tz_gmtoff - utcOffset; + } + rule.stdOffset = utcOffset; + rule.dstOffset = tz_type.tz_gmtoff - utcOffset; + rule.abbreviationIndex = tz_type.tz_abbrind; + + // If the rule already exist then use that, otherwise add it + int ruleIndex = m_tranRules.indexOf(rule); + if (ruleIndex == -1) { + m_tranRules.append(rule); + tran.ruleIndex = m_tranRules.size() - 1; + } else { + tran.ruleIndex = ruleIndex; + } + + tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000; + m_tranTimes.append(tran); + } + if (m_tranTimes.isEmpty() && m_posixRule.isEmpty()) + return; // Invalid after all ! + + if (ianaId.isEmpty()) + m_id = systemTimeZoneId(); + else + m_id = ianaId; +} + +QLocale::Country QTzTimeZonePrivate::country() const +{ + return tzZones->value(m_id).country; +} + +QString QTzTimeZonePrivate::comment() const +{ + return QString::fromUtf8(tzZones->value(m_id).comment); +} + +QString QTzTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ +#if QT_CONFIG(icu) + if (!m_icu) + m_icu = new QIcuTimeZonePrivate(m_id); + // TODO small risk may not match if tran times differ due to outdated files + // TODO Some valid TZ names are not valid ICU names, use translation table? + if (m_icu->isValid()) + return m_icu->displayName(atMSecsSinceEpoch, nameType, locale); +#else + Q_UNUSED(nameType) + Q_UNUSED(locale) +#endif + return abbreviation(atMSecsSinceEpoch); +} + +QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ +#if QT_CONFIG(icu) + if (!m_icu) + m_icu = new QIcuTimeZonePrivate(m_id); + // TODO small risk may not match if tran times differ due to outdated files + // TODO Some valid TZ names are not valid ICU names, use translation table? + if (m_icu->isValid()) + return m_icu->displayName(timeType, nameType, locale); +#else + Q_UNUSED(timeType) + Q_UNUSED(nameType) + Q_UNUSED(locale) +#endif + // If no ICU available then have to use abbreviations instead + // Abbreviations don't have GenericTime + if (timeType == QTimeZone::GenericTime) + timeType = QTimeZone::StandardTime; + + // Get current tran, if valid and is what we want, then use it + const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch(); + QTimeZonePrivate::Data tran = data(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise get next tran and if valid and is what we want, then use it + tran = nextTransition(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise get prev tran and if valid and is what we want, then use it + tran = previousTransition(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs()) + tran = previousTransition(tran.atMSecsSinceEpoch); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise is strange sequence, so work backwards through trans looking for first match, if any + auto it = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + [currentMSecs](const QTzTransitionTime &at) { + return at.atMSecsSinceEpoch <= currentMSecs; + }); + + while (it != m_tranTimes.cbegin()) { + --it; + tran = dataForTzTransition(*it); + int offset = tran.daylightTimeOffset; + if ((timeType == QTimeZone::DaylightTime) != (offset == 0)) + return tran.abbreviation; + } + + // Otherwise if no match use current data + return data(currentMSecs).abbreviation; +} + +QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).abbreviation; +} + +int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + const QTimeZonePrivate::Data tran = data(atMSecsSinceEpoch); + return tran.offsetFromUtc; // == tran.standardTimeOffset + tran.daylightTimeOffset +} + +int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).standardTimeOffset; +} + +int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).daylightTimeOffset; +} + +bool QTzTimeZonePrivate::hasDaylightTime() const +{ + // TODO Perhaps cache as frequently accessed? + for (const QTzTransitionRule &rule : m_tranRules) { + if (rule.dstOffset != 0) + return true; + } + return false; +} + +bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + return (daylightTimeOffset(atMSecsSinceEpoch) != 0); +} + +QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran) const +{ + QTimeZonePrivate::Data data; + data.atMSecsSinceEpoch = tran.atMSecsSinceEpoch; + QTzTransitionRule rule = m_tranRules.at(tran.ruleIndex); + data.standardTimeOffset = rule.stdOffset; + data.daylightTimeOffset = rule.dstOffset; + data.offsetFromUtc = rule.stdOffset + rule.dstOffset; + data.abbreviation = QString::fromUtf8(m_abbreviations.at(rule.abbreviationIndex)); + return data; +} + +QVector<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const +{ + const int year = QDateTime::fromMSecsSinceEpoch(msNear, Qt::UTC).date().year(); + // The Data::atMSecsSinceEpoch of the single entry if zone is constant: + qint64 atTime = m_tranTimes.isEmpty() ? msNear : m_tranTimes.last().atMSecsSinceEpoch; + return calculatePosixTransitions(m_posixRule, year - 1, year + 1, atTime); +} + +QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + // If the required time is after the last transition (or there were none) + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)) { + QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch); + auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), + [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { + return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; + }); + // Use most recent, if any in the past; or the first if we have no other rules: + if (it > posixTrans.cbegin() || (m_tranTimes.isEmpty() && it < posixTrans.cend())) { + QTimeZonePrivate::Data data = *(it > posixTrans.cbegin() ? it - 1 : it); + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; + } + } + if (m_tranTimes.isEmpty()) // Only possible if !isValid() + return invalidData(); + + // Otherwise, use the rule for the most recent or first transition: + auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + [forMSecsSinceEpoch] (const QTzTransitionTime &at) { + return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; + }); + if (last > m_tranTimes.cbegin()) + --last; + Data data = dataForTzTransition(*last); + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; +} + +bool QTzTimeZonePrivate::hasTransitions() const +{ + return true; +} + +QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // If the required time is after the last transition (or there were none) + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) { + QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch); + auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), + [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { + return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; + }); + + return it == posixTrans.cend() ? invalidData() : *it; + } + + // Otherwise, if we can find a valid tran, use its rule: + auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + [afterMSecsSinceEpoch] (const QTzTransitionTime &at) { + return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; + }); + return last != m_tranTimes.cend() ? dataForTzTransition(*last) : invalidData(); +} + +QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // If the required time is after the last transition (or there were none) + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) { + QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch); + auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), + [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { + return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch; + }); + if (it > posixTrans.cbegin()) + return *--it; + // It fell between the last transition (if any) and the first of the POSIX rule: + return m_tranTimes.isEmpty() ? invalidData() : dataForTzTransition(m_tranTimes.last()); + } + + // Otherwise if we can find a valid tran then use its rule + auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + [beforeMSecsSinceEpoch] (const QTzTransitionTime &at) { + return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch; + }); + return last > m_tranTimes.cbegin() ? dataForTzTransition(*--last) : invalidData(); +} + +// TODO Could cache the value and monitor the required files for any changes +QByteArray QTzTimeZonePrivate::systemTimeZoneId() const +{ + // Check TZ env var first, if not populated try find it + QByteArray ianaId = qgetenv("TZ"); + if (!ianaId.isEmpty() && ianaId.at(0) == ':') + ianaId = ianaId.mid(1); + + // The TZ value can be ":/etc/localtime" which libc considers + // to be a "default timezone", in which case it will be read + // by one of the blocks below, so unset it here so it is not + // considered as a valid/found ianaId + if (ianaId == "/etc/localtime") + ianaId.clear(); + + // On most distros /etc/localtime is a symlink to a real file so extract name from the path + if (ianaId.isEmpty()) { + const QString path = QFile::symLinkTarget(QStringLiteral("/etc/localtime")); + if (!path.isEmpty()) { + // /etc/localtime is a symlink to the current TZ file, so extract from path + int index = path.indexOf(QLatin1String("/zoneinfo/")); + if (index != -1) + ianaId = path.mid(index + 10).toUtf8(); + } + } + + // On Debian Etch up to Jessie, /etc/localtime is a regular file while the actual name is in /etc/timezone + if (ianaId.isEmpty()) { + QFile tzif(QStringLiteral("/etc/timezone")); + if (tzif.open(QIODevice::ReadOnly)) { + // TODO QTextStream inefficient, replace later + QTextStream ts(&tzif); + if (!ts.atEnd()) + ianaId = ts.readLine().toUtf8(); + } + } + + // On some Red Hat distros /etc/localtime is real file with name held in /etc/sysconfig/clock + // in a line like ZONE="Europe/Oslo" or TIMEZONE="Europe/Oslo" + if (ianaId.isEmpty()) { + QFile tzif(QStringLiteral("/etc/sysconfig/clock")); + if (tzif.open(QIODevice::ReadOnly)) { + // TODO QTextStream inefficient, replace later + QTextStream ts(&tzif); + QString line; + while (ianaId.isEmpty() && !ts.atEnd() && ts.status() == QTextStream::Ok) { + line = ts.readLine(); + if (line.startsWith(QLatin1String("ZONE="))) { + ianaId = line.mid(6, line.size() - 7).toUtf8(); + } else if (line.startsWith(QLatin1String("TIMEZONE="))) { + ianaId = line.mid(10, line.size() - 11).toUtf8(); + } + } + } + } + + // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ: + if (ianaId.isEmpty()) { + QFile zone(QStringLiteral("/etc/TZ")); + if (zone.open(QIODevice::ReadOnly)) + ianaId = zone.readAll().trimmed(); + } + + // Give up for now and return UTC + if (ianaId.isEmpty()) + ianaId = utcQByteArray(); + + return ianaId; +} + +bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const +{ + return tzZones->contains(ianaId); +} + +QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const +{ + QList<QByteArray> result = tzZones->keys(); + std::sort(result.begin(), result.end()); + return result; +} + +QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + // TODO AnyCountry + QList<QByteArray> result; + for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) { + if (it.value().country == country) + result << it.key(); + } + std::sort(result.begin(), result.end()); + return result; +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_win.cpp b/src/corelib/time/qtimezoneprivate_win.cpp new file mode 100644 index 0000000000..1bf2366748 --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_win.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt <jlayt@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include "qdatetime.h" + +#include "qdebug.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +#ifndef Q_OS_WINRT +// The registry-based timezone backend is not available on WinRT, which falls back to equivalent APIs. +#define QT_USE_REGISTRY_TIMEZONE 1 +#endif + +/* + Private + + Windows system implementation +*/ + +#define MAX_KEY_LENGTH 255 +#define FILETIME_UNIX_EPOCH Q_UINT64_C(116444736000000000) + +// MSDN home page for Time support +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724962%28v=vs.85%29.aspx + +// For Windows XP and later refer to MSDN docs on TIME_ZONE_INFORMATION structure +// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms725481%28v=vs.85%29.aspx + +// Vista introduced support for historic data, see MSDN docs on DYNAMIC_TIME_ZONE_INFORMATION +// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms724253%28v=vs.85%29.aspx +#ifdef QT_USE_REGISTRY_TIMEZONE +static const char tzRegPath[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"; +static const char currTzRegPath[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"; +#endif + +enum { + MIN_YEAR = -292275056, + MAX_YEAR = 292278994, + MSECS_PER_DAY = 86400000, + TIME_T_MAX = 2145916799, // int maximum 2037-12-31T23:59:59 UTC + JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1) +}; + +// Copied from MSDN, see above for link +typedef struct _REG_TZI_FORMAT +{ + LONG Bias; + LONG StandardBias; + LONG DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +} REG_TZI_FORMAT; + +namespace { + +// Fast and reliable conversion from msecs to date for all values +// Adapted from QDateTime msecsToDate +QDate msecsToDate(qint64 msecs) +{ + qint64 jd = JULIAN_DAY_FOR_EPOCH; + + if (qAbs(msecs) >= MSECS_PER_DAY) { + jd += (msecs / MSECS_PER_DAY); + msecs %= MSECS_PER_DAY; + } + + if (msecs < 0) { + qint64 ds = MSECS_PER_DAY - msecs - 1; + jd -= ds / MSECS_PER_DAY; + } + + return QDate::fromJulianDay(jd); +} + +bool equalSystemtime(const SYSTEMTIME &t1, const SYSTEMTIME &t2) +{ + return (t1.wYear == t2.wYear + && t1.wMonth == t2.wMonth + && t1.wDay == t2.wDay + && t1.wDayOfWeek == t2.wDayOfWeek + && t1.wHour == t2.wHour + && t1.wMinute == t2.wMinute + && t1.wSecond == t2.wSecond + && t1.wMilliseconds == t2.wMilliseconds); +} + +bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMATION &tzi2) +{ + return(tzi1.Bias == tzi2.Bias + && tzi1.StandardBias == tzi2.StandardBias + && equalSystemtime(tzi1.StandardDate, tzi2.StandardDate) + && wcscmp(tzi1.StandardName, tzi2.StandardName) == 0 + && tzi1.DaylightBias == tzi2.DaylightBias + && equalSystemtime(tzi1.DaylightDate, tzi2.DaylightDate) + && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0); +} + +#ifdef QT_USE_REGISTRY_TIMEZONE +bool openRegistryKey(const QString &keyPath, HKEY *key) +{ + return RegOpenKeyEx(HKEY_LOCAL_MACHINE, reinterpret_cast<const wchar_t*>(keyPath.utf16()), + 0, KEY_READ, key) == ERROR_SUCCESS; +} + +QString readRegistryString(const HKEY &key, const wchar_t *value) +{ + wchar_t buffer[MAX_PATH] = {0}; + DWORD size = sizeof(wchar_t) * MAX_PATH; + RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast<LPBYTE>(buffer), &size); + return QString::fromWCharArray(buffer); +} + +int readRegistryValue(const HKEY &key, const wchar_t *value) +{ + DWORD buffer; + DWORD size = sizeof(buffer); + RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast<LPBYTE>(&buffer), &size); + return buffer; +} + +QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(const HKEY &key, + const wchar_t *value, bool *ok) +{ + *ok = false; + QWinTimeZonePrivate::QWinTransitionRule rule; + REG_TZI_FORMAT tzi; + DWORD tziSize = sizeof(tzi); + if (RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast<BYTE *>(&tzi), &tziSize) + == ERROR_SUCCESS) { + rule.startYear = 0; + rule.standardTimeBias = tzi.Bias + tzi.StandardBias; + rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias; + rule.standardTimeRule = tzi.StandardDate; + rule.daylightTimeRule = tzi.DaylightDate; + *ok = true; + } + return rule; +} + +TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *ok) +{ + *ok = false; + TIME_ZONE_INFORMATION tzi; + REG_TZI_FORMAT regTzi; + DWORD regTziSize = sizeof(regTzi); + HKEY key = NULL; + const QString tziKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') + + QString::fromUtf8(windowsId); + + if (openRegistryKey(tziKeyPath, &key)) { + + DWORD size = sizeof(tzi.DaylightName); + RegQueryValueEx(key, L"Dlt", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.DaylightName), &size); + + size = sizeof(tzi.StandardName); + RegQueryValueEx(key, L"Std", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.StandardName), &size); + + if (RegQueryValueEx(key, L"TZI", nullptr, nullptr, reinterpret_cast<BYTE *>(®Tzi), ®TziSize) + == ERROR_SUCCESS) { + tzi.Bias = regTzi.Bias; + tzi.StandardBias = regTzi.StandardBias; + tzi.DaylightBias = regTzi.DaylightBias; + tzi.StandardDate = regTzi.StandardDate; + tzi.DaylightDate = regTzi.DaylightDate; + *ok = true; + } + + RegCloseKey(key); + } + + return tzi; +} +#else // QT_USE_REGISTRY_TIMEZONE +struct QWinDynamicTimeZone +{ + QString standardName; + QString daylightName; + QString timezoneName; + qint32 bias; + bool daylightTime; +}; + +typedef QHash<QByteArray, QWinDynamicTimeZone> QWinRTTimeZoneHash; + +Q_GLOBAL_STATIC(QWinRTTimeZoneHash, gTimeZones) + +void enumerateTimeZones() +{ + DYNAMIC_TIME_ZONE_INFORMATION dtzInfo; + quint32 index = 0; + QString prevTimeZoneKeyName; + while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) { + QWinDynamicTimeZone item; + item.timezoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName); + // As soon as key name repeats, break. Some systems continue to always + // return the last item independent of index being out of range + if (item.timezoneName == prevTimeZoneKeyName) + break; + item.standardName = QString::fromWCharArray(dtzInfo.StandardName); + item.daylightName = QString::fromWCharArray(dtzInfo.DaylightName); + item.daylightTime = !dtzInfo.DynamicDaylightTimeDisabled; + item.bias = dtzInfo.Bias; + gTimeZones->insert(item.timezoneName.toUtf8(), item); + prevTimeZoneKeyName = item.timezoneName; + } +} + +DYNAMIC_TIME_ZONE_INFORMATION dynamicInfoForId(const QByteArray &windowsId) +{ + DYNAMIC_TIME_ZONE_INFORMATION dtzInfo; + quint32 index = 0; + QString prevTimeZoneKeyName; + while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) { + const QString timeZoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName); + if (timeZoneName == QLatin1String(windowsId)) + break; + if (timeZoneName == prevTimeZoneKeyName) + break; + prevTimeZoneKeyName = timeZoneName; + } + return dtzInfo; +} + +QWinTimeZonePrivate::QWinTransitionRule +readDynamicRule(DYNAMIC_TIME_ZONE_INFORMATION &dtzi, int year, bool *ok) +{ + TIME_ZONE_INFORMATION tzi; + QWinTimeZonePrivate::QWinTransitionRule rule; + *ok = GetTimeZoneInformationForYear(year, &dtzi, &tzi); + if (*ok) { + rule.startYear = 0; + rule.standardTimeBias = tzi.Bias + tzi.StandardBias; + rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias; + rule.standardTimeRule = tzi.StandardDate; + rule.daylightTimeRule = tzi.DaylightDate; + } + return rule; +} +#endif // QT_USE_REGISTRY_TIMEZONE + +bool isSameRule(const QWinTimeZonePrivate::QWinTransitionRule &last, + const QWinTimeZonePrivate::QWinTransitionRule &rule) +{ + // In particular, when this is true and either wYear is 0, so is the other; + // so if one rule is recurrent and they're equal, so is the other. If + // either rule *isn't* recurrent, it has non-0 wYear which shall be + // different from the other's. Note that we don't compare .startYear, since + // that will always be different. + return equalSystemtime(last.standardTimeRule, rule.standardTimeRule) + && equalSystemtime(last.daylightTimeRule, rule.daylightTimeRule) + && last.standardTimeBias == rule.standardTimeBias + && last.daylightTimeBias == rule.daylightTimeBias; +} + +QList<QByteArray> availableWindowsIds() +{ +#ifdef QT_USE_REGISTRY_TIMEZONE + // TODO Consider caching results in a global static, very unlikely to change. + QList<QByteArray> list; + HKEY key = NULL; + if (openRegistryKey(QString::fromUtf8(tzRegPath), &key)) { + DWORD idCount = 0; + if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS + && idCount > 0) { + for (DWORD i = 0; i < idCount; ++i) { + DWORD maxLen = MAX_KEY_LENGTH; + TCHAR buffer[MAX_KEY_LENGTH]; + if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS) + list.append(QString::fromWCharArray(buffer).toUtf8()); + } + } + RegCloseKey(key); + } + return list; +#else // QT_USE_REGISTRY_TIMEZONE + if (gTimeZones->isEmpty()) + enumerateTimeZones(); + return gTimeZones->keys(); +#endif // QT_USE_REGISTRY_TIMEZONE +} + +QByteArray windowsSystemZoneId() +{ +#ifdef QT_USE_REGISTRY_TIMEZONE + // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath + QString id; + HKEY key = NULL; + QString tziKeyPath = QString::fromUtf8(currTzRegPath); + if (openRegistryKey(tziKeyPath, &key)) { + id = readRegistryString(key, L"TimeZoneKeyName"); + RegCloseKey(key); + if (!id.isEmpty()) + return std::move(id).toUtf8(); + } + + // On XP we have to iterate over the zones until we find a match on + // names/offsets with the current data + TIME_ZONE_INFORMATION sysTzi; + GetTimeZoneInformation(&sysTzi); + bool ok = false; + const auto winIds = availableWindowsIds(); + for (const QByteArray &winId : winIds) { + if (equalTzi(getRegistryTzi(winId, &ok), sysTzi)) + return winId; + } +#else // QT_USE_REGISTRY_TIMEZONE + DYNAMIC_TIME_ZONE_INFORMATION dtzi; + if (SUCCEEDED(GetDynamicTimeZoneInformation(&dtzi))) + return QString::fromWCharArray(dtzi.TimeZoneKeyName).toLocal8Bit(); +#endif // QT_USE_REGISTRY_TIMEZONE + + // If we can't determine the current ID use UTC + return QTimeZonePrivate::utcQByteArray(); +} + +QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) +{ + // If month is 0 then there is no date + if (rule.wMonth == 0) + return QDate(); + + // Interpret SYSTEMTIME according to the slightly quirky rules in: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + + // If the year is set, the rule gives an absolute date: + if (rule.wYear) + return QDate(rule.wYear, rule.wMonth, rule.wDay); + + // Otherwise, the rule date is annual and relative: + const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek; + QDate date(year, rule.wMonth, 1); + // How many days before was last dayOfWeek before target month ? + int adjust = dayOfWeek - date.dayOfWeek(); // -6 <= adjust < 7 + if (adjust >= 0) // Ensure -7 <= adjust < 0: + adjust -= 7; + // Normally, wDay is day-within-month; but here it is 1 for the first + // of the given dayOfWeek in the month, through 4 for the fourth or ... + adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7; + date = date.addDays(adjust); + // ... 5 for the last; so back up by weeks to get within the month: + if (date.month() != rule.wMonth) { + Q_ASSERT(rule.wDay > 4); + // (Note that, with adjust < 0, date <= 28th of our target month + // is guaranteed when wDay <= 4, or after our first -7 here.) + date = date.addDays(-7); + Q_ASSERT(date.month() == rule.wMonth); + } + return date; +} + +// Converts a date/time value into msecs +inline qint64 timeToMSecs(const QDate &date, const QTime &time) +{ + return ((date.toJulianDay() - JULIAN_DAY_FOR_EPOCH) * MSECS_PER_DAY) + + time.msecsSinceStartOfDay(); +} + +qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias) +{ + // TODO Consider caching the calculated values - i.e. replace SYSTEMTIME in + // WinTransitionRule; do this in init() once and store the results. + const QDate date = calculateTransitionLocalDate(rule, year); + const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond); + if (date.isValid() && time.isValid()) + return timeToMSecs(date, time) + bias * 60000; + return QTimeZonePrivate::invalidMSecs(); +} + +struct TransitionTimePair +{ + // Transition times after the epoch, in ms: + qint64 std, dst; + // If either is invalidMSecs(), which shall then be < the other, there is no + // DST and the other describes a change in actual standard offset. + + TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule, + int year, int oldYearOffset) + // The local time in Daylight Time of the switch to Standard Time + : std(calculateTransitionForYear(rule.standardTimeRule, year, + rule.standardTimeBias + rule.daylightTimeBias)), + // The local time in Standard Time of the switch to Daylight Time + dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias)) + { + /* + Check for potential "fake DST", used by MS's APIs because the + TIME_ZONE_INFORMATION spec either expresses no transitions in the + year, or expresses a transition of each kind, even if standard time + did change in a year with no DST. We've seen year-start fake-DST + (whose offset matches prior standard offset, in which the previous + year ended); and conjecture that similar might be used at a year-end. + (This might be used for a southern-hemisphere zone, where the start of + the year usually is in DST, when applicable.) Note that, here, wDay + identifies an instance of a given day-of-week in the month, with 5 + meaning last. + + Either the alleged standardTimeRule or the alleged daylightTimeRule + may be faked; either way, the transition is actually a change to the + current standard offset; but the unfaked half of the rule contains the + useful bias data, so we have to go along with its lies. + + Example: Russia/Moscow + Format: -bias +( -stdBias, stdDate | -dstBias, dstDate ) notes + Last year of DST, 2010: 180 +( 0, 0-10-5 3:0 | 60, 0-3-5 2:0 ) normal DST + Zone change in 2011: 180 +( 0, 0-1-1 0:0 | 60 0-3-5 2:0 ) fake DST at transition + Fixed standard in 2012: 240 +( 0, 0-0-0 0:0 | 60, 0-0-0 0:0 ) standard time years + Zone change in 2014: 180 +( 0, 0-10-5 2:0 | 60, 0-1-1 0:0 ) fake DST at year-start + The last of these is missing on Win7 VMs (too old to know about it). + */ + if (rule.daylightTimeRule.wMonth == 1 && rule.daylightTimeRule.wDay == 1) { + // Fake "DST transition" at start of year producing the same offset as + // previous year ended in. + if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset) + dst = QTimeZonePrivate::invalidMSecs(); + } else if (rule.daylightTimeRule.wMonth == 12 && rule.daylightTimeRule.wDay > 3) { + // Similar, conjectured, for end of year, not changing offset. + if (rule.daylightTimeBias == 0) + dst = QTimeZonePrivate::invalidMSecs(); + } + if (rule.standardTimeRule.wMonth == 1 && rule.standardTimeRule.wDay == 1) { + // Fake "transition out of DST" at start of year producing the same + // offset as previous year ended in. + if (rule.standardTimeBias == oldYearOffset) + std = QTimeZonePrivate::invalidMSecs(); + } else if (rule.standardTimeRule.wMonth == 12 && rule.standardTimeRule.wDay > 3) { + // Similar, conjectured, for end of year, not changing offset. + if (rule.daylightTimeBias == 0) + std = QTimeZonePrivate::invalidMSecs(); + } + } + + bool fakesDst() const + { + return std == QTimeZonePrivate::invalidMSecs() + || dst == QTimeZonePrivate::invalidMSecs(); + } +}; + +int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year) +{ + int offset = rule.standardTimeBias; + // Only needed to help another TransitionTimePair work out year + 1's start + // offset; and the oldYearOffset we use only affects an alleged transition + // at the *start* of this year, so it doesn't matter if we guess wrong here: + TransitionTimePair pair(rule, year, offset); + if (pair.dst > pair.std) + offset += rule.daylightTimeBias; + return offset; +} + +QLocale::Country userCountry() +{ + const GEOID id = GetUserGeoID(GEOCLASS_NATION); + wchar_t code[3]; + const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0); + return (size == 3) ? QLocalePrivate::codeToCountry(QStringView(code, size)) + : QLocale::AnyCountry; +} + +// Index of last rule in rules with .startYear <= year: +int ruleIndexForYear(const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules, int year) +{ + if (rules.last().startYear <= year) + return rules.count() - 1; + // We don't have a rule for before the first, but the first is the best we can offer: + if (rules.first().startYear > year) + return 0; + + // Otherwise, use binary chop: + int lo = 0, hi = rules.count(); + // invariant: rules[i].startYear <= year < rules[hi].startYear + // subject to treating rules[rules.count()] as "off the end of time" + while (lo + 1 < hi) { + const int mid = (lo + hi) / 2; + // lo + 2 <= hi, so lo + 1 <= mid <= hi - 1, so lo < mid < hi + // In particular, mid < rules.count() + const int midYear = rules.at(mid).startYear; + if (midYear > year) + hi = mid; + else if (midYear < year) + lo = mid; + else // No two rules have the same startYear: + return mid; + } + return lo; +} + +} // anonymous namespace + +// Create the system default time zone +QWinTimeZonePrivate::QWinTimeZonePrivate() + : QTimeZonePrivate() +{ + init(QByteArray()); +} + +// Create a named time zone +QWinTimeZonePrivate::QWinTimeZonePrivate(const QByteArray &ianaId) + : QTimeZonePrivate() +{ + init(ianaId); +} + +QWinTimeZonePrivate::QWinTimeZonePrivate(const QWinTimeZonePrivate &other) + : QTimeZonePrivate(other), m_windowsId(other.m_windowsId), + m_displayName(other.m_displayName), m_standardName(other.m_standardName), + m_daylightName(other.m_daylightName), m_tranRules(other.m_tranRules) +{ +} + +QWinTimeZonePrivate::~QWinTimeZonePrivate() +{ +} + +QWinTimeZonePrivate *QWinTimeZonePrivate::clone() const +{ + return new QWinTimeZonePrivate(*this); +} + +void QWinTimeZonePrivate::init(const QByteArray &ianaId) +{ + if (ianaId.isEmpty()) { + m_windowsId = windowsSystemZoneId(); + m_id = systemTimeZoneId(); + } else { + m_windowsId = ianaIdToWindowsId(ianaId); + m_id = ianaId; + } + + bool badMonth = false; // Only warn once per zone, if at all. + if (!m_windowsId.isEmpty()) { +#ifdef QT_USE_REGISTRY_TIMEZONE + // Open the base TZI for the time zone + HKEY baseKey = NULL; + const QString baseKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') + + QString::fromUtf8(m_windowsId); + if (openRegistryKey(baseKeyPath, &baseKey)) { + // Load the localized names + m_displayName = readRegistryString(baseKey, L"Display"); + m_standardName = readRegistryString(baseKey, L"Std"); + m_daylightName = readRegistryString(baseKey, L"Dlt"); + // On Vista and later the optional dynamic key holds historic data + const QString dynamicKeyPath = baseKeyPath + QLatin1String("\\Dynamic DST"); + HKEY dynamicKey = NULL; + if (openRegistryKey(dynamicKeyPath, &dynamicKey)) { + // Find out the start and end years stored, then iterate over them + int startYear = readRegistryValue(dynamicKey, L"FirstEntry"); + int endYear = readRegistryValue(dynamicKey, L"LastEntry"); + for (int year = startYear; year <= endYear; ++year) { + bool ruleOk; + QWinTransitionRule rule = readRegistryRule(dynamicKey, + reinterpret_cast<LPCWSTR>(QString::number(year).utf16()), + &ruleOk); + if (ruleOk + // Don't repeat a recurrent rule: + && (m_tranRules.isEmpty() + || !isSameRule(m_tranRules.last(), rule))) { + if (!badMonth + && (rule.standardTimeRule.wMonth == 0) + != (rule.daylightTimeRule.wMonth == 0)) { + badMonth = true; + qWarning("MS registry TZ API violated its wMonth constraint;" + "this may cause mistakes for %s from %d", + ianaId.constData(), year); + } + rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year; + m_tranRules.append(rule); + } + } + RegCloseKey(dynamicKey); + } else { + // No dynamic data so use the base data + bool ruleOk; + QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk); + rule.startYear = MIN_YEAR; + if (ruleOk) + m_tranRules.append(rule); + } + RegCloseKey(baseKey); + } +#else // QT_USE_REGISTRY_TIMEZONE + if (gTimeZones->isEmpty()) + enumerateTimeZones(); + QWinRTTimeZoneHash::const_iterator it = gTimeZones->find(m_windowsId); + if (it != gTimeZones->constEnd()) { + m_displayName = it->timezoneName; + m_standardName = it->standardName; + m_daylightName = it->daylightName; + DWORD firstYear = 0; + DWORD lastYear = 0; + DYNAMIC_TIME_ZONE_INFORMATION dtzi = dynamicInfoForId(m_windowsId); + if (GetDynamicTimeZoneInformationEffectiveYears(&dtzi, &firstYear, &lastYear) + == ERROR_SUCCESS && firstYear < lastYear) { + for (DWORD year = firstYear; year <= lastYear; ++year) { + bool ok = false; + QWinTransitionRule rule = readDynamicRule(dtzi, year, &ok); + if (ok + // Don't repeat a recurrent rule + && (m_tranRules.isEmpty() + || !isSameRule(m_tranRules.last(), rule))) { + if (!badMonth + && (rule.standardTimeRule.wMonth == 0) + != (rule.daylightTimeRule.wMonth == 0)) { + badMonth = true; + qWarning("MS dynamic TZ API violated its wMonth constraint;" + "this may cause mistakes for %s from %d", + ianaId.constData(), year); + } + rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year; + m_tranRules.append(rule); + } + } + } else { + // At least try to get the non-dynamic data: + dtzi.DynamicDaylightTimeDisabled = false; + bool ok = false; + QWinTransitionRule rule = readDynamicRule(dtzi, 1970, &ok); + if (ok) { + rule.startYear = MIN_YEAR; + m_tranRules.append(rule); + } + } + } +#endif // QT_USE_REGISTRY_TIMEZONE + } + + // If there are no rules then we failed to find a windowsId or any tzi info + if (m_tranRules.size() == 0) { + m_id.clear(); + m_windowsId.clear(); + m_displayName.clear(); + } +} + +QString QWinTimeZonePrivate::comment() const +{ + return m_displayName; +} + +QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + // TODO Registry holds MUI keys, should be able to look up translations? + Q_UNUSED(locale); + + if (nameType == QTimeZone::OffsetName) { + const QWinTransitionRule &rule = + m_tranRules.at(ruleIndexForYear(m_tranRules, QDate::currentDate().year())); + int offset = rule.standardTimeBias; + if (timeType == QTimeZone::DaylightTime) + offset += rule.daylightTimeBias; + return isoOffsetFormat(offset * -60); + } + + switch (timeType) { + case QTimeZone::DaylightTime : + return m_daylightName; + case QTimeZone::GenericTime : + return m_displayName; + case QTimeZone::StandardTime : + return m_standardName; + } + return m_standardName; +} + +QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).abbreviation; +} + +int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).offsetFromUtc; +} + +int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).standardTimeOffset; +} + +int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).daylightTimeOffset; +} + +bool QWinTimeZonePrivate::hasDaylightTime() const +{ + return hasTransitions(); +} + +bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + return (data(atMSecsSinceEpoch).daylightTimeOffset != 0); +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + int year = msecsToDate(forMSecsSinceEpoch).year(); + for (int ruleIndex = ruleIndexForYear(m_tranRules, year); + ruleIndex >= 0; --ruleIndex) { + const QWinTransitionRule &rule = m_tranRules.at(ruleIndex); + // Does this rule's period include any transition at all ? + if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) { + const int endYear = qMax(rule.startYear, year - 1); + while (year >= endYear) { + const int newYearOffset = (year <= rule.startYear && ruleIndex > 0) + ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1) + : yearEndOffset(rule, year - 1); + const TransitionTimePair pair(rule, year, newYearOffset); + bool isDst = false; + if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) { + isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch; + } else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) { + isDst = true; + } else { + --year; // Try an earlier year for this rule (once). + continue; + } + return ruleToData(rule, forMSecsSinceEpoch, + isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime, + pair.fakesDst()); + } + // Fell off start of rule, try previous rule. + } else { + // No transition, no DST, use the year's standard time. + return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime); + } + if (year >= rule.startYear) + year = rule.startYear - 1; // Seek last transition in new rule. + } + // We don't have relevant data :-( + return invalidData(); +} + +bool QWinTimeZonePrivate::hasTransitions() const +{ + for (const QWinTransitionRule &rule : m_tranRules) { + if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0) + return true; + } + return false; +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + int year = msecsToDate(afterMSecsSinceEpoch).year(); + for (int ruleIndex = ruleIndexForYear(m_tranRules, year); + ruleIndex < m_tranRules.count(); ++ruleIndex) { + const QWinTransitionRule &rule = m_tranRules.at(ruleIndex); + // Does this rule's period include any transition at all ? + if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) { + if (year < rule.startYear) + year = rule.startYear; // Seek first transition in this rule. + const int endYear = ruleIndex + 1 < m_tranRules.count() + ? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2); + int newYearOffset = (year <= rule.startYear && ruleIndex > 0) + ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1) + : yearEndOffset(rule, year - 1); + while (year < endYear) { + const TransitionTimePair pair(rule, year, newYearOffset); + bool isDst = false; + Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch); // invalid is min qint64 + if (pair.std > afterMSecsSinceEpoch) { + isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch; + } else if (pair.dst > afterMSecsSinceEpoch) { + isDst = true; + } else { + newYearOffset = rule.standardTimeBias; + if (pair.dst > pair.std) + newYearOffset += rule.daylightTimeBias; + ++year; // Try a later year for this rule (once). + continue; + } + + if (isDst) + return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst()); + return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst()); + } + // Fell off end of rule, try next rule. + } // else: no transition during rule's period + } + // Apparently no transition after the given time: + return invalidData(); +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + const qint64 startOfTime = invalidMSecs() + 1; + if (beforeMSecsSinceEpoch <= startOfTime) + return invalidData(); + + int year = msecsToDate(beforeMSecsSinceEpoch).year(); + for (int ruleIndex = ruleIndexForYear(m_tranRules, year); + ruleIndex >= 0; --ruleIndex) { + const QWinTransitionRule &rule = m_tranRules.at(ruleIndex); + // Does this rule's period include any transition at all ? + if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) { + const int endYear = qMax(rule.startYear, year - 1); + while (year >= endYear) { + const int newYearOffset = (year <= rule.startYear && ruleIndex > 0) + ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1) + : yearEndOffset(rule, year - 1); + const TransitionTimePair pair(rule, year, newYearOffset); + bool isDst = false; + if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) { + isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch; + } else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) { + isDst = true; + } else { + --year; // Try an earlier year for this rule (once). + continue; + } + if (isDst) + return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst()); + return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst()); + } + // Fell off start of rule, try previous rule. + } else if (ruleIndex == 0) { + // Treat a no-transition first rule as a transition at the start of + // time, so that a scan through all rules *does* see it as the first + // rule: + return ruleToData(rule, startOfTime, QTimeZone::StandardTime, false); + } // else: no transition during rule's period + if (year >= rule.startYear) + year = rule.startYear - 1; // Seek last transition in new rule + } + // Apparently no transition before the given time: + return invalidData(); +} + +QByteArray QWinTimeZonePrivate::systemTimeZoneId() const +{ + const QLocale::Country country = userCountry(); + const QByteArray windowsId = windowsSystemZoneId(); + QByteArray ianaId; + // If we have a real country, then try get a specific match for that country + if (country != QLocale::AnyCountry) + ianaId = windowsIdToDefaultIanaId(windowsId, country); + // If we don't have a real country, or there wasn't a specific match, try the global default + if (ianaId.isEmpty()) { + ianaId = windowsIdToDefaultIanaId(windowsId); + // If no global default then probably an unknown Windows ID so return UTC + if (ianaId.isEmpty()) + return utcQByteArray(); + } + return ianaId; +} + +QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds() const +{ + QList<QByteArray> result; + const auto winIds = availableWindowsIds(); + for (const QByteArray &winId : winIds) + result += windowsIdToIanaIds(winId); + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + return result; +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule, + qint64 atMSecsSinceEpoch, + QTimeZone::TimeType type, + bool fakeDst) const +{ + Data tran = invalidData(); + tran.atMSecsSinceEpoch = atMSecsSinceEpoch; + tran.standardTimeOffset = rule.standardTimeBias * -60; + if (fakeDst) { + tran.daylightTimeOffset = 0; + tran.abbreviation = m_standardName; + // Rule may claim we're in DST when it's actually a standard time change: + if (type == QTimeZone::DaylightTime) + tran.standardTimeOffset += rule.daylightTimeBias * -60; + } else if (type == QTimeZone::DaylightTime) { + tran.daylightTimeOffset = rule.daylightTimeBias * -60; + tran.abbreviation = m_daylightName; + } else { + tran.daylightTimeOffset = 0; + tran.abbreviation = m_standardName; + } + tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset; + return tran; +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/time.pri b/src/corelib/time/time.pri new file mode 100644 index 0000000000..bacb7e875d --- /dev/null +++ b/src/corelib/time/time.pri @@ -0,0 +1,34 @@ +# Qt time / date / zone / calendar module + +HEADERS += \ + time/qdatetime.h \ + time/qdatetime_p.h + +SOURCES += time/qdatetime.cpp + +qtConfig(timezone) { + HEADERS += \ + time/qtimezone.h \ + time/qtimezoneprivate_p.h \ + time/qtimezoneprivate_data_p.h + SOURCES += \ + time/qtimezone.cpp \ + time/qtimezoneprivate.cpp + !nacl:darwin: { + SOURCES += time/qtimezoneprivate_mac.mm + } else: android:!android-embedded: { + SOURCES += time/qtimezoneprivate_android.cpp + } else: unix: { + SOURCES += time/qtimezoneprivate_tz.cpp + qtConfig(icu): SOURCES += time/qtimezoneprivate_icu.cpp + } else: qtConfig(icu): { + SOURCES += time/qtimezoneprivate_icu.cpp + } else: win32: { + SOURCES += time/qtimezoneprivate_win.cpp + } +} + +qtConfig(datetimeparser) { + HEADERS += time/qdatetimeparser_p.h + SOURCES += time/qdatetimeparser.cpp +} |