diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 1060f5a678..2a4b65a81f 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -2263,9 +2263,13 @@ QString QLocale::dateTimeFormat(FormatType format) const /*! \since 4.4 - Parses the time string given in \a string and returns the - time. The format of the time string is chosen according to the - \a format parameter (see timeFormat()). + Reads \a string as a time in a locale-specific \a format. + + Parses \a string and returns the time it represents. The format of the time + string is chosen according to the \a format parameter (see timeFormat()). + + \note Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the time could not be parsed, returns an invalid time. @@ -2279,9 +2283,13 @@ QTime QLocale::toTime(const QString &string, FormatType format) const /*! \since 4.4 - Parses the date string given in \a string and returns the - date. The format of the date string is chosen according to the - \a format parameter (see dateFormat()). + Reads \a string as a date in a locale-specific \a format. + + Parses \a string and returns the date it represents. The format of the date + string is chosen according to the \a format parameter (see dateFormat()). + + \note Month and day names, where used, must be given in the locale's + language. If the date could not be parsed, returns an invalid date. @@ -2304,9 +2312,15 @@ QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) c /*! \since 4.4 - Parses the date/time string given in \a string and returns the - time. The format of the date/time string is chosen according to the - \a format parameter (see dateTimeFormat()). + Reads \a string as a date-time in a locale-specific \a format. + + Parses \a string and returns the date-time it represents. The format of the + date string is chosen according to the \a format parameter (see + dateFormat()). + + \note Month and day names, where used, must be given in the locale's + language. Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the string could not be parsed, returns an invalid QDateTime. @@ -2329,9 +2343,13 @@ QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalenda /*! \since 4.4 - Parses the time string given in \a string and returns the - time. See QTime::fromString() for information on what is a valid - format string. + Reads \a string as a time in the given \a format. + + Parses \a string and returns the time it represents. See QTime::fromString() + for the interpretation of \a format. + + \note Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the time could not be parsed, returns an invalid time. @@ -2355,12 +2373,13 @@ QTime QLocale::toTime(const QString &string, const QString &format) const /*! \since 4.4 - Parses the date string given in \a string and returns the - date. See QDate::fromString() for information on the expressions - that can be used with this function. + Reads \a string as a date in the given \a format. - This function searches month names and the names of the days of - the week in the current locale. + Parses \a string and returns the date it represents. See QDate::fromString() + for the interpretation of \a format. + + \note Month and day names, where used, must be given in the locale's + language. If the date could not be parsed, returns an invalid date. @@ -2394,12 +2413,14 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca /*! \since 4.4 - Parses the date/time string given in \a string and returns the - time. See QDateTime::fromString() for information on the expressions - that can be used with this function. + Reads \a string as a date-time in the given \a format. - \note The month and day names used must be given in the user's local - language. + Parses \a string and returns the date-time it represents. See + QDateTime::fromString() for the interpretation of \a format. + + \note Month and day names, where used, must be given in the locale's + language. Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the string could not be parsed, returns an invalid QDateTime. If the string can be parsed and represents an invalid date-time (e.g. in a gap @@ -3356,19 +3377,21 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & } break; - case 'a': - used = true; - repeat = format.mid(i + 1).startsWith(QLatin1Char('p')) ? 2 : 1; - result.append(time.hour() < 12 ? locale.amText().toLower() - : locale.pmText().toLower()); - break; - case 'A': + case 'a': { + QString text = time.hour() < 12 ? locale.amText() : locale.pmText(); used = true; - repeat = format.mid(i + 1).startsWith(QLatin1Char('P')) ? 2 : 1; - result.append(time.hour() < 12 ? locale.amText().toUpper() - : locale.pmText().toUpper()); + repeat = 1; + if (format.mid(i + 1).startsWith(QLatin1Char('p'), Qt::CaseInsensitive)) + ++repeat; + if (c.unicode() == 'A' && (repeat == 1 || format.at(i + 1).unicode() == 'P')) + text = std::move(text).toUpper(); + else if (c.unicode() == 'a' && (repeat == 1 || format.at(i + 1).unicode() == 'p')) + text = std::move(text).toLower(); + // else 'Ap' or 'aP' => use CLDR text verbatim, preserving case + result.append(text); break; + } case 'z': used = true; diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 61a1ca9273..7ee3865611 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -1135,9 +1135,8 @@ QString QDate::toString(Qt::DateFormat format) const If the datetime is invalid, an empty string will be returned. - \note Day and month names are given in English (C locale). - If localized month and day names are desired, use - QLocale::system().toString(). + \note Day and month names are given in English (C locale). To get localized + month and day names, use QLocale::system().toString(). \sa fromString(), QDateTime::toString(), QTime::toString(), QLocale::toString() @@ -1544,9 +1543,8 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format) minus sign for negative years. \endtable - \note Day and month names must be given in English (C locale). - If localized month and day names are used, use - QLocale::system().toDate(). + \note Day and month names must be given in English (C locale). If localized + month and day names are to be recognized, use QLocale::system().toDate(). All other input characters will be treated as text. Any non-empty sequence of characters enclosed in single quotes will also be treated (stripped of @@ -1886,9 +1884,19 @@ QString QTime::toString(Qt::DateFormat format) const \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 'AM' or 'PM' + \li Use AM/PM display. \c A/AP will be replaced by 'AM' or 'PM'. In + localized forms (only relevant to \l{QLocale::toString()}), the + locale-appropriate text is converted to upper-case. \row \li ap or a - \li Use am/pm display. \e a/ap will be replaced by 'am' or 'pm' + \li Use am/pm display. \c a/ap will be replaced by 'am' or 'pm'. In + localized forms (only relevant to \l{QLocale::toString()}), the + locale-appropriate text is converted to lower-case. + \row \li aP or Ap + \li Use AM/PM display (since 6.3). \c aP/Ap will be replaced by 'AM' or + 'PM'. In localized forms (only relevant to + \l{QLocale::toString()}), the locale-appropriate text (returned by + \l{QLocale::amText()} or \l{QLocaleie:pmText()}) is used without + change of case. \row \li t \li The timezone (for example "CEST") \endtable @@ -1903,8 +1911,7 @@ QString QTime::toString(Qt::DateFormat format) const 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}) + Example format strings (assuming that the QTime is 14:13:09.042) \table \header \li Format \li Result @@ -1915,8 +1922,8 @@ QString QTime::toString(Qt::DateFormat format) const If the time is invalid, an empty string will be returned. - \note If localized forms of am or pm (the AP, ap, A or a formats) are - desired, please use QLocale::system().toString(). + \note To get localized forms of AM or PM (the AP, ap, A, a, aP or Ap + formats), use QLocale::system().toString(). \sa fromString(), QDate::toString(), QDateTime::toString(), QLocale::toString() */ @@ -2277,10 +2284,9 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format) 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 A/AP will match 'AM' or 'PM'. - \row \li ap or a - \li Interpret as an am/pm time. \e a/ap will match 'am' or 'pm'. + \row \li AP, A, ap, a, aP or Ap + \li Either 'AM' indicating a time before 12:00 or 'PM' for later times, + matched case-insensitively. \endtable All other input characters will be treated as text. Any non-empty sequence @@ -2304,11 +2310,11 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format) \snippet code/src_corelib_time_qdatetime.cpp 8 - \note If localized forms of am or pm (the AP, ap, A or a formats) are used, - please use QLocale::system().toTime(). + \note If localized forms of am or pm (the AP, ap, Ap, aP, A or a formats) + are to be recognized, use QLocale::system().toTime(). \sa toString(), QDateTime::fromString(), QDate::fromString(), - QLocale::toTime() + QLocale::toTime(), QLocale::toDateTime() */ /*! @@ -4327,9 +4333,9 @@ QString QDateTime::toString(Qt::DateFormat format) const If the datetime is invalid, an empty string will be returned. - \note Day and month names as well as AM/PM indication are given in English (C locale). - If localized month and day names and localized forms of AM/PM are used, use - QLocale::system().toDateTime(). + \note Day and month names as well as AM/PM indicators are given in English + (C locale). To get localized month and day names and localized forms of + AM/PM, use QLocale::system().toDateTime(). \sa fromString(), QDate::toString(), QTime::toString(), QLocale::toString() */ @@ -5235,9 +5241,9 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format) \snippet code/src_corelib_time_qdatetime.cpp 21 - \note Day and month names as well as AM/PM indication must be given in English (C locale). - If localized month and day names and localized forms of AM/PM are used, use - QLocale::system().toDateTime(). + \note Day and month names as well as AM/PM indicators must be given in + English (C locale). If localized month and day names or localized forms of + AM/PM are to be recognized, use QLocale::system().toDateTime(). \sa toString(), QDate::fromString(), QTime::fromString(), QLocale::toDateTime() diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index e699bdeb24..4904ac3b44 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -292,7 +292,7 @@ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const case DayOfWeekSectionLong: return 7; case AmPmSection: - return 1; + return int(UpperCase); default: break; } @@ -330,7 +330,7 @@ int QDateTimeParser::absoluteMin(int s) const 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)", @@ -545,15 +545,19 @@ bool QDateTimeParser::parseFormat(QStringView 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(QLatin1Char('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; @@ -699,8 +703,8 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const case AmPmSection: // Special: "count" here is a case flag, not field width ! - return qMax(getAmPmText(AmText, count ? UpperCase : LowerCase).size(), - getAmPmText(PmText, count ? UpperCase : LowerCase).size()); + return qMax(getAmPmText(AmText, Case(count)).size(), + getAmPmText(PmText, Case(count)).size()); case Hour24Section: case Hour12Section: @@ -1910,9 +1914,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]; @@ -2034,8 +2038,8 @@ QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const break; case AmPmSection: // Some locales have different length AM and PM texts. - if (getAmPmText(AmText, sn.count ? UpperCase : LowerCase).size() - == getAmPmText(PmText, sn.count ? UpperCase : LowerCase).size()) { + if (getAmPmText(AmText, Case(sn.count)).size() + == getAmPmText(PmText, Case(sn.count)).size()) { // Only relevant to DateTimeEdit's fixups in parse(). ret |= FixedWidth; } @@ -2054,7 +2058,7 @@ QString QDateTimeParser::SectionNode::format() const { QChar fillChar; switch (type) { - case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap"); + case AmPmSection: return QLatin1String(count == 1 ? "ap" : count == 2 ? "AP" : "Ap"); case MSecSection: fillChar = QLatin1Char('z'); break; case SecondSection: fillChar = QLatin1Char('s'); break; case MinuteSection: fillChar = QLatin1Char('m'); break; @@ -2260,7 +2264,14 @@ 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 raw.toUpper(); + case LowerCase: return raw.toLower(); + case NativeCase: return raw; + } + Q_UNREACHABLE(); + return raw; } /* diff --git a/src/corelib/time/qdatetimeparser_p.h b/src/corelib/time/qdatetimeparser_p.h index 6f44c34949..a5b20c5e35 100644 --- a/src/corelib/time/qdatetimeparser_p.h +++ b/src/corelib/time/qdatetimeparser_p.h @@ -140,7 +140,7 @@ public: struct Q_CORE_EXPORT SectionNode { Section type; mutable int pos; - int count; + int count; // (used as Case(count) indicator for AmPmSection) int zeroesAdded; static QString name(Section s); @@ -170,11 +170,6 @@ public: PmText }; - enum Case { - UpperCase, - LowerCase - }; - StateNode parse(const QString &input, int position, const QDateTime &defaultValue, bool fixup) const; bool fromString(const QString &text, QDate *date, QTime *time) const; @@ -239,6 +234,14 @@ private: return potentialValue(QStringView(str), min, max, index, currentValue, insert); } + enum Case { + NativeCase, + LowerCase, + UpperCase + }; + + QString getAmPmText(AmPm ap, Case cs) const; + friend class QDTPUnitTestParser; protected: // for the benefit of QDateTimeEditPrivate @@ -262,7 +265,6 @@ protected: // for the benefit of QDateTimeEditPrivate return skipToNextSection(section, current, QStringView(sectionText)); } QString stateName(State s) const; - QString getAmPmText(AmPm ap, Case cs) const; virtual QDateTime getMinimum() const; virtual QDateTime getMaximum() const; diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index de72c0a4c4..31f9bd9d33 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -1675,18 +1675,27 @@ void tst_QLocale::formatTime_data() << QTime(14, 2, 3) << "en_US" << "h:m:s ap" << "2:2:3 pm"; QTest::newRow("en_US-H:m:s+AP-pm") << QTime(14, 2, 3) << "en_US" << "H:m:s AP" << "14:2:3 PM"; + QTest::newRow("en_US-H:m:s+Ap-pm") + << QTime(14, 2, 3) << "en_US" << "H:m:s Ap" << "14:2:3 PM"; QTest::newRow("en_US-h:m:s+ap-am") << QTime(1, 2, 3) << "en_US" << "h:m:s ap" << "1:2:3 am"; QTest::newRow("en_US-H:m:s+AP-am") << QTime(1, 2, 3) << "en_US" << "H:m:s AP" << "1:2:3 AM"; + QTest::newRow("en_US-H:m:s+aP-am") + << QTime(1, 2, 3) << "en_US" << "H:m:s aP" << "1:2:3 AM"; + QTest::newRow("cs_CZ-h:m:s+ap-pm") << QTime(14, 2, 3) << "cs_CZ" << "h:m:s ap" << "2:2:3 odp."; QTest::newRow("cs_CZ-h:m:s+AP-pm") << QTime(14, 2, 3) << "cs_CZ" << "h:m:s AP" << "2:2:3 ODP."; + QTest::newRow("cs_CZ-h:m:s+Ap-pm") + << QTime(14, 2, 3) << "cs_CZ" << "h:m:s Ap" << "2:2:3 odp."; QTest::newRow("cs_CZ-h:m:s+ap-am") << QTime(1, 2, 3) << "cs_CZ" << "h:m:s ap" << "1:2:3 dop."; QTest::newRow("cs_CZ-h:m:s+AP-am") << QTime(1, 2, 3) << "cs_CZ" << "h:m:s AP" << "1:2:3 DOP."; + QTest::newRow("cs_CZ-h:m:s+aP-am") + << QTime(1, 2, 3) << "cs_CZ" << "h:m:s aP" << "1:2:3 dop."; } void tst_QLocale::formatTime() @@ -1810,7 +1819,7 @@ void tst_QLocale::formatDateTime_data() << QString("31-apAP12-1999 23:59:59.999"); QTest::newRow("datetime3") << "en_US" << testLongHour << QString("Apdd-MM-yyyy hh:mm:ss.zzz") - << QString("PMp31-12-1999 11:59:59.999"); + << QString("PM31-12-1999 11:59:59.999"); QTest::newRow("datetime4") << "en_US" << testLongHour << QString("'ap'apdd-MM-yyyy 'AP'hh:mm:ss.zzz") << QString("appm31-12-1999 AP11:59:59.999"); @@ -1986,6 +1995,14 @@ void tst_QLocale::toDateTime_data() << "en_US" << QDateTime(QDate(2009, 1, 5), QTime(11, 48, 32)) << "dddd, MMMM d, yyyy h:mm:ss AP " << "Monday, January 5, 2009 11:48:32 AM " << true; + // Parsing am/pm indicators case-insensitively: + QTest::newRow("am-cs_CZ") + << "cs_CZ" << QDateTime(QDate(1945, 8, 6), QTime(8, 15, 44, 400)) + << "yyyy-MM-dd hh:mm:ss.z aP" << "1945-08-06 08:15:44.4 dOp." << true; + QTest::newRow("pm-cs_CZ") + << "cs_CZ" << QDateTime(QDate(1945, 8, 15), QTime(12, 0)) + << "yyyy-MM-dd hh:mm aP" << "1945-08-15 12:00 OdP." << true; + const QDateTime dt(QDate(2017, 02, 25), QTime(17, 21, 25)); // These formats correspond to the locale formats, with the timezone removed. // We hardcode them in case an update to the locale DB changes them. diff --git a/tests/auto/corelib/time/qtime/tst_qtime.cpp b/tests/auto/corelib/time/qtime/tst_qtime.cpp index 3626f61ac7..480fbcc14f 100644 --- a/tests/auto/corelib/time/qtime/tst_qtime.cpp +++ b/tests/auto/corelib/time/qtime/tst_qtime.cpp @@ -553,11 +553,18 @@ void tst_QTime::fromStringFormat_data() QTest::newRow("data7") << QString("25") << QString("hh") << invalidTime(); QTest::newRow("data8") << QString("22pm") << QString("Hap") << QTime(22, 0, 0); QTest::newRow("data9") << QString("2221") << QString("hhhh") << invalidTime(); - QTest::newRow("data10") << QString("02:23PM") << QString("hh:mmAP") << QTime(14,23,0,0); - QTest::newRow("data11") << QString("02:23pm") << QString("hh:mmap") << QTime(14,23,0,0); - QTest::newRow("short-msecs-lt100") << QString("10:12:34:045") << QString("hh:m:ss:z") << QTime(10,12,34,45); - QTest::newRow("short-msecs-gt100") << QString("10:12:34:45") << QString("hh:m:ss:z") << QTime(10,12,34,450); - QTest::newRow("late") << QString("23:59:59.999") << QString("hh:mm:ss.z") << QTime(23, 59, 59, 999); + // Parsing of am/pm indicators is case-insensitive + QTest::newRow("pm-upper") << QString("02:23PM") << QString("hh:mmAp") << QTime(14, 23); + QTest::newRow("pm-lower") << QString("02:23pm") << QString("hh:mmaP") << QTime(14, 23); + QTest::newRow("pm-as-upper") << QString("02:23Pm") << QString("hh:mmAP") << QTime(14, 23); + QTest::newRow("pm-as-lower") << QString("02:23pM") << QString("hh:mmap") << QTime(14, 23); + // Millisecond parsing must interpolate 0s only at the end and notice them at the start. + QTest::newRow("short-msecs-lt100") + << QString("10:12:34:045") << QString("hh:m:ss:z") << QTime(10, 12, 34, 45); + QTest::newRow("short-msecs-gt100") + << QString("10:12:34:45") << QString("hh:m:ss:z") << QTime(10, 12, 34, 450); + QTest::newRow("late") + << QString("23:59:59.999") << QString("hh:mm:ss.z") << QTime(23, 59, 59, 999); // Test unicode handling. QTest::newRow("emoji in format string 1")