diff options
-rw-r--r-- | src/corelib/global/qnamespace.qdoc | 23 | ||||
-rw-r--r-- | src/corelib/time/qdatetime.cpp | 36 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp | 20 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtime/tst_qtime.cpp | 14 |
4 files changed, 69 insertions, 24 deletions
diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index a877b17a4f..3cddb711c5 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -709,18 +709,17 @@ fractional part suffix on the preceding field, which may be separated from that field either by a comma \c{','} or the dot \c{'.'} shown. Precision beyond milliseconds is accepted but discarded, rounding to the nearest - millisecond or, when rounding fractional seconds up would change the second - field, rounded down. The presence of a literal \c T character is used to - separate the date and time when both are specified. For the \c TextDate and - \c RFC2822Date formats, \c{ddd} stands for the first three letters of the - name of the day of the week and \c{MMM} stands for the first three letters - of the month name. The names of days and months are always in English (C - locale) regardless of user preferences or system settings. The other format - characters have the same meaning as for the ISODate format, except that 24 - is not accepted as an hour. Parts of a format enclosed in square brackets - \c{[...]} are optional; the square brackets do not form part of the - format. The plus-or-minus character \c{'±'} here stands for either sign - character, \c{'-'} for minus or \c{'+'} for plus. + representable millisecond. The presence of a literal \c T character is used + to separate the date and time when both are specified. For the \c TextDate + and \c RFC2822Date formats, \c{ddd} stands for the first three letters of + the name of the day of the week and \c{MMM} stands for the first three + letters of the month name. The names of days and months are always in + English (C locale) regardless of user preferences or system settings. The + other format characters have the same meaning as for the ISODate format, + except that 24 is not accepted as an hour. Parts of a format enclosed in + square brackets \c{[...]} are optional; the square brackets do not form part + of the format. The plus-or-minus character \c{'±'} here stands for either + sign character, \c{'-'} for minus or \c{'+'} for plus. \sa QDate::toString(), QTime::toString(), QDateTime::toString(), QDate::fromString(), QTime::fromString(), QDateTime::fromString() diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index da3fee5b81..96932ff129 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -2113,6 +2113,7 @@ int QTime::msecsTo(QTime t) const static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool *isMidnight24) { + Q_ASSERT(format == Qt::TextDate || format == Qt::ISODate || format == Qt::ISODateWithMs); if (isMidnight24) *isMidnight24 = false; // Match /\d\d(:\d\d(:\d\d)?)?([,.]\d+)?/ as "HH[:mm[:ss]][.zzz]" @@ -2145,14 +2146,14 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * return QTime(); ParsedInt hour = readInt(string.first(2)); - if (!hour.ok) + if (!hour.ok || hour.value > (format == Qt::TextDate ? 23 : 24)) return QTime(); ParsedInt minute; if (string.size() > 2) { if (string[2] == u':' && string.size() > 4) minute = readInt(string.sliced(3, 2)); - if (!minute.ok) + if (!minute.ok || minute.value >= 60) return QTime(); } else if (format == Qt::TextDate) { // Requires minutes return QTime(); @@ -2167,7 +2168,7 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * if (string.size() > 5) { if (string[5] == u':' && string.size() == 8) second = readInt(string.sliced(6, 2)); - if (!second.ok) + if (!second.ok || second.value >= 60) return QTime(); } else if (frac.ok) { if (format == Qt::TextDate) // Doesn't allow fraction of minutes @@ -2179,13 +2180,32 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * } Q_ASSERT(!(fraction < 0.0) && fraction < 1.0); - // Round millis to nearest (unlike minutes and seconds, rounded down), - // but clip to 999 (historical behavior): - const int msec = frac.ok ? qMin(qRound(1000 * fraction), 999) : 0; + // Round millis to nearest (unlike minutes and seconds, rounded down): + int msec = frac.ok ? qRound(1000 * fraction) : 0; + // But handle overflow gracefully: + if (msec == 1000) { + // If we can (when data were otherwise valid) validly propagate overflow + // into other fields, do so: + if (isMidnight24 || hour.value < 23 || minute.value < 59 || second.value < 59) { + msec = 0; + if (++second.value == 60) { + second.value = 0; + if (++minute.value == 60) { + minute.value = 0; + ++hour.value; + // May need to propagate further via isMidnight24, see below + } + } + } else { + // QTime::fromString() or Qt::TextDate: rounding up would cause + // 23:59:59.999... to become invalid; clip to 999 ms instead: + msec = 999; + } + } // For ISO date format, 24:0:0 means 0:0:0 on the next day: - if ((format == Qt::ISODate || format == Qt::ISODateWithMs) - && hour.value == 24 && minute.value == 0 && second.value == 0 && msec == 0) { + if (hour.value == 24 && minute.value == 0 && second.value == 0 && msec == 0) { + Q_ASSERT(format != Qt::TextDate); // It clipped hour at 23, above. if (isMidnight24) *isMidnight24 = true; hour.value = 0; diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index 18b7a9d74f..2e2b5fbf24 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -2202,6 +2202,15 @@ void tst_QDateTime::fromStringDateFormat_data() << Qt::TextDate << QDateTime(); QTest::newRow("text second fraction") << QString::fromLatin1("Mon 6. May 2013 01:02:03.456") << Qt::TextDate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 456)); + QTest::newRow("text max milli") + << QString::fromLatin1("Mon 6. May 2013 01:02:03.999499999") + << Qt::TextDate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 999)); + QTest::newRow("text milli wrap") + << QString::fromLatin1("Mon 6. May 2013 01:02:03.9995") + << Qt::TextDate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 4)); + QTest::newRow("text last milli") // Special case, don't round up to invalid: + << QString::fromLatin1("Mon 6. May 2013 23:59:59.9999999999") + << Qt::TextDate << QDateTime(QDate(2013, 5, 6), QTime(23, 59, 59, 999)); const QDateTime ref(QDate(1974, 12, 1), QTime(13, 2)); QTest::newRow("day:,:month") @@ -2331,6 +2340,17 @@ void tst_QDateTime::fromStringDateFormat_data() << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 333), Qt::LocalTime); QTest::newRow("ISO .00009 of a second (period)") << QString::fromLatin1("2012-01-01T08:00:00.00009") << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + QTest::newRow("ISO second fraction") << QString::fromLatin1("2013-05-06T01:02:03.456") + << Qt::ISODate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 456)); + QTest::newRow("ISO max milli") + << QString::fromLatin1("2013-05-06T01:02:03.999499999") + << Qt::ISODate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 999)); + QTest::newRow("ISO milli wrap") + << QString::fromLatin1("2013-05-06T01:02:03.9995") + << Qt::ISODate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 4)); + QTest::newRow("ISO last milli") // Does round up and overflow into new day: + << QString::fromLatin1("2013-05-06T23:59:59.9999999999") + << Qt::ISODate << QDate(2013, 5, 7).startOfDay(); QTest::newRow("ISO no fraction specified") << QString::fromLatin1("2012-01-01T08:00:00.") << Qt::ISODate << QDateTime(); // Test invalid characters (should ignore invalid characters at end of string). diff --git a/tests/auto/corelib/time/qtime/tst_qtime.cpp b/tests/auto/corelib/time/qtime/tst_qtime.cpp index b79cb609e7..ce8a24515b 100644 --- a/tests/auto/corelib/time/qtime/tst_qtime.cpp +++ b/tests/auto/corelib/time/qtime/tst_qtime.cpp @@ -588,10 +588,12 @@ void tst_QTime::fromStringDateFormat_data() << QString("10:12:34") << Qt::TextDate << QTime(10, 12, 34); QTest::newRow("TextDate - milli-max") << QString("19:03:54.998601") << Qt::TextDate << QTime(19, 3, 54, 999); - QTest::newRow("TextDate - milli-no-overflow") - << QString("19:03:54.999601") << Qt::TextDate << QTime(19, 3, 54, 999); + QTest::newRow("TextDate - milli-wrap") + << QString("19:03:54.999601") << Qt::TextDate << QTime(19, 3, 55); QTest::newRow("TextDate - no-secs") << QString("10:12") << Qt::TextDate << QTime(10, 12); + QTest::newRow("TextDate - midnight-nowrap") + << QString("23:59:59.9999") << Qt::TextDate << QTime(23, 59, 59, 999); QTest::newRow("TextDate - invalid, minutes") << QString::fromLatin1("23:XX:00") << Qt::TextDate << invalidTime(); QTest::newRow("TextDate - invalid, minute fraction") << QString::fromLatin1("23:00.123456") << Qt::TextDate << invalidTime(); QTest::newRow("TextDate - invalid, seconds") << QString::fromLatin1("23:00:XX") << Qt::TextDate << invalidTime(); @@ -601,6 +603,8 @@ void tst_QTime::fromStringDateFormat_data() QTest::newRow("IsoDate - valid, start of day, omit seconds") << QString::fromLatin1("00:00") << Qt::ISODate << QTime(0, 0, 0); QTest::newRow("IsoDate - valid, omit seconds") << QString::fromLatin1("22:21") << Qt::ISODate << QTime(22, 21, 0); + QTest::newRow("IsoDate - minute fraction") // 60 * 0.816666 = 48.99996 should round up: + << QString::fromLatin1("22:21.816666") << Qt::ISODate << QTime(22, 21, 49); QTest::newRow("IsoDate - valid, omit seconds (2)") << QString::fromLatin1("23:59") << Qt::ISODate << QTime(23, 59, 0); QTest::newRow("IsoDate - valid, end of day") << QString::fromLatin1("23:59:59") << Qt::ISODate << QTime(23, 59, 59); @@ -619,8 +623,10 @@ void tst_QTime::fromStringDateFormat_data() QTest::newRow("IsoDate - ordinary") << QString("10:12:34") << Qt::ISODate << QTime(10, 12, 34); QTest::newRow("IsoDate - milli-max") << QString("19:03:54.998601") << Qt::ISODate << QTime(19, 3, 54, 999); - QTest::newRow("IsoDate - milli-no-overflow") - << QString("19:03:54.999601") << Qt::ISODate << QTime(19, 3, 54, 999); + QTest::newRow("IsoDate - milli-wrap") + << QString("19:03:54.999601") << Qt::ISODate << QTime(19, 3, 55); + QTest::newRow("IsoDate - midnight-nowrap") + << QString("23:59:59.9999") << Qt::ISODate << QTime(23, 59, 59, 999); QTest::newRow("IsoDate - midnight 24") << QString("24:00:00") << Qt::ISODate << QTime(0, 0); QTest::newRow("IsoDate - minute fraction midnight") |