summaryrefslogtreecommitdiffstats
path: root/src/corelib/time/qdatetimeparser.cpp
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2020-04-22 17:38:37 +0300
committerAndrei Golubev <andrei.golubev@qt.io>2020-05-12 12:45:58 +0300
commitbed25fdf6065d7137fa5610e4f214e9a8f8e172b (patch)
tree6d381eb6c8bdf68dfe2a9ecf2812cfca488210cb /src/corelib/time/qdatetimeparser.cpp
parented4c1b4e90aa5cb7ce6e904667bbdd6131ce7307 (diff)
Teach QDateTimeParser some common time-zone offset formats
Fixes: QTBUG-83687 Fixes: QTBUG-83844 Pick-to: 5.15 Change-Id: Ia1c827017b93cf8277aa5a0266805d773d2d9818 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/corelib/time/qdatetimeparser.cpp')
-rw-r--r--src/corelib/time/qdatetimeparser.cpp193
1 files changed, 137 insertions, 56 deletions
diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp
index 5bd2821827..e5242b43eb 100644
--- a/src/corelib/time/qdatetimeparser.cpp
+++ b/src/corelib/time/qdatetimeparser.cpp
@@ -218,9 +218,11 @@ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const
{
const SectionNode &sn = sectionNode(s);
switch (sn.type) {
-#if QT_CONFIG(timezone)
case TimeZoneSection:
+#if QT_CONFIG(timezone)
return QTimeZone::MaxUtcOffsetSecs;
+#else
+ return +14 * 3600; // NB: copied from QTimeZone
#endif
case Hour24Section:
case Hour12Section:
@@ -263,8 +265,11 @@ int QDateTimeParser::absoluteMin(int s) const
{
const SectionNode &sn = sectionNode(s);
switch (sn.type) {
+ case TimeZoneSection:
#if QT_CONFIG(timezone)
- case TimeZoneSection: return QTimeZone::MinUtcOffsetSecs;
+ return QTimeZone::MinUtcOffsetSecs;
+#else
+ return -14 * 3600; // NB: copied from QTimeZone
#endif
case Hour24Section:
case Hour12Section:
@@ -1200,24 +1205,29 @@ QDateTimeParser::scanString(const QDateTime &defaultValue,
case TimeZoneSection:
current = &zoneOffset;
if (sect.used > 0) {
-#if QT_CONFIG(timezone) // Synchronize with what findTimeZone() found:
+ // 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());
- if (latinZone.startsWith("UTC") &&
- (latinZone.size() == 3 || latinZone.at(3) == '+' || latinZone.at(3) == '-' )) {
- timeZone = QTimeZone(sect.value);
+
+ const QStringRef 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");
+
+ if (isUtc || isUtcOffset) {
tspec = sect.value ? Qt::OffsetFromUTC : Qt::UTC;
} else {
- timeZone = QTimeZone(latinZone);
+#if QT_CONFIG(timezone)
+ timeZone = QTimeZone(zoneName.toLatin1());
tspec = timeZone.isValid()
? Qt::TimeZone
: (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime);
- }
#else
- tspec = Qt::LocalTime;
+ tspec = Qt::LocalTime;
#endif
+ }
}
break;
case Hour24Section: current = &hour; break;
@@ -1640,61 +1650,104 @@ int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex
/*!
\internal
+ Return's .value is UTC offset in seconds.
+ The caller must verify that the offset is within a valid range.
+ */
+QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringRef str) const
+{
+ const bool startsWithUtc = str.startsWith(QLatin1String("UTC"));
+ // Get rid of UTC prefix if it exists
+ if (startsWithUtc)
+ str = str.mid(3);
+
+ const bool negativeSign = str.startsWith(QLatin1Char('-'));
+ // Must start with a sign:
+ if (!negativeSign && !str.startsWith(QLatin1Char('+')))
+ return ParsedSection();
+ str = str.mid(1); // drop sign
+
+ const int colonPosition = str.indexOf(QLatin1Char(':'));
+ // Colon that belongs to offset is at most at position 2 (hh:mm)
+ bool hasColon = (colonPosition >= 0 && colonPosition < 3);
+
+ // We deal only with digits at this point (except ':'), so collect them
+ const int digits = hasColon ? colonPosition + 3 : 4;
+ int i = 0;
+ for (const int offsetLength = qMin(digits, str.size()); i < offsetLength; ++i) {
+ if (i != colonPosition && !str.at(i).isDigit())
+ break;
+ }
+ const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
+ if (hoursLength < 1)
+ return ParsedSection();
+ // Field either ends with hours or also has two digits of minutes
+ if (i < digits) {
+ // Only allow single-digit hours with UTC prefix or :mm suffix
+ if (!startsWithUtc && hoursLength != 2)
+ return ParsedSection();
+ i = hoursLength;
+ hasColon = false;
+ }
+ 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);
+ if (!isInt)
+ return ParsedSection();
+ const QStringRef minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
+ const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
+ if (!isInt)
+ return ParsedSection();
+
+ // Keep in sync with QTimeZone::maxUtcOffset hours (14 at most). Also, user
+ // could be in the middle of updating the offset (e.g. UTC+14:23) which is
+ // an intermediate state
+ const State status = (hours > 14 || minutes >= 60) ? Invalid
+ : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
+
+ int offset = 3600 * hours + 60 * minutes;
+ if (negativeSign)
+ offset = -offset;
+
+ // Used: UTC, sign, hours, colon, minutes
+ const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
+ + minutesStr.size();
+
+ return ParsedSection(status, offset, usedSymbols);
+}
+
+/*!
+ \internal
+
Return's .value is zone's offset, zone time - UTC time, in seconds.
+ The caller must verify that the offset is within a valid range.
See QTimeZonePrivate::isValidId() for the format of zone names.
*/
QDateTimeParser::ParsedSection
-QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when,
- int maxVal, int minVal) const
+QDateTimeParser::findTimeZoneName(QStringRef str, const QDateTime &when) const
{
-#if QT_CONFIG(timezone)
int index = startsWithLocalTimeZone(str);
- int offset;
+ if (index > 0) // won't actually use the offset, but need it to be valid
+ return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), index);
- 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) {
- const auto here = str[index].unicode();
- if (here < 127
- && (QChar::isLetterOrNumber(here)
- || here == '/' || here == '-'
- || here == '_' || here == '.'
- || here == '+' || here == ':'))
- index++;
- else
- break;
- }
+#if QT_CONFIG(timezone)
+ const int size = str.length();
- 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 ...
- }
+ // Collect up plausibly-valid characters; let QTimeZone work out what's
+ // truly valid.
+ for (; index < size; ++index) {
+ const QChar here = str[index];
+ if (here >= 127 || (!here.isLetterOrNumber() && !QLatin1String("/-_.+:").contains(here)))
+ break;
}
- if (index > 0 && maxVal >= offset && offset >= minVal)
- return ParsedSection(Acceptable, offset, index);
-
-#else // timezone
- Q_UNUSED(str);
- Q_UNUSED(when);
- Q_UNUSED(maxVal);
- Q_UNUSED(minVal);
+ while (index > 0) {
+ str.truncate(index);
+ QTimeZone zone(str.toLatin1());
+ if (zone.isValid())
+ return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
+ index--; // maybe we collected too much ...
+ }
#endif
return ParsedSection();
}
@@ -1702,6 +1755,34 @@ QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when,
/*!
\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
+{
+ ParsedSection section = findUtcOffset(str);
+ if (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))
+ section.state = Intermediate;
+ 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);
+
+ return ParsedSection();
+}
+
+/*!
+ \internal
+
Compares str to the am/pm texts returned by getAmPmText().
Returns AM or PM if str is one of those texts. Failing that, it looks to see
whether, ignoring spaces and case, each character of str appears in one of