diff options
Diffstat (limited to 'src/corelib/time/qdatetimeparser.cpp')
-rw-r--r-- | src/corelib/time/qdatetimeparser.cpp | 1124 |
1 files changed, 633 insertions, 491 deletions
diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index dd9e1507e4..cce32e7ad2 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -1,53 +1,20 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #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" +#include "qlocale.h" +#include "qset.h" +#include "qtimezone.h" +#include "qvarlengtharray.h" +#include "private/qlocale_p.h" + +#include "private/qstringiterator_p.h" +#include "private/qtenvironmentvariables_p.h" //#define QDATETIMEPARSER_DEBUG #if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM) @@ -60,6 +27,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + template <typename T> using ShortVector = QVarLengthArray<T, 13>; // enough for month (incl. leap) and day-of-week names @@ -79,12 +48,8 @@ QDateTimeParser::~QDateTimeParser() 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); @@ -99,22 +64,46 @@ int QDateTimeParser::getDigit(const QDateTime &t, int index) const case MonthSection: return t.date().month(calendar); case DaySection: return t.date().day(calendar); case DayOfWeekSectionShort: - case DayOfWeekSectionLong: return t.date().day(calendar); + case DayOfWeekSectionLong: return calendar.dayOfWeek(t.date()); 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 + Difference between two days of the week. + + Returns a difference in the range from -3 through +3, so that steps by small + numbers of days move us through the month in the same direction as through + the week. +*/ + +static int dayOfWeekDiff(int sought, int held) +{ + const int diff = sought - held; + return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff; +} + +static bool preferDayOfWeek(const QList<QDateTimeParser::SectionNode> &nodes) +{ + // True precisely if there is a day-of-week field but no day-of-month field. + bool result = false; + for (const auto &node : nodes) { + if (node.type & QDateTimeParser::DaySection) + return false; + if (node.type & QDateTimeParser::DayOfWeekSectionMask) + result = true; + } + return result; +} + +/*! \internal Sets a digit in a datetime. E.g. @@ -129,27 +118,24 @@ int QDateTimeParser::getDigit(const QDateTime &t, int index) const 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; } - QCalendar::YearMonthDay date = calendar.partsFromDate(v.date()); + const QDate oldDate = v.date(); + QCalendar::YearMonthDay date = calendar.partsFromDate(oldDate); if (!date.isValid()) return false; + int weekDay = calendar.dayOfWeek(oldDate); + enum { NoFix, MonthDay, WeekDay } fixDay = NoFix; const QTime time = v.time(); 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; + QTimeZone timeZone = v.timeRepresentation(); const SectionNode &node = sectionNodes.at(index); switch (node.type) { @@ -161,8 +147,6 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const case YearSection: date.year = newVal; break; case MonthSection: date.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 @@ -170,12 +154,21 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const return false; } date.day = newVal; + fixDay = MonthDay; + break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + if (newVal > 7 || newVal <= 0) + return false; + date.day += dayOfWeekDiff(newVal, weekDay); + weekDay = newVal; + fixDay = WeekDay; break; case TimeZoneSection: if (newVal < absoluteMin(index) || newVal > absoluteMax(index)) return false; - tspec = Qt::OffsetFromUTC; - offset = newVal; + // Only offset from UTC is amenable to setting an int value: + timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal); break; case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break; default: @@ -187,9 +180,27 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const if (!(node.type & DaySectionMask)) { if (date.day < cachedDay) date.day = cachedDay; + fixDay = MonthDay; + if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) { + const int max = calendar.daysInMonth(date.month, date.year); + if (max > 0 && date.day > max) + date.day = max; + const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date)); + if (newDoW > 0 && newDoW <= 7) + date.day += dayOfWeekDiff(weekDay, newDoW); + fixDay = WeekDay; + } + } + + if (fixDay != NoFix) { const int max = calendar.daysInMonth(date.month, date.year); - if (date.day > max) - date.day = max; + // max > 0 precisely if the year does have such a month + if (max > 0 && date.day > max) + date.day = fixDay == WeekDay ? date.day - 7 : max; + else if (date.day < 1) + date.day = fixDay == WeekDay ? date.day + 7 : 1; + Q_ASSERT(fixDay != WeekDay + || calendar.dayOfWeek(calendar.dateFromParts(date)) == weekDay); } const QDate newDate = calendar.dateFromParts(date); @@ -197,12 +208,7 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const 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); + v = QDateTime(newDate, newTime, timeZone); return true; } @@ -219,11 +225,7 @@ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const const SectionNode &sn = sectionNode(s); switch (sn.type) { case TimeZoneSection: -#if QT_CONFIG(timezone) return QTimeZone::MaxUtcOffsetSecs; -#else - return +14 * 3600; // NB: copied from QTimeZone -#endif case Hour24Section: case Hour12Section: // This is special-cased in parseSection. @@ -235,18 +237,19 @@ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const case MSecSection: return 999; case YearSection2Digits: - case YearSection: // sectionMaxSize will prevent people from typing in a larger number in // count == 2 sections; stepBy() will work on real years anyway. + case YearSection: return 9999; case MonthSection: return calendar.maximumMonthsInYear(); case DaySection: + return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth(); case DayOfWeekSectionShort: case DayOfWeekSectionLong: - return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth(); + return 7; case AmPmSection: - return 1; + return int(UpperCase); default: break; } @@ -266,23 +269,21 @@ int QDateTimeParser::absoluteMin(int s) const const SectionNode &sn = sectionNode(s); switch (sn.type) { case TimeZoneSection: -#if QT_CONFIG(timezone) return QTimeZone::MinUtcOffsetSecs; -#else - return -14 * 3600; // NB: copied from QTimeZone -#endif case Hour24Section: case Hour12Section: case MinuteSection: case SecondSection: case MSecSection: case YearSection2Digits: - case YearSection: return 0; + return 0; + case YearSection: + return -9999; case MonthSection: case DaySection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: return 1; - case AmPmSection: return 0; + case AmPmSection: return int(NativeCase); default: break; } qWarning("QDateTimeParser::absoluteMin() Internal error (%ls, %0x)", @@ -333,7 +334,7 @@ int QDateTimeParser::sectionPos(int sectionIndex) const return sectionPos(sectionNode(sectionIndex)); } -int QDateTimeParser::sectionPos(const SectionNode &sn) const +int QDateTimeParser::sectionPos(SectionNode sn) const { switch (sn.type) { case FirstSection: return 0; @@ -347,6 +348,22 @@ int QDateTimeParser::sectionPos(const SectionNode &sn) const return sn.pos; } +/*! + \internal + + Helper function for parseSection. +*/ + +static qsizetype digitCount(QStringView str) +{ + qsizetype digits = 0; + for (QStringIterator it(str); it.hasNext();) { + if (!QChar::isDigit(it.next())) + break; + digits++; + } + return digits; +} /*! \internal @@ -355,24 +372,23 @@ int QDateTimeParser::sectionPos(const SectionNode &sn) const not escaped and removes the escaping on those that are escaped */ - static QString unquote(QStringView str) { - const QChar quote(QLatin1Char('\'')); - const QChar slash(QLatin1Char('\\')); - const QChar zero(QLatin1Char('0')); + // ### Align unquoting format strings for both from/toString(), QTBUG-110669 + const QLatin1Char quote('\''); + const QLatin1Char slash('\\'); + const QLatin1Char zero('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) { + if (status != quote) status = quote; - } else if (!ret.isEmpty() && str.at(i - 1) == slash) { + else if (!ret.isEmpty() && str.at(i - 1) == slash) ret[ret.size() - 1] = quote; - } else { + else status = zero; - } } else { ret += str.at(i); } @@ -380,20 +396,20 @@ static QString unquote(QStringView str) return ret; } -static inline int countRepeat(const QString &str, int index, int maxCount) +static inline int countRepeat(QStringView 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; + str = str.sliced(index); + if (maxCount < str.size()) + str = str.first(maxCount); + + return qt_repeatCount(str); } -static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote) +static inline void appendSeparator(QStringList *list, QStringView string, + int from, int size, int lastQuote) { - const QStringView separator = QStringView(string).mid(from, size); + Q_ASSERT(size >= 0 && from + size <= string.size()); + const QStringView separator = string.sliced(from, size); list->append(lastQuote >= from ? unquote(separator) : separator.toString()); } @@ -403,14 +419,13 @@ static inline void appendSeparator(QStringList *list, const QString &string, int Parses the format \a newFormat. If successful, returns \c true and sets up the format. Else keeps the old format and returns \c false. */ -bool QDateTimeParser::parseFormat(const QString &newFormat) +bool QDateTimeParser::parseFormat(QStringView newFormat) { const QLatin1Char quote('\''); const QLatin1Char slash('\\'); const QLatin1Char zero('0'); - if (newFormat == displayFormat && !newFormat.isEmpty()) { + if (newFormat == displayFormat && !newFormat.isEmpty()) return true; - } QDTPDEBUGN("parseFormat: %s", newFormat.toLatin1().constData()); @@ -419,18 +434,17 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) QStringList newSeparators; int i, index = 0; int add = 0; - QChar status(zero); + QLatin1Char 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) { + if (status != quote) status = quote; - } else if (i > 0 && newFormat.at(i - 1) != slash) { + else if (i > 0 && newFormat.at(i - 1) != slash) status = zero; - } } else if (status != quote) { const char sect = newFormat.at(i).toLatin1(); switch (sect) { @@ -469,10 +483,11 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) case 'z': if (parserType != QMetaType::QDate) { - const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; + const int repeat = countRepeat(newFormat, i, 3); + const SectionNode sn = { MSecSection, i - add, repeat < 3 ? 1 : 3, 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; + i += repeat - 1; index = i + 1; newDisplay |= MSecSection; } @@ -480,15 +495,18 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) case 'A': case 'a': if (parserType != QMetaType::QDate) { - const bool cap = (sect == 'A'); - const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; - newSectionNodes.append(sn); + const int pos = i - add; + Case caseOpt = sect == 'A' ? UpperCase : LowerCase; appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); newDisplay |= AmPmSection; if (i + 1 < newFormat.size() - && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { + && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) { ++i; + if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ? 'P' : 'p')) + caseOpt = NativeCase; } + const SectionNode sn = { AmPmSection, pos, int(caseOpt), 0 }; + newSectionNodes.append(sn); index = i + 1; } break; @@ -510,7 +528,7 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) if (parserType != QMetaType::QTime) { const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; newSectionNodes.append(sn); - newSeparators.append(unquote(QStringView{newFormat}.mid(index, i - index))); + newSeparators.append(unquote(newFormat.first(i).sliced(index))); i += sn.count - 1; index = i + 1; newDisplay |= MonthSection; @@ -531,7 +549,8 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) break; case 't': if (parserType == QMetaType::QDateTime) { - const SectionNode sn = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 }; + 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; @@ -544,9 +563,8 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) } } } - if (newSectionNodes.isEmpty() && context == DateTimeEdit) { + if (newSectionNodes.isEmpty() && context == DateTimeEdit) return false; - } if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) { const int count = newSectionNodes.size(); @@ -557,13 +575,12 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) } } - if (index < max) { - appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); - } else { + if (index < max) + appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote); + else newSeparators.append(QString()); - } - displayFormat = newFormat; + displayFormat = newFormat.toString(); separators = newSeparators; sectionNodes = newSectionNodes; display = newDisplay; @@ -574,7 +591,7 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) // } QDTPDEBUG << newFormat << displayFormat; - QDTPDEBUGN("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData()); + QDTPDEBUGN("separators:\n'%s'", separators.join("\n"_L1).toLatin1().constData()); return true; } @@ -602,7 +619,7 @@ int QDateTimeParser::sectionSize(int sectionIndex) const // The size difference is always due to leading zeroes. int sizeAdjustment = 0; const int displayTextSize = displayText().size(); - if (displayTextSize != text.size()) { + if (displayTextSize != m_text.size()) { // Any zeroes added before this section will affect our size. int preceedingZeroesAdded = 0; if (sectionNodes.size() > 1 && context == DateTimeEdit) { @@ -634,13 +651,10 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const case LastSection: return 0; - case AmPmSection: { - const int lowerMax = qMax(getAmPmText(AmText, LowerCase).size(), - getAmPmText(PmText, LowerCase).size()); - const int upperMax = qMax(getAmPmText(AmText, UpperCase).size(), - getAmPmText(PmText, UpperCase).size()); - return qMax(lowerMax, upperMax); - } + case AmPmSection: + // Special: "count" here is a case flag, not field width ! + return qMax(getAmPmText(AmText, Case(count)).size(), + getAmPmText(PmText, Case(count)).size()); case Hour24Section: case Hour12Section: @@ -651,16 +665,12 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const case DayOfWeekSectionShort: case DayOfWeekSectionLong: -#if !QT_CONFIG(textdate) - return 2; -#else +#if QT_CONFIG(textdate) mcount = 7; Q_FALLTHROUGH(); #endif case MonthSection: -#if !QT_CONFIG(textdate) - return 2; -#else +#if QT_CONFIG(textdate) if (count <= 2) return 2; @@ -676,7 +686,9 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const } return ret; } -#endif +#else + return 2; +#endif // textdate case MSecSection: return 3; case YearSection: @@ -697,6 +709,7 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const case DaySectionMask: qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s", SectionNode::name(s).toLatin1().constData()); + break; case NoSectionIndex: case FirstSectionIndex: @@ -715,6 +728,36 @@ int QDateTimeParser::sectionMaxSize(int index) const return sectionMaxSize(sn.type, sn.count); } +// Separator matching +// +// QTBUG-114909: user may be oblivious to difference between visibly +// indistinguishable spacing characters. For now we only treat horizontal +// spacing characters, excluding tab, as equivalent. + +static int matchesSeparator(QStringView text, QStringView separator) +{ + const auto isSimpleSpace = [](char32_t ch) { + // Distinguish tab, CR and the vertical spaces from the rest: + return ch == u' ' || (ch > 127 && QChar::isSpace(ch)); + }; + // -1 if not a match, else length of prefix of text that does match. + // First check for exact match + if (!text.startsWith(separator)) { + // Failing that, check for space-identifying match: + QStringIterator given(text), sep(separator); + while (sep.hasNext()) { + if (!given.hasNext()) + return -1; + char32_t s = sep.next(), g = given.next(); + if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g))) + return -1; + } + // One side may have used a surrogate pair space where the other didn't: + return given.index(); + } + return separator.size(); +} + /*! \internal @@ -743,26 +786,24 @@ QString QDateTimeParser::sectionText(int sectionIndex) const return sectionText(displayText(), sectionIndex, sn.pos); } - -#if QT_CONFIG(datestring) - QDateTimeParser::ParsedSection -QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, - int offset, QString *text) const +QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, int offset) 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; - } + Q_ASSERT_X(!(sn.type & Internal), + "QDateTimeParser::parseSection", "Internal error"); const int sectionmaxsize = sectionMaxSize(sectionIndex); - QStringView sectionTextRef = QStringView{*text}.mid(offset, sectionmaxsize); + const bool negate = (sn.type == YearSection && m_text.size() > offset + && m_text.at(offset) == u'-'); + const int negativeYearOffset = negate ? 1 : 0; + + QStringView sectionTextRef = + QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize); QDTPDEBUG << "sectionValue for" << sn.name() - << "with text" << *text << "and (at" << offset + << "with text" << m_text << "and (at" << offset << ") st:" << sectionTextRef; switch (sn.type) { @@ -790,12 +831,12 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, break; } if (result.state != Invalid) - text->replace(offset, used, sectiontext.constData(), used); + m_text.replace(offset, used, sectiontext.constData(), used); break; } case TimeZoneSection: result = findTimeZone(sectionTextRef, currentValue, absoluteMax(sectionIndex), - absoluteMin(sectionIndex)); + absoluteMin(sectionIndex), sn.count); break; case MonthSection: case DayOfWeekSectionShort: @@ -814,7 +855,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, result = ParsedSection(Intermediate, num, used); if (num != -1) { - text->replace(offset, used, sectiontext.constData(), used); + m_text.replace(offset, used, sectiontext.constData(), used); if (used == sectiontext.size()) result = ParsedSection(Acceptable, num, used); } @@ -830,73 +871,96 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, 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 auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset), + negativeYearOffset, sectionIndex, this]() { + // No-digit field if next separator is here, otherwise invalid. + const auto &sep = separators.at(sectionIndex + 1); + if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1) + result = ParsedSection(Intermediate, 0, negativeYearOffset); + else if (negativeYearOffset && matchesSeparator(field, sep) != -1) + result = ParsedSection(Intermediate, 0, 0); + else + return false; + return true; + }; + int used = negativeYearOffset; + // We already sliced off the - sign if it was acceptable. + // QLocale::toUInt() would accept a sign, so we must reject it overtly: + if (sectionTextRef.startsWith(u'-') + || sectionTextRef.startsWith(u'+')) { + // However, a sign here may indicate a field with no digits, if it + // starts the next separator: + checkSeparator(); + break; + } + QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef)); + if (digitsStr.isEmpty()) { + result = ParsedSection(Intermediate, 0, used); + } else { + const QLocale loc = locale(); const int absMax = absoluteMax(sectionIndex); - QLocale loc; - bool ok = true; - int last = -1, used = -1; - - Q_ASSERT(sectiontextSize <= sectionmaxsize); - QStringView 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; + const int absMin = absoluteMin(sectionIndex); + + int lastVal = -1; + + for (; digitsStr.size(); digitsStr.chop(1)) { + bool ok = false; + int value = int(loc.toUInt(digitsStr, &ok)); + if (!ok || (negate ? -value < absMin : value > absMax)) + continue; + + if (sn.type == Hour12Section) { + if (value > 12) + continue; + if (value == 12) + value = 0; } + + QDTPDEBUG << digitsStr << value << digitsStr.size(); + lastVal = value; + used += digitsStr.size(); + 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; + if (lastVal == -1) { + if (!checkSeparator()) { + QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" + << lastVal; + } } else { + if (negate) + lastVal = -lastVal; const FieldInfo fi = fieldInfo(sectionIndex); - const bool unfilled = used < sectionmaxsize; + const bool unfilled = used - negativeYearOffset < 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; + lastVal *= 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)) { + Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax); + if (negate ? lastVal > absMax : lastVal < absMin) { + if (unfilled) { + result = ParsedSection(Intermediate, lastVal, used); + } else if (negate) { + QDTPDEBUG << "invalid because" << lastVal << "is greater than absoluteMax" + << absMax; + } else { + QDTPDEBUG << "invalid because" << lastVal << "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'))); + result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes); + m_text.insert(offset, QString(missingZeroes, u'0')); ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded); } else { - result = ParsedSection(Intermediate, last, used);; + result = ParsedSection(Intermediate, lastVal, used); } } else { - result = ParsedSection(Acceptable, last, used); + result = ParsedSection(Acceptable, lastVal, used); } } } @@ -914,21 +978,30 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, /*! \internal - Returns a day-number, in the same month as \a rough and as close to \a rough's - day number as is valid, that \a calendar puts on the day of the week indicated - by \a weekDay. + Returns the day-number of a day, as close as possible to the given \a day, in + the specified \a month of \a year for the given \a calendar, that falls on the + day of the week indicated by \a weekDay. */ -static int weekDayWithinMonth(QCalendar calendar, QDate rough, int weekDay) +static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day, int weekDay) { // TODO: can we adapt this to cope gracefully with intercallary days (day of // week > 7) without making it slower for more widely-used calendars ? - int day = rough.day(calendar) + weekDay - calendar.dayOfWeek(rough); - if (day <= 0) - return day + 7; - if (day > rough.daysInMonth(calendar)) - return day - 7; - return day; + const int maxDay = calendar.daysInMonth(month, year); // 0 if no such month + day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day); + day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar))); + return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day; +} + +/*! + \internal + Returns whichever of baseYear through baseYear + 99 has its % 100 == y2d. +*/ +static int yearInCenturyFrom(int y2d, int baseYear) +{ + Q_ASSERT(0 <= y2d && y2d < 100); + const int year = baseYear - baseYear % 100 + y2d; + return year < baseYear ? year + 100 : year; } /*! @@ -939,21 +1012,21 @@ static int weekDayWithinMonth(QCalendar calendar, QDate rough, int weekDay) when on valid date is consistent with the data. */ -static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calendar, +static QDate actualDate(QDateTimeParser::Sections known, QCalendar calendar, int baseYear, int year, int year2digits, int month, int day, int dayofweek) { QDate actual(year, month, day, calendar); if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek) return actual; // The obvious candidate is fine :-) - if (dayofweek < 1 || dayofweek > 7) // Invalid: ignore + if (dayofweek < 1 || dayofweek > 7) // Intercallary (or 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; + year = yearInCenturyFrom(year2digits, baseYear); known &= ~QDateTimeParser::YearSection; } else { year2digits = year % 100; @@ -970,16 +1043,21 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend } QDate first(year, month, 1, calendar); - int last = known & QDateTimeParser::YearSection && known & QDateTimeParser::MonthSection - ? first.daysInMonth(calendar) : 0; + int last = known & QDateTimeParser::MonthSection + ? (known.testAnyFlag(QDateTimeParser::YearSectionMask) + ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month)) + : 0; + // We can only fix DOW if we know year as well as month (hence last): + const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection + && known & QDateTimeParser::DayOfWeekSectionMask; // 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 - calendar.dayOfWeek(first) - last) % 7; + if (fixDayOfWeek) { + const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7; Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0. last += diff; } if (day < 1) { - if (known & QDateTimeParser::DayOfWeekSectionMask && last) { + if (fixDayOfWeek) { day = 1 + dayofweek - calendar.dayOfWeek(first); if (day < 1) day += 7; @@ -987,7 +1065,7 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend day = 1; } known &= ~QDateTimeParser::DaySection; - } else if (day > 31) { + } else if (day > calendar.maximumDaysInMonth()) { day = last; known &= ~QDateTimeParser::DaySection; } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) { @@ -1014,7 +1092,7 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend if ((known & QDateTimeParser::DaySection) == 0) { // Relatively easy to fix. - day = weekDayWithinMonth(calendar, actual, dayofweek); + day = weekDayWithinMonth(calendar, year, month, day, dayofweek); actual = QDate(year, month, day, calendar); return actual; } @@ -1043,20 +1121,11 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend 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, calendar); - if (calendar.dayOfWeek(actual) == dayofweek) - return actual; - actual = QDate(year - 100, month, day, calendar); - if (calendar.dayOfWeek(actual) == dayofweek) + actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek); + if (actual.isValid()) { + Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek); return actual; + } } else { // Offset by 7 is usually enough, but rare cases may need more: for (int y = 1; y < 12; y++) { @@ -1109,12 +1178,53 @@ static QTime actualTime(QDateTimeParser::Sections known, return actual; } +/* + \internal +*/ +static int startsWithLocalTimeZone(QStringView name, const QDateTime &when, const QLocale &locale) +{ + // Pick longest match that we might get. + qsizetype longest = 0; + // On MS-Win, at least when system zone is UTC, the tzname[]s may be empty. + for (int i = 0; i < 2; ++i) { + const QString zone(qTzName(i)); + if (zone.size() > longest && name.startsWith(zone)) + longest = zone.size(); + } + // Mimic each candidate QLocale::toString() could have used, to ensure round-trips work: + const auto consider = [name, &longest](QStringView zone) { + if (name.startsWith(zone)) { + // UTC-based zone's displayName() only includes seconds if non-zero: + if (9 > longest && zone.size() == 6 && zone.startsWith("UTC"_L1) + && name.sliced(6, 3) == ":00"_L1) { + longest = 9; + } else if (zone.size() > longest) { + longest = zone.size(); + } + } + }; +#if QT_CONFIG(timezone) + /* QLocale::toString would skip this if locale == QLocale::system(), but we + might not be using the same system locale as whoever generated the text + we're parsing. So consider it anyway. */ + { + const auto localWhen = QDateTime(when.date(), when.time()); + consider(localWhen.timeRepresentation().displayName( + localWhen, QTimeZone::ShortName, locale)); + } +#else + Q_UNUSED(locale); +#endif + consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation()); + Q_ASSERT(longest <= INT_MAX); // Timezone names are not that long. + return int(longest); +} + /*! \internal */ QDateTimeParser::StateNode -QDateTimeParser::scanString(const QDateTime &defaultValue, - bool fixup, QString *input) const +QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const { State state = Acceptable; bool conflicts = false; @@ -1132,26 +1242,7 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, int second = defaultTime.second(); int msec = defaultTime.msec(); int dayofweek = calendar.dayOfWeek(defaultDate); - 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; - } + QTimeZone timeZone = defaultValue.timeRepresentation(); int ampm = -1; Sections isSet = NoSection; @@ -1159,39 +1250,35 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, for (int index = 0; index < sectionNodesCount; ++index) { Q_ASSERT(state != Invalid); const QString &separator = separators.at(index); - if (QStringView{*input}.mid(pos, separator.size()) != separator) { - QDTPDEBUG << "invalid because" << QStringView{*input}.mid(pos, separator.size()) - << "!=" << separator + int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator); + if (step == -1) { + QDTPDEBUG << "invalid because" << QStringView{m_text}.sliced(pos) + << "does not start with" << separator << index << pos << currentSectionIndex; return StateNode(); } - pos += separator.size(); + pos += step; sectionNodes[index].pos = pos; int *current = nullptr; + int zoneOffset; // Needed to serve as *current when setting zone const SectionNode sn = sectionNodes.at(index); - ParsedSection sect; - - { - const QDate date = actualDate(isSet, calendar, year, year2digits, - month, day, dayofweek); + const QDateTime usedDateTime = [&] { + const QDate date = actualDate(isSet, calendar, defaultCenturyStart, + 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); - } + return QDateTime(date, time, timeZone); + }(); + ParsedSection sect = parseSection(usedDateTime, index, pos); - QDTPDEBUG << "sectionValue" << sn.name() << *input + QDTPDEBUG << "sectionValue" << sn.name() << m_text << "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); + const QString newText = QString::asprintf("%0*d", sn.count, sect.value); + m_text.replace(pos, sect.used, newText); sect.used = sn.count; } } @@ -1206,27 +1293,24 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, current = &zoneOffset; if (sect.used > 0) { // Synchronize with what findTimeZone() found: - QStringView zoneName = QStringView{*input}.mid(pos, sect.used); + QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used); Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0 - const QStringView offsetStr = zoneName.startsWith(QLatin1String("UTC")) - ? zoneName.mid(3) : zoneName; - const bool isUtcOffset = offsetStr.startsWith(QLatin1Char('+')) - || offsetStr.startsWith(QLatin1Char('-')); - const bool isUtc = zoneName == QLatin1String("Z") - || zoneName == QLatin1String("UTC"); + const QStringView offsetStr + = zoneName.startsWith("UTC"_L1) ? zoneName.sliced(3) : zoneName; + const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-'); + const bool isUtc = zoneName == "Z"_L1 || zoneName == "UTC"_L1; if (isUtc || isUtcOffset) { - tspec = sect.value ? Qt::OffsetFromUTC : Qt::UTC; - } else { + timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value); #if QT_CONFIG(timezone) - timeZone = QTimeZone(zoneName.toLatin1()); - tspec = timeZone.isValid() - ? Qt::TimeZone - : (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime); -#else - tspec = Qt::LocalTime; + } else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) { + QTimeZone namedZone = QTimeZone(zoneName.toLatin1()); + Q_ASSERT(namedZone.isValid()); + timeZone = namedZone; #endif + } else { + timeZone = QTimeZone::LocalTime; } } break; @@ -1245,71 +1329,69 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, default: qWarning("QDateTimeParser::parse Internal error (%ls)", qUtf16Printable(sn.name())); - break; + return StateNode(); } + Q_ASSERT(current); + Q_ASSERT(sect.state != Invalid); 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) { + if (index != currentSectionIndex) continue; - } } - if (sect.state != Invalid) - *current = sect.value; + *current = sect.value; // Record the present section: isSet |= sn.type; } - if (QStringView{*input}.mid(pos) != separators.last()) { - QDTPDEBUG << "invalid because" << QStringView{*input}.mid(pos) - << "!=" << separators.last() << pos; + int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last()); + if (step == -1 || step + pos < m_text.size()) { + QDTPDEBUG << "invalid because" << QStringView{m_text}.sliced(pos) + << "does not match" << separators.last() << pos; return StateNode(); } if (parserType != QMetaType::QTime) { if (year % 100 != year2digits && (isSet & YearSection2Digits)) { + const QDate date = actualDate(isSet, calendar, defaultCenturyStart, + year, year2digits, month, day, dayofweek); if (!(isSet & YearSection)) { - year = (year / 100) * 100; - year += year2digits; + year = date.year(); } else { conflicts = true; const SectionNode &sn = sectionNode(currentSectionIndex); - if (sn.type == YearSection2Digits) { - year = (year / 100) * 100; - year += year2digits; - } + if (sn.type == YearSection2Digits) + year = date.year(); } } + const auto fieldType = sectionType(currentSectionIndex); const QDate date(year, month, day, calendar); - if (dayofweek != calendar.dayOfWeek(date) + if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date)) && 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 = weekDayWithinMonth(calendar, date, dayofweek); + // Change to day of week should adjust day of month; + // when day of month isn't set, so should change to year or month. + if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask + || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) { + day = weekDayWithinMonth(calendar, year, month, day, dayofweek); QDTPDEBUG << year << month << day << dayofweek << calendar.dayOfWeek(QDate(year, month, day, calendar)); } } bool needfixday = false; - if (sectionType(currentSectionIndex) & DaySectionMask) { + if (fieldType & DaySectionMask) { cachedDay = day; - } else if (cachedDay > day) { + } else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) { day = cachedDay; needfixday = true; } @@ -1321,9 +1403,8 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, needfixday = true; } if (needfixday) { - if (context == FromString) { + if (context == FromString) return StateNode(); - } if (state == Acceptable && fixday) { day = qMin<int>(day, calendar.daysInMonth(month, year)); @@ -1331,14 +1412,14 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, 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)); + m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day)); } else if (sn.type & DayOfWeekSectionMask) { const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar)); 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); + m_text.replace(sectionPos(sn), sectionSize(i), dayName); } } } else if (state > Intermediate) { @@ -1349,26 +1430,19 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, if (parserType != QMetaType::QDate) { 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) { + const bool hasHour = isSet.testAnyFlag(Hour24Section); + if (ampm == -1) // If we don't know from hour, assume am: + ampm = !hasHour || hour < 12 ? 0 : 1; + hour12 = hour12 % 12 + ampm * 12; + if (!hasHour) hour = hour12; - } else if (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)) { + if (!(isSet & (Hour24Section))) + hour = 12 * ampm; // Special case: only ap section + else if ((ampm == 0) != (hour < 12)) conflicts = true; - } } } @@ -1377,29 +1451,42 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, const QDate date(year, month, day, calendar); 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 == QMetaType::QDateTime && !(isSet & HourSectionMask) && !when.isValid()) { - qint64 msecs = when.toMSecsSinceEpoch(); - // Fortunately, that gets a useful answer, even though when is invalid ... - 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); + const QDateTime when = QDateTime(date, time, timeZone); + + if (when.time() != time || when.date() != date) { + // In a spring-forward, if we hit the skipped hour, we may have been + // shunted out of it. + + // If hour wasn't specified, so we're using our default, changing it may + // fix that. + if (!(isSet & HourSectionMask)) { + switch (parserType) { + case QMetaType::QDateTime: { + qint64 msecs = when.toMSecsSinceEpoch(); + // Fortunately, that gets a useful answer, even though when is invalid ... + const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone); + 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); + } + } break; + case QMetaType::QDate: + // Don't care about time, so just use start of day (and ignore spec): + return StateNode(date.startOfDay(QTimeZone::UTC), + state, padding, conflicts); + break; + case QMetaType::QTime: + // Don't care about date or representation, so pick a safe representation: + return StateNode(QDateTime(date, time, QTimeZone::UTC), + state, padding, conflicts); + default: + Q_UNREACHABLE_RETURN(StateNode()); + } + } else if (state > Intermediate) { + state = Intermediate; } } @@ -1411,15 +1498,17 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, */ QDateTimeParser::StateNode -QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const +QDateTimeParser::parse(const QString &input, int position, + const QDateTime &defaultValue, bool fixup) const { const QDateTime minimum = getMinimum(); const QDateTime maximum = getMaximum(); + m_text = input; 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(), + StateNode scan = scanString(defaultValue, fixup); + QDTPDEBUGN("'%s' => '%s'(%s)", m_text.toLatin1().constData(), + scan.value.toString("yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(), stateName(scan.state).toLatin1().constData()); if (scan.value.isValid() && scan.state != Invalid) { @@ -1434,9 +1523,9 @@ QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValu 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(); + QString t = sectionText(m_text, i, sn.pos).toLower(); if ((t.size() < sectionMaxSize(i) - && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) + && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) || t.contains(space)) { switch (sn.type) { case AmPmSection: @@ -1484,9 +1573,9 @@ QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValu int toMax; if (sn.type & TimeSectionMask) { - if (scan.value.daysTo(minimum) != 0) { + if (scan.value.daysTo(minimum) != 0) break; - } + const QTime time = scan.value.time(); toMin = time.msecsTo(minimum.time()); if (scan.value.daysTo(maximum) > 0) @@ -1540,22 +1629,16 @@ QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValu Q_ASSERT(maximum.date().toJulianDay() == 5373484); if (scan.value.date().toJulianDay() > 5373484) scan.state = Invalid; - } else { - if (scan.value > maximum) - 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; - /* - We might have ended up with an invalid datetime: the non-existent hour - during dst changes, for instance. - */ - if (!scan.value.isValid() && scan.state == Acceptable) - scan.state = Intermediate; + // An invalid time should only arise if we set the state to less than acceptable: + Q_ASSERT(scan.value.isValid() || scan.state != Acceptable); return scan; } @@ -1571,7 +1654,7 @@ QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValu 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 ShortVector<QString> &entries, QString *usedText, int *used) +static int findTextEntry(QStringView text, const ShortVector<QString> &entries, QString *usedText, int *used) { if (text.isEmpty()) return -1; @@ -1608,7 +1691,7 @@ static int findTextEntry(const QString &text, const ShortVector<QString> &entrie match. Starting from \a index; str should already by lowered */ -int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex, +int QDateTimeParser::findMonth(QStringView str, int startMonth, int sectionIndex, int year, QString *usedMonth, int *used) const { const SectionNode &sn = sectionNode(sectionIndex); @@ -1624,11 +1707,11 @@ int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionI for (int month = startMonth; month <= 12; ++month) monthNames.append(calendar.monthName(l, month, year, type)); - const int index = findTextEntry(str1, monthNames, usedMonth, used); + const int index = findTextEntry(str, monthNames, usedMonth, used); return index < 0 ? index : index + startMonth; } -int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const +int QDateTimeParser::findDay(QStringView str, int startDay, int sectionIndex, QString *usedDay, int *used) const { const SectionNode &sn = sectionNode(sectionIndex); if (!(sn.type & DaySectionMask)) { @@ -1643,7 +1726,7 @@ int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex for (int day = startDay; day <= 7; ++day) daysOfWeek.append(l.dayName(day, type)); - const int index = findTextEntry(str1, daysOfWeek, usedDay, used); + const int index = findTextEntry(str, daysOfWeek, usedDay, used); return index < 0 ? index : index + startDay; } @@ -1652,21 +1735,29 @@ int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex Return's .value is UTC offset in seconds. The caller must verify that the offset is within a valid range. + The mode is 1 for permissive parsing, 2 and 3 for strict offset-only format + (no UTC prefix) with no colon for 2 and a colon for 3. */ -QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str) const +QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str, int mode) const { - const bool startsWithUtc = str.startsWith(QLatin1String("UTC")); - // Get rid of UTC prefix if it exists - if (startsWithUtc) - str = str.mid(3); + Q_ASSERT(mode > 0 && mode < 4); + const bool startsWithUtc = str.startsWith("UTC"_L1); + // Deal with UTC prefix if present: + if (startsWithUtc) { + if (mode != 1) + return ParsedSection(); + str = str.sliced(3); + if (str.isEmpty()) + return ParsedSection(Acceptable, 0, 3); + } - const bool negativeSign = str.startsWith(QLatin1Char('-')); + const bool negativeSign = str.startsWith(u'-'); // Must start with a sign: - if (!negativeSign && !str.startsWith(QLatin1Char('+'))) + if (!negativeSign && !str.startsWith(u'+')) return ParsedSection(); - str = str.mid(1); // drop sign + str = str.sliced(1); // drop sign - const int colonPosition = str.indexOf(QLatin1Char(':')); + const int colonPosition = str.indexOf(u':'); // Colon that belongs to offset is at most at position 2 (hh:mm) bool hasColon = (colonPosition >= 0 && colonPosition < 3); @@ -1688,10 +1779,12 @@ QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str) c i = hoursLength; hasColon = false; } + if (mode == (hasColon ? 2 : 3)) + return ParsedSection(); str.truncate(i); // The rest of the string is not part of the UTC offset bool isInt = false; - const int hours = str.mid(0, hoursLength).toInt(&isInt); + const int hours = str.first(hoursLength).toInt(&isInt); if (!isInt) return ParsedSection(); const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2); @@ -1726,17 +1819,35 @@ QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str) c QDateTimeParser::ParsedSection QDateTimeParser::findTimeZoneName(QStringView str, const QDateTime &when) const { - const int systemLength = startsWithLocalTimeZone(str); + const int systemLength = startsWithLocalTimeZone(str, when, locale()); #if QT_CONFIG(timezone) // Collect up plausibly-valid characters; let QTimeZone work out what's // truly valid. const auto invalidZoneNameCharacter = [] (const QChar &c) { - return c.unicode() >= 127u - || (!c.isLetterOrNumber() && !QLatin1String("+-./:_").contains(c)); + const auto cu = c.unicode(); + return cu >= 127u || !(memchr("+-./:_", char(cu), 6) || c.isLetterOrNumber()); }; int index = std::distance(str.cbegin(), std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter)); + // Limit name fragments (between slashes) to 20 characters. + // (Valid time-zone IDs are allowed up to 14 and Android has quirks up to 17.) + // Limit number of fragments to six; no known zone name has more than four. + int lastSlash = -1; + int count = 0; + Q_ASSERT(index <= str.size()); + while (lastSlash < index) { + int slash = str.indexOf(u'/', lastSlash + 1); + if (slash < 0 || slash > index) + slash = index; // i.e. the end of the candidate text + else if (++count > 5) + index = slash; // Truncate + if (slash - lastSlash > 20) + index = lastSlash + 20; // Truncate + // If any of those conditions was met, index <= slash, so this exits the loop: + lastSlash = slash; + } + for (; index > systemLength; --index) { // Find longest match str.truncate(index); QTimeZone zone(str.toLatin1()); @@ -1754,13 +1865,26 @@ QDateTimeParser::findTimeZoneName(QStringView str, const QDateTime &when) const Return's .value is zone's offset, zone time - UTC time, in seconds. See QTimeZonePrivate::isValidId() for the format of zone names. - */ + + The mode is the number of 't' characters in the field specifier: + * 1: any recognized format + * 2: only the simple offset format, without colon + * 3: only the simple offset format, with colon + * 4: only a zone name +*/ QDateTimeParser::ParsedSection QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when, - int maxVal, int minVal) const + int maxVal, int minVal, int mode) const { - ParsedSection section = findUtcOffset(str); - if (section.used <= 0) // if nothing used, try time zone parsing + Q_ASSERT(mode > 0 && mode <= 4); + // Short-cut Zulu suffix when it's all there is (rather than a prefix match): + if (mode == 1 && str == u'Z') + return ParsedSection(Acceptable, 0, 1); + + ParsedSection section; + if (mode != 4) + section = findUtcOffset(str, mode); + if (mode != 2 && mode != 3 && section.used <= 0) // if nothing used, try time zone parsing section = findTimeZoneName(str, when); // It can be a well formed time zone specifier, but with value out of range if (section.state == Acceptable && (section.value < minVal || section.value > maxVal)) @@ -1768,11 +1892,13 @@ QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when, if (section.used > 0) return section; - // Check if string is UTC or alias to UTC, after all other options - if (str.startsWith(QLatin1String("UTC"))) - return ParsedSection(Acceptable, 0, 3); - if (str.startsWith(QLatin1Char('Z'))) - return ParsedSection(Acceptable, 0, 1); + if (mode == 1) { + // Check if string is UTC or alias to UTC, after all other options + if (str.startsWith("UTC"_L1)) + return ParsedSection(Acceptable, 0, 3); + if (str.startsWith(u'Z')) + return ParsedSection(Acceptable, 0, 1); + } return ParsedSection(); } @@ -1800,9 +1926,9 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI } if (used) *used = str.size(); - if (QStringView(str).trimmed().isEmpty()) { + if (QStringView(str).trimmed().isEmpty()) return PossibleBoth; - } + const QLatin1Char space(' '); int size = sectionMaxSize(sectionIndex); @@ -1811,9 +1937,9 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI 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[amindex] = getAmPmText(AmText, Case(s.count)); + ampm[pmindex] = getAmPmText(PmText, Case(s.count)); + for (int i = 0; i < 2; ++i) ampm[i].truncate(size); QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1]; @@ -1831,20 +1957,21 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI bool broken[2] = {false, false}; for (int i=0; i<size; ++i) { - if (str.at(i) != space) { + const QChar ch = str.at(i); + if (ch != 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) + int index = ampm[j].indexOf(ch); + QDTPDEBUG << "looking for" << ch << "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() + if (ch.category() == QChar::Letter_Uppercase) { + index = ampm[j].indexOf(ch.toLower()); + QDTPDEBUG << "trying with" << ch.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() + } else if (ch.category() == QChar::Letter_Lowercase) { + index = ampm[j].indexOf(ch.toUpper()); + QDTPDEBUG << "trying with" << ch.toUpper() << "in" << ampm[j] << "and got" << index; } if (index == -1) { @@ -1867,7 +1994,6 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI return PossibleBoth; return (!broken[amindex] ? PossibleAM : PossiblePM); } -#endif // datestring /*! \internal @@ -1935,7 +2061,12 @@ QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const ret |= FixedWidth; break; case AmPmSection: - ret |= FixedWidth; + // Some locales have different length AM and PM texts. + if (getAmPmText(AmText, Case(sn.count)).size() + == getAmPmText(PmText, Case(sn.count)).size()) { + // Only relevant to DateTimeEdit's fixups in parse(). + ret |= FixedWidth; + } break; case TimeZoneSection: break; @@ -1951,18 +2082,18 @@ 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 AmPmSection: return count == 1 ? "ap"_L1 : count == 2 ? "AP"_L1 : "Ap"_L1; + case MSecSection: fillChar = u'z'; break; + case SecondSection: fillChar = u's'; break; + case MinuteSection: fillChar = u'm'; break; + case Hour24Section: fillChar = u'H'; break; + case Hour12Section: fillChar = u'h'; break; case DayOfWeekSectionShort: case DayOfWeekSectionLong: - case DaySection: fillChar = QLatin1Char('d'); break; - case MonthSection: fillChar = QLatin1Char('M'); break; + case DaySection: fillChar = u'd'; break; + case MonthSection: fillChar = u'M'; break; case YearSection2Digits: - case YearSection: fillChar = QLatin1Char('y'); break; + case YearSection: fillChar = u'y'; break; default: qWarning("QDateTimeParser::sectionFormat Internal error (%ls)", qUtf16Printable(name(type))); @@ -1986,9 +2117,9 @@ QString QDateTimeParser::SectionNode::format() const bool QDateTimeParser::potentialValue(QStringView str, int min, int max, int index, const QDateTime ¤tValue, int insert) const { - if (str.isEmpty()) { + if (str.isEmpty()) return true; - } + const int size = sectionMaxSize(index); int val = (int)locale().toUInt(str); const SectionNode &sn = sectionNode(index); @@ -1996,13 +2127,10 @@ bool QDateTimeParser::potentialValue(QStringView str, int min, int max, int inde const int year = currentValue.date().year(calendar); val += year - (year % 100); } - if (val >= min && val <= max && str.size() == size) { + if (val >= min && val <= max && str.size() == size) return true; - } else if (val > max) { + if (val > max || (str.size() == size && val < min)) return false; - } else if (str.size() == size && val < min) { - return false; - } const int len = size - str.size(); for (int i=0; i<len; ++i) { @@ -2063,23 +2191,23 @@ bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, QSt 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)); + case AmPmSection: return "AmPmSection"_L1; + case DaySection: return "DaySection"_L1; + case DayOfWeekSectionShort: return "DayOfWeekSectionShort"_L1; + case DayOfWeekSectionLong: return "DayOfWeekSectionLong"_L1; + case Hour24Section: return "Hour24Section"_L1; + case Hour12Section: return "Hour12Section"_L1; + case MSecSection: return "MSecSection"_L1; + case MinuteSection: return "MinuteSection"_L1; + case MonthSection: return "MonthSection"_L1; + case SecondSection: return "SecondSection"_L1; + case TimeZoneSection: return "TimeZoneSection"_L1; + case YearSection: return "YearSection"_L1; + case YearSection2Digits: return "YearSection2Digits"_L1; + case NoSection: return "NoSection"_L1; + case FirstSection: return "FirstSection"_L1; + case LastSection: return "LastSection"_L1; + default: return "Unknown section "_L1 + QString::number(int(s)); } } @@ -2091,53 +2219,63 @@ QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section 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); + case Invalid: return "Invalid"_L1; + case Intermediate: return "Intermediate"_L1; + case Acceptable: return "Acceptable"_L1; + default: return "Unknown state "_L1 + QString::number(s); } } -#if QT_CONFIG(datestring) -bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const + +/*! + \internal + Compute a defaultValue to pass to parse(). +*/ +QDateTime QDateTimeParser::baseDate(const QTimeZone &zone) const +{ + QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone); + if (const QDateTime start = getMinimum(); when < start) + return start; + if (const QDateTime end = getMaximum(); when > end) + return end; + return when; +} + +// Only called when we want only one of date or time; use UTC to avoid bogus DST issues. +bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time, int baseYear) const { - QDateTime datetime; - if (!fromString(t, &datetime)) + defaultCenturyStart = baseYear; + const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC), false); + if (tmp.state != Acceptable || tmp.conflicts) return false; if (time) { - const QTime t = datetime.time(); - if (!t.isValid()) { + Q_ASSERT(!date); + const QTime t = tmp.value.time(); + if (!t.isValid()) return false; - } *time = t; } if (date) { - const QDate d = datetime.date(); - if (!d.isValid()) { + Q_ASSERT(!time); + const QDate d = tmp.value.date(); + if (!d.isValid()) return false; - } *date = d; } return true; } -bool QDateTimeParser::fromString(const QString &t, QDateTime* datetime) const +// Only called when we want both date and time; default to local time. +bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime, int baseYear) 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 (datetime) { - if (!tmp.value.isValid()) - return false; + defaultCenturyStart = baseYear; + const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime), false); + if (datetime) *datetime = tmp.value; - } - - return true; + return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid(); } -#endif // datestring QDateTime QDateTimeParser::getMinimum() const { @@ -2146,7 +2284,7 @@ QDateTime QDateTimeParser::getMinimum() const // method. At the time of writing, this is done by QDateTimeEditPrivate. // Cache the only case - static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime)); + static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay()); return localTimeMin; } @@ -2157,7 +2295,7 @@ QDateTime QDateTimeParser::getMaximum() const // method. At the time of writing, this is done by QDateTimeEditPrivate. // Cache the only case - static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime)); + static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay()); return localTimeMax; } @@ -2165,16 +2303,20 @@ 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(); + switch (cs) + { + case UpperCase: return std::move(raw).toUpper(); + case LowerCase: return std::move(raw).toLower(); + case NativeCase: return raw; + } + Q_UNREACHABLE_RETURN(raw); } /* \internal - - I give arg2 preference because arg1 is always a QDateTime. */ -bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2) +bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2) { return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count); } @@ -2183,7 +2325,7 @@ bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::S Sets \a cal as the calendar to use. The default is Gregorian. */ -void QDateTimeParser::setCalendar(const QCalendar &cal) +void QDateTimeParser::setCalendar(QCalendar cal) { calendar = cal; } |