File indexing completed on 2024-05-12 12:41:36
0001 /**************************************************************************** 0002 ** 0003 ** SPDX-FileCopyrightText: 2016 The Qt Company Ltd. 0004 ** Contact: https://www.qt.io/licensing/ 0005 ** 0006 ** This file is part of the QtCore module of the Qt Toolkit. 0007 ** 0008 ** SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KFQF-Accepted-GPL OR LicenseRef-Qt-Commercial 0009 ** 0010 ****************************************************************************/ 0011 0012 #include "qplatformdefs.h" 0013 #include "qdatetimeparser_p.h" 0014 0015 #include <QBasicMutex> 0016 #include <QDataStream> 0017 #include <QDateTime> 0018 #include <QDebug> 0019 #include <QSet> 0020 #include <QTimeZone> 0021 0022 #include <mutex> 0023 0024 //#define QDATETIMEPARSER_DEBUG 0025 #if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM) 0026 # define QDTPDEBUG qDebug() 0027 # define QDTPDEBUGN qDebug 0028 #else 0029 # define QDTPDEBUG if (false) qDebug() 0030 # define QDTPDEBUGN if (false) qDebug 0031 #endif 0032 0033 static QBasicMutex environmentMutex; 0034 0035 QT_BEGIN_NAMESPACE 0036 0037 QDateTimeParser::~QDateTimeParser() 0038 { 0039 } 0040 0041 /*! 0042 \internal 0043 Gets the digit from a datetime. E.g. 0044 0045 QDateTime var(QDate(2004, 02, 02)); 0046 int digit = getDigit(var, Year); 0047 // digit = 2004 0048 */ 0049 0050 int QDateTimeParser::getDigit(const QDateTime &t, int index) const 0051 { 0052 if (index < 0 || index >= sectionNodes.size()) { 0053 #if QT_CONFIG(datestring) 0054 qWarning("QDateTimeParser::getDigit() Internal error (%s %d)", 0055 qPrintable(t.toString()), index); 0056 #else 0057 qWarning("QDateTimeParser::getDigit() Internal error (%d)", index); 0058 #endif 0059 return -1; 0060 } 0061 const SectionNode &node = sectionNodes.at(index); 0062 switch (node.type) { 0063 case TimeZoneSection: return t.offsetFromUtc(); 0064 case Hour24Section: case Hour12Section: return t.time().hour(); 0065 case MinuteSection: return t.time().minute(); 0066 case SecondSection: return t.time().second(); 0067 case MSecSection: return t.time().msec(); 0068 case YearSection2Digits: 0069 case YearSection: return t.date().year(); 0070 case MonthSection: return t.date().month(); 0071 case DaySection: return t.date().day(); 0072 case DayOfWeekSectionShort: 0073 case DayOfWeekSectionLong: return t.date().day(); 0074 case AmPmSection: return t.time().hour() > 11 ? 1 : 0; 0075 0076 default: break; 0077 } 0078 0079 #if QT_CONFIG(datestring) 0080 qWarning("QDateTimeParser::getDigit() Internal error 2 (%s %d)", 0081 qPrintable(t.toString()), index); 0082 #else 0083 qWarning("QDateTimeParser::getDigit() Internal error 2 (%d)", index); 0084 #endif 0085 return -1; 0086 } 0087 0088 /*! 0089 \internal 0090 Sets a digit in a datetime. E.g. 0091 0092 QDateTime var(QDate(2004, 02, 02)); 0093 int digit = getDigit(var, Year); 0094 // digit = 2004 0095 setDigit(&var, Year, 2005); 0096 digit = getDigit(var, Year); 0097 // digit = 2005 0098 */ 0099 0100 bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const 0101 { 0102 if (index < 0 || index >= sectionNodes.size()) { 0103 #if QT_CONFIG(datestring) 0104 qWarning("QDateTimeParser::setDigit() Internal error (%s %d %d)", 0105 qPrintable(v.toString()), index, newVal); 0106 #else 0107 qWarning("QDateTimeParser::setDigit() Internal error (%d %d)", index, newVal); 0108 #endif 0109 return false; 0110 } 0111 const SectionNode &node = sectionNodes.at(index); 0112 0113 const QDate date = v.date(); 0114 const QTime time = v.time(); 0115 int year = date.year(); 0116 int month = date.month(); 0117 int day = date.day(); 0118 int hour = time.hour(); 0119 int minute = time.minute(); 0120 int second = time.second(); 0121 int msec = time.msec(); 0122 Qt::TimeSpec tspec = v.timeSpec(); 0123 // Only offset from UTC is amenable to setting an int value: 0124 int offset = tspec == Qt::OffsetFromUTC ? v.offsetFromUtc() : 0; 0125 0126 switch (node.type) { 0127 case Hour24Section: case Hour12Section: hour = newVal; break; 0128 case MinuteSection: minute = newVal; break; 0129 case SecondSection: second = newVal; break; 0130 case MSecSection: msec = newVal; break; 0131 case YearSection2Digits: 0132 case YearSection: year = newVal; break; 0133 case MonthSection: month = newVal; break; 0134 case DaySection: 0135 case DayOfWeekSectionShort: 0136 case DayOfWeekSectionLong: 0137 if (newVal > 31) { 0138 // have to keep legacy behavior. setting the 0139 // date to 32 should return false. Setting it 0140 // to 31 for february should return true 0141 return false; 0142 } 0143 day = newVal; 0144 break; 0145 case TimeZoneSection: 0146 if (newVal < absoluteMin(index) || newVal > absoluteMax(index)) 0147 return false; 0148 tspec = Qt::OffsetFromUTC; 0149 offset = newVal; 0150 break; 0151 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break; 0152 default: 0153 qWarning("QDateTimeParser::setDigit() Internal error (%s)", 0154 qPrintable(node.name())); 0155 break; 0156 } 0157 0158 if (!(node.type & DaySectionMask)) { 0159 if (day < cachedDay) 0160 day = cachedDay; 0161 const int max = QDate(year, month, 1).daysInMonth(); 0162 if (day > max) { 0163 day = max; 0164 } 0165 } 0166 0167 const QDate newDate(year, month, day); 0168 const QTime newTime(hour, minute, second, msec); 0169 if (!newDate.isValid() || !newTime.isValid()) 0170 return false; 0171 0172 // Preserve zone: 0173 v = 0174 #if QT_CONFIG(timezone) 0175 tspec == Qt::TimeZone ? QDateTime(newDate, newTime, v.timeZone()) : 0176 #endif 0177 QDateTime(newDate, newTime, tspec, offset); 0178 return true; 0179 } 0180 0181 0182 0183 /*! 0184 \internal 0185 0186 Returns the absolute maximum for a section 0187 */ 0188 0189 int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const 0190 { 0191 const SectionNode &sn = sectionNode(s); 0192 switch (sn.type) { 0193 #if QT_CONFIG(timezone) 0194 case TimeZoneSection: return QTimeZone::MaxUtcOffsetSecs; 0195 #endif 0196 case Hour24Section: 0197 case Hour12Section: return 23; // this is special-cased in 0198 // parseSection. We want it to be 0199 // 23 for the stepBy case. 0200 case MinuteSection: 0201 case SecondSection: return 59; 0202 case MSecSection: return 999; 0203 case YearSection2Digits: 0204 case YearSection: return 9999; // sectionMaxSize will prevent 0205 // people from typing in a larger 0206 // number in count == 2 sections. 0207 // stepBy() will work on real years anyway 0208 case MonthSection: return 12; 0209 case DaySection: 0210 case DayOfWeekSectionShort: 0211 case DayOfWeekSectionLong: return cur.isValid() ? cur.date().daysInMonth() : 31; 0212 case AmPmSection: return 1; 0213 default: break; 0214 } 0215 qWarning("QDateTimeParser::absoluteMax() Internal error (%s)", 0216 qPrintable(sn.name())); 0217 return -1; 0218 } 0219 0220 /*! 0221 \internal 0222 0223 Returns the absolute minimum for a section 0224 */ 0225 0226 int QDateTimeParser::absoluteMin(int s) const 0227 { 0228 const SectionNode &sn = sectionNode(s); 0229 switch (sn.type) { 0230 #if QT_CONFIG(timezone) 0231 case TimeZoneSection: return QTimeZone::MinUtcOffsetSecs; 0232 #endif 0233 case Hour24Section: 0234 case Hour12Section: 0235 case MinuteSection: 0236 case SecondSection: 0237 case MSecSection: 0238 case YearSection2Digits: 0239 case YearSection: return 0; 0240 case MonthSection: 0241 case DaySection: 0242 case DayOfWeekSectionShort: 0243 case DayOfWeekSectionLong: return 1; 0244 case AmPmSection: return 0; 0245 default: break; 0246 } 0247 qWarning("QDateTimeParser::absoluteMin() Internal error (%s, %0x)", 0248 qPrintable(sn.name()), sn.type); 0249 return -1; 0250 } 0251 0252 /*! 0253 \internal 0254 0255 Returns the sectionNode for the Section \a s. 0256 */ 0257 0258 const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(int sectionIndex) const 0259 { 0260 if (sectionIndex < 0) { 0261 switch (sectionIndex) { 0262 case FirstSectionIndex: 0263 return first; 0264 case LastSectionIndex: 0265 return last; 0266 case NoSectionIndex: 0267 return none; 0268 } 0269 } else if (sectionIndex < sectionNodes.size()) { 0270 return sectionNodes.at(sectionIndex); 0271 } 0272 0273 qWarning("QDateTimeParser::sectionNode() Internal error (%d)", 0274 sectionIndex); 0275 return none; 0276 } 0277 0278 QDateTimeParser::Section QDateTimeParser::sectionType(int sectionIndex) const 0279 { 0280 return sectionNode(sectionIndex).type; 0281 } 0282 0283 0284 /*! 0285 \internal 0286 0287 Returns the starting position for section \a s. 0288 */ 0289 0290 int QDateTimeParser::sectionPos(int sectionIndex) const 0291 { 0292 return sectionPos(sectionNode(sectionIndex)); 0293 } 0294 0295 int QDateTimeParser::sectionPos(const SectionNode &sn) const 0296 { 0297 switch (sn.type) { 0298 case FirstSection: return 0; 0299 case LastSection: return displayText().size() - 1; 0300 default: break; 0301 } 0302 if (sn.pos == -1) { 0303 qWarning("QDateTimeParser::sectionPos Internal error (%s)", qPrintable(sn.name())); 0304 return -1; 0305 } 0306 return sn.pos; 0307 } 0308 0309 0310 /*! 0311 \internal 0312 0313 helper function for parseFormat. removes quotes that are 0314 not escaped and removes the escaping on those that are escaped 0315 0316 */ 0317 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0318 static QString unquote(const QStringRef &str) 0319 #else 0320 static QString unquote(const QStringView &str) 0321 #endif 0322 { 0323 const QChar quote(QLatin1Char('\'')); 0324 const QChar slash(QLatin1Char('\\')); 0325 const QChar zero(QLatin1Char('0')); 0326 QString ret; 0327 QChar status(zero); 0328 const int max = str.size(); 0329 for (int i=0; i<max; ++i) { 0330 if (str.at(i) == quote) { 0331 if (status != quote) { 0332 status = quote; 0333 } else if (!ret.isEmpty() && str.at(i - 1) == slash) { 0334 ret[ret.size() - 1] = quote; 0335 } else { 0336 status = zero; 0337 } 0338 } else { 0339 ret += str.at(i); 0340 } 0341 } 0342 return ret; 0343 } 0344 /*! 0345 \internal 0346 0347 Parses the format \a newFormat. If successful, returns \c true and 0348 sets up the format. Else keeps the old format and returns \c false. 0349 0350 */ 0351 0352 static inline int countRepeat(const QString &str, int index, int maxCount) 0353 { 0354 int count = 1; 0355 const QChar ch(str.at(index)); 0356 const int max = qMin(index + maxCount, str.size()); 0357 while (index + count < max && str.at(index + count) == ch) { 0358 ++count; 0359 } 0360 return count; 0361 } 0362 0363 static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote) 0364 { 0365 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0366 const QStringRef separator = string.midRef(from, size); 0367 #else 0368 const auto separator = QStringView(string).mid(from, size); 0369 #endif 0370 list->append(lastQuote >= from ? unquote(separator) : separator.toString()); 0371 } 0372 0373 0374 bool QDateTimeParser::parseFormat(const QString &newFormat) 0375 { 0376 const QLatin1Char quote('\''); 0377 const QLatin1Char slash('\\'); 0378 const QLatin1Char zero('0'); 0379 if (newFormat == displayFormat && !newFormat.isEmpty()) { 0380 return true; 0381 } 0382 0383 QDTPDEBUGN("parseFormat: %s", newFormat.toLatin1().constData()); 0384 0385 QVector<SectionNode> newSectionNodes; 0386 Sections newDisplay = NoSection; 0387 QStringList newSeparators; 0388 int index = 0; 0389 int add = 0; 0390 QChar status(zero); 0391 const int max = newFormat.size(); 0392 int lastQuote = -1; 0393 for (int i = 0; i<max; ++i) { 0394 if (newFormat.at(i) == quote) { 0395 lastQuote = i; 0396 ++add; 0397 if (status != quote) { 0398 status = quote; 0399 } else if (i > 0 && newFormat.at(i - 1) != slash) { 0400 status = zero; 0401 } 0402 } else if (status != quote) { 0403 const char sect = newFormat.at(i).toLatin1(); 0404 switch (sect) { 0405 case 'H': 0406 case 'h': 0407 if (parserType != QMetaType::QDate) { 0408 const Section hour = (sect == 'h') ? Hour12Section : Hour24Section; 0409 const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 }; 0410 newSectionNodes.append(sn); 0411 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0412 i += sn.count - 1; 0413 index = i + 1; 0414 newDisplay |= hour; 0415 } 0416 break; 0417 case 'm': 0418 if (parserType != QMetaType::QDate) { 0419 const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 }; 0420 newSectionNodes.append(sn); 0421 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0422 i += sn.count - 1; 0423 index = i + 1; 0424 newDisplay |= MinuteSection; 0425 } 0426 break; 0427 case 's': 0428 if (parserType != QMetaType::QDate) { 0429 const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 }; 0430 newSectionNodes.append(sn); 0431 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0432 i += sn.count - 1; 0433 index = i + 1; 0434 newDisplay |= SecondSection; 0435 } 0436 break; 0437 0438 case 'z': 0439 if (parserType != QMetaType::QDate) { 0440 const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; 0441 newSectionNodes.append(sn); 0442 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0443 i += sn.count - 1; 0444 index = i + 1; 0445 newDisplay |= MSecSection; 0446 } 0447 break; 0448 case 'A': 0449 case 'a': 0450 if (parserType != QMetaType::QDate) { 0451 const bool cap = (sect == 'A'); 0452 const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; 0453 newSectionNodes.append(sn); 0454 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0455 newDisplay |= AmPmSection; 0456 if (i + 1 < newFormat.size() 0457 && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { 0458 ++i; 0459 } 0460 index = i + 1; 0461 } 0462 break; 0463 case 'y': 0464 if (parserType != QMetaType::QTime) { 0465 const int repeat = countRepeat(newFormat, i, 4); 0466 if (repeat >= 2) { 0467 const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits, 0468 i - add, repeat == 4 ? 4 : 2, 0 }; 0469 newSectionNodes.append(sn); 0470 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0471 i += sn.count - 1; 0472 index = i + 1; 0473 newDisplay |= sn.type; 0474 } 0475 } 0476 break; 0477 case 'M': 0478 if (parserType != QMetaType::QTime) { 0479 const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; 0480 newSectionNodes.append(sn); 0481 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0482 newSeparators.append(unquote(newFormat.midRef(index, i - index))); 0483 #else 0484 newSeparators.append(unquote(QStringView(newFormat).mid(index, i - index))); 0485 #endif 0486 i += sn.count - 1; 0487 index = i + 1; 0488 newDisplay |= MonthSection; 0489 } 0490 break; 0491 case 'd': 0492 if (parserType != QMetaType::QTime) { 0493 const int repeat = countRepeat(newFormat, i, 4); 0494 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong 0495 : (repeat == 3 ? DayOfWeekSectionShort : DaySection)); 0496 const SectionNode sn = { sectionType, i - add, repeat, 0 }; 0497 newSectionNodes.append(sn); 0498 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0499 i += sn.count - 1; 0500 index = i + 1; 0501 newDisplay |= sn.type; 0502 } 0503 break; 0504 case 't': 0505 if (parserType != QMetaType::QTime) { 0506 const SectionNode sn = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 }; 0507 newSectionNodes.append(sn); 0508 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); 0509 i += sn.count - 1; 0510 index = i + 1; 0511 newDisplay |= TimeZoneSection; 0512 } 0513 break; 0514 default: 0515 break; 0516 } 0517 } 0518 } 0519 if (newSectionNodes.isEmpty() && context == DateTimeEdit) { 0520 return false; 0521 } 0522 0523 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) { 0524 const int count = newSectionNodes.size(); 0525 for (int i = 0; i < count; ++i) { 0526 SectionNode &node = newSectionNodes[i]; 0527 if (node.type == Hour12Section) 0528 node.type = Hour24Section; 0529 } 0530 } 0531 0532 if (index < max) { 0533 appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); 0534 } else { 0535 newSeparators.append(QString()); 0536 } 0537 0538 displayFormat = newFormat; 0539 separators = newSeparators; 0540 sectionNodes = newSectionNodes; 0541 display = newDisplay; 0542 last.pos = -1; 0543 0544 // for (int i=0; i<sectionNodes.size(); ++i) { 0545 // QDTPDEBUG << sectionNodes.at(i).name() << sectionNodes.at(i).count; 0546 // } 0547 0548 QDTPDEBUG << newFormat << displayFormat; 0549 QDTPDEBUGN("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData()); 0550 0551 return true; 0552 } 0553 0554 /*! 0555 \internal 0556 0557 Returns the size of section \a s. 0558 */ 0559 0560 int QDateTimeParser::sectionSize(int sectionIndex) const 0561 { 0562 if (sectionIndex < 0) 0563 return 0; 0564 0565 if (sectionIndex >= sectionNodes.size()) { 0566 qWarning("QDateTimeParser::sectionSize Internal error (%d)", sectionIndex); 0567 return -1; 0568 } 0569 0570 if (sectionIndex == sectionNodes.size() - 1) { 0571 // In some cases there is a difference between displayText() and text. 0572 // e.g. when text is 2000/01/31 and displayText() is "2000/2/31" - text 0573 // is the previous value and displayText() is the new value. 0574 // The size difference is always due to leading zeroes. 0575 int sizeAdjustment = 0; 0576 const int displayTextSize = displayText().size(); 0577 if (displayTextSize != text.size()) { 0578 // Any zeroes added before this section will affect our size. 0579 int preceedingZeroesAdded = 0; 0580 if (sectionNodes.size() > 1 && context == DateTimeEdit) { 0581 const auto begin = sectionNodes.cbegin(); 0582 const auto end = begin + sectionIndex; 0583 for (auto sectionIt = begin; sectionIt != end; ++sectionIt) 0584 preceedingZeroesAdded += sectionIt->zeroesAdded; 0585 } 0586 sizeAdjustment = preceedingZeroesAdded; 0587 } 0588 0589 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size(); 0590 } else { 0591 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex) 0592 - separators.at(sectionIndex + 1).size(); 0593 } 0594 } 0595 0596 0597 int QDateTimeParser::sectionMaxSize(Section s, int count) const 0598 { 0599 #if QT_CONFIG(textdate) 0600 int mcount = 12; 0601 #endif 0602 0603 switch (s) { 0604 case FirstSection: 0605 case NoSection: 0606 case LastSection: return 0; 0607 0608 case AmPmSection: { 0609 const int lowerMax = qMin(getAmPmText(AmText, LowerCase).size(), 0610 getAmPmText(PmText, LowerCase).size()); 0611 const int upperMax = qMin(getAmPmText(AmText, UpperCase).size(), 0612 getAmPmText(PmText, UpperCase).size()); 0613 return qMin(4, qMin(lowerMax, upperMax)); 0614 } 0615 0616 case Hour24Section: 0617 case Hour12Section: 0618 case MinuteSection: 0619 case SecondSection: 0620 case DaySection: return 2; 0621 case DayOfWeekSectionShort: 0622 case DayOfWeekSectionLong: 0623 #if !QT_CONFIG(textdate) 0624 return 2; 0625 #else 0626 mcount = 7; 0627 Q_FALLTHROUGH(); 0628 #endif 0629 case MonthSection: 0630 #if !QT_CONFIG(textdate) 0631 return 2; 0632 #else 0633 if (count <= 2) 0634 return 2; 0635 0636 { 0637 int ret = 0; 0638 const QLocale l = locale(); 0639 const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; 0640 for (int i=1; i<=mcount; ++i) { 0641 const QString str = (s == MonthSection 0642 ? l.monthName(i, format) 0643 : l.dayName(i, format)); 0644 ret = qMax(str.size(), ret); 0645 } 0646 return ret; 0647 } 0648 #endif 0649 case MSecSection: return 3; 0650 case YearSection: return 4; 0651 case YearSection2Digits: return 2; 0652 // Arbitrarily many tokens (each up to 14 bytes) joined with / separators: 0653 case TimeZoneSection: return std::numeric_limits<int>::max(); 0654 0655 case CalendarPopupSection: 0656 case Internal: 0657 case TimeSectionMask: 0658 case DateSectionMask: 0659 case HourSectionMask: 0660 case YearSectionMask: 0661 case DayOfWeekSectionMask: 0662 case DaySectionMask: 0663 qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s", 0664 SectionNode::name(s).toLatin1().constData()); 0665 0666 case NoSectionIndex: 0667 case FirstSectionIndex: 0668 case LastSectionIndex: 0669 case CalendarPopupIndex: 0670 // these cases can't happen 0671 break; 0672 } 0673 return -1; 0674 } 0675 0676 0677 int QDateTimeParser::sectionMaxSize(int index) const 0678 { 0679 const SectionNode &sn = sectionNode(index); 0680 return sectionMaxSize(sn.type, sn.count); 0681 } 0682 0683 /*! 0684 \internal 0685 0686 Returns the text of section \a s. This function operates on the 0687 arg text rather than edit->text(). 0688 */ 0689 0690 0691 QString QDateTimeParser::sectionText(const QString &text, int sectionIndex, int index) const 0692 { 0693 const SectionNode &sn = sectionNode(sectionIndex); 0694 switch (sn.type) { 0695 case NoSectionIndex: 0696 case FirstSectionIndex: 0697 case LastSectionIndex: 0698 return QString(); 0699 default: break; 0700 } 0701 0702 return text.mid(index, sectionSize(sectionIndex)); 0703 } 0704 0705 QString QDateTimeParser::sectionText(int sectionIndex) const 0706 { 0707 const SectionNode &sn = sectionNode(sectionIndex); 0708 return sectionText(displayText(), sectionIndex, sn.pos); 0709 } 0710 0711 0712 #if QT_CONFIG(datestring) 0713 0714 QDateTimeParser::ParsedSection 0715 QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, 0716 int offset, QString *text) const 0717 { 0718 ParsedSection result; // initially Invalid 0719 const SectionNode &sn = sectionNode(sectionIndex); 0720 if (sn.type & Internal) { 0721 qWarning("QDateTimeParser::parseSection Internal error (%s %d)", 0722 qPrintable(sn.name()), sectionIndex); 0723 return result; 0724 } 0725 0726 const int sectionmaxsize = sectionMaxSize(sectionIndex); 0727 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0728 QStringRef sectionTextRef = text->midRef(offset, sectionmaxsize); 0729 #else 0730 QStringView sectionTextRef = QStringView(*text).mid(offset, sectionmaxsize); 0731 #endif 0732 0733 QDTPDEBUG << "sectionValue for" << sn.name() 0734 << "with text" << *text << "and (at" << offset 0735 << ") st:" << sectionTextRef; 0736 0737 switch (sn.type) { 0738 case AmPmSection: { 0739 QString sectiontext = sectionTextRef.toString(); 0740 int used; 0741 const int ampm = findAmPm(sectiontext, sectionIndex, &used); 0742 switch (ampm) { 0743 case AM: // sectiontext == AM 0744 case PM: // sectiontext == PM 0745 result = ParsedSection(Acceptable, ampm, used); 0746 break; 0747 case PossibleAM: // sectiontext => AM 0748 case PossiblePM: // sectiontext => PM 0749 result = ParsedSection(Intermediate, ampm - 2, used); 0750 break; 0751 case PossibleBoth: // sectiontext => AM|PM 0752 result = ParsedSection(Intermediate, 0, used); 0753 break; 0754 case Neither: 0755 QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1"; 0756 break; 0757 default: 0758 QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm); 0759 break; 0760 } 0761 if (result.state != Invalid) 0762 text->replace(offset, used, sectiontext.constData(), used); 0763 break; } 0764 case TimeZoneSection: 0765 #if QT_CONFIG(timezone) 0766 result = findTimeZone(sectionTextRef, currentValue, 0767 absoluteMax(sectionIndex), 0768 absoluteMin(sectionIndex)); 0769 #endif 0770 break; 0771 case MonthSection: 0772 case DayOfWeekSectionShort: 0773 case DayOfWeekSectionLong: 0774 if (sn.count >= 3) { 0775 QString sectiontext = sectionTextRef.toString(); 0776 int num = 0, used = 0; 0777 if (sn.type == MonthSection) { 0778 const QDate minDate = getMinimum().date(); 0779 const int min = (currentValue.date().year() == minDate.year()) 0780 ? minDate.month() : 1; 0781 num = findMonth(sectiontext.toLower(), min, sectionIndex, §iontext, &used); 0782 } else { 0783 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used); 0784 } 0785 0786 result = ParsedSection(Intermediate, num, used); 0787 if (num != -1) { 0788 text->replace(offset, used, sectiontext.constData(), used); 0789 if (used == sectiontext.size()) 0790 result = ParsedSection(Acceptable, num, used); 0791 } 0792 break; 0793 } 0794 Q_FALLTHROUGH(); 0795 // All numeric: 0796 case DaySection: 0797 case YearSection: 0798 case YearSection2Digits: 0799 case Hour12Section: 0800 case Hour24Section: 0801 case MinuteSection: 0802 case SecondSection: 0803 case MSecSection: { 0804 int sectiontextSize = sectionTextRef.size(); 0805 if (sectiontextSize == 0) { 0806 result = ParsedSection(Intermediate); 0807 } else { 0808 for (int i = 0; i < sectiontextSize; ++i) { 0809 if (sectionTextRef.at(i).isSpace()) 0810 sectiontextSize = i; // which exits the loop 0811 } 0812 0813 const int absMax = absoluteMax(sectionIndex); 0814 QLocale loc; 0815 bool ok = true; 0816 int last = -1, used = -1; 0817 0818 Q_ASSERT(sectiontextSize <= sectionmaxsize); 0819 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0820 QStringRef digitsStr = sectionTextRef.left(sectiontextSize); 0821 #else 0822 QStringView digitsStr = sectionTextRef.left(sectiontextSize); 0823 #endif 0824 for (int digits = sectiontextSize; digits >= 1; --digits) { 0825 digitsStr.truncate(digits); 0826 int tmp = (int)loc.toUInt(digitsStr, &ok); 0827 if (ok && sn.type == Hour12Section) { 0828 if (tmp > 12) { 0829 tmp = -1; 0830 ok = false; 0831 } else if (tmp == 12) { 0832 tmp = 0; 0833 } 0834 } 0835 if (ok && tmp <= absMax) { 0836 QDTPDEBUG << sectionTextRef.left(digits) << tmp << digits; 0837 last = tmp; 0838 used = digits; 0839 break; 0840 } 0841 } 0842 0843 if (last == -1) { 0844 QChar first(sectionTextRef.at(0)); 0845 if (separators.at(sectionIndex + 1).startsWith(first)) 0846 result = ParsedSection(Intermediate, 0, used); 0847 else 0848 QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" << last << ok; 0849 } else { 0850 const FieldInfo fi = fieldInfo(sectionIndex); 0851 const bool unfilled = used < sectionmaxsize; 0852 if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002 0853 for (int i = used; i < sectionmaxsize; ++i) 0854 last *= 10; 0855 } 0856 // Even those *= 10s can't take last above absMax: 0857 Q_ASSERT(last <= absMax); 0858 const int absMin = absoluteMin(sectionIndex); 0859 if (last < absMin) { 0860 if (unfilled) 0861 result = ParsedSection(Intermediate, last, used); 0862 else 0863 QDTPDEBUG << "invalid because" << last << "is less than absoluteMin" << absMin; 0864 } else if (unfilled && (fi & (FixedWidth|Numeric)) == (FixedWidth|Numeric)) { 0865 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) { 0866 const int missingZeroes = sectionmaxsize - digitsStr.size(); 0867 result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes); 0868 text->insert(offset, QString(missingZeroes, QLatin1Char('0'))); 0869 ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded); 0870 } else { 0871 result = ParsedSection(Intermediate, last, used);; 0872 } 0873 } else { 0874 result = ParsedSection(Acceptable, last, used); 0875 } 0876 } 0877 } 0878 break; } 0879 default: 0880 qWarning("QDateTimeParser::parseSection Internal error (%s %d)", 0881 qPrintable(sn.name()), sectionIndex); 0882 return result; 0883 } 0884 Q_ASSERT(result.state != Invalid || result.value == -1); 0885 0886 return result; 0887 } 0888 0889 /*! 0890 \internal 0891 0892 Returns a date consistent with the given data on parts specified by known, 0893 while staying as close to the given data as it can. Returns an invalid date 0894 when on valid date is consistent with the data. 0895 */ 0896 0897 static QDate actualDate(QDateTimeParser::Sections known, int year, int year2digits, 0898 int month, int day, int dayofweek) 0899 { 0900 QDate actual(year, month, day); 0901 if (actual.isValid() && year % 100 == year2digits && actual.dayOfWeek() == dayofweek) 0902 return actual; // The obvious candidate is fine :-) 0903 0904 if (dayofweek < 1 || dayofweek > 7) // Invalid: ignore 0905 known &= ~QDateTimeParser::DayOfWeekSectionMask; 0906 0907 // Assuming year > 0 ... 0908 if (year % 100 != year2digits) { 0909 if (known & QDateTimeParser::YearSection2Digits) { 0910 // Over-ride year, even if specified: 0911 year += year2digits - year % 100; 0912 known &= ~QDateTimeParser::YearSection; 0913 } else { 0914 year2digits = year % 100; 0915 } 0916 } 0917 Q_ASSERT(year % 100 == year2digits); 0918 0919 if (month < 1) { // If invalid, clip to nearest valid and ignore in known. 0920 month = 1; 0921 known &= ~QDateTimeParser::MonthSection; 0922 } else if (month > 12) { 0923 month = 12; 0924 known &= ~QDateTimeParser::MonthSection; 0925 } 0926 0927 QDate first(year, month, 1); 0928 int last = known & QDateTimeParser::YearSection && known & QDateTimeParser::MonthSection 0929 ? first.daysInMonth() : 0; 0930 // If we also know day-of-week, tweak last to the last in the month that matches it: 0931 if (last && known & QDateTimeParser::DayOfWeekSectionMask) { 0932 int diff = (dayofweek - first.dayOfWeek() - last) % 7; 0933 Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0. 0934 last += diff; 0935 } 0936 if (day < 1) { 0937 if (known & QDateTimeParser::DayOfWeekSectionMask && last) { 0938 day = 1 + dayofweek - first.dayOfWeek(); 0939 if (day < 1) 0940 day += 7; 0941 } else { 0942 day = 1; 0943 } 0944 known &= ~QDateTimeParser::DaySection; 0945 } else if (day > 31) { 0946 day = last; 0947 known &= ~QDateTimeParser::DaySection; 0948 } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) { 0949 day = last; 0950 } 0951 0952 actual = QDate(year, month, day); 0953 if (!actual.isValid() // We can't do better than we have, in this case 0954 || (known & QDateTimeParser::DaySection 0955 && known & QDateTimeParser::MonthSection 0956 && known & QDateTimeParser::YearSection) // ditto 0957 || actual.dayOfWeek() == dayofweek // Good enough, use it. 0958 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) { // No contradiction, use it. 0959 return actual; 0960 } 0961 0962 /* 0963 Now it gets trickier. 0964 0965 We have some inconsistency in our data; we've been told day of week, but 0966 it doesn't fit with our year, month and day. At least one of these is 0967 unknown, though: so we can fix day of week by tweaking it. 0968 */ 0969 0970 if ((known & QDateTimeParser::DaySection) == 0) { 0971 // Relatively easy to fix. 0972 day += dayofweek - actual.dayOfWeek(); 0973 if (day < 1) 0974 day += 7; 0975 else if (day > actual.daysInMonth()) 0976 day -= 7; 0977 actual = QDate(year, month, day); 0978 return actual; 0979 } 0980 0981 if ((known & QDateTimeParser::MonthSection) == 0) { 0982 /* 0983 Try possible month-offsets, m, preferring small; at least one (present 0984 month doesn't work) and at most 11 (max month, 12, minus min, 1); try 0985 in both directions, ignoring any offset that takes us out of range. 0986 */ 0987 for (int m = 1; m < 12; m++) { 0988 if (m < month) { 0989 actual = QDate(year, month - m, day); 0990 if (actual.dayOfWeek() == dayofweek) 0991 return actual; 0992 } 0993 if (m + month <= 12) { 0994 actual = QDate(year, month + m, day); 0995 if (actual.dayOfWeek() == dayofweek) 0996 return actual; 0997 } 0998 } 0999 // Should only get here in corner cases; e.g. day == 31 1000 actual = QDate(year, month, day); // Restore from trial values. 1001 } 1002 1003 if ((known & QDateTimeParser::YearSection) == 0) { 1004 if (known & QDateTimeParser::YearSection2Digits) { 1005 /* 1006 Two-digit year and month are specified; choice of century can only 1007 fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if 1008 diff is in the other. It's also only reasonable to consider 1009 adjacent century, e.g. if year thinks it's 2012 and two-digit year 1010 is '97, it makes sense to consider 1997. If either adjacent 1011 century does work, the other won't. 1012 */ 1013 actual = QDate(year + 100, month, day); 1014 if (actual.dayOfWeek() == dayofweek) 1015 return actual; 1016 actual = QDate(year - 100, month, day); 1017 if (actual.dayOfWeek() == dayofweek) 1018 return actual; 1019 } else { 1020 // Offset by 7 is usually enough, but rare cases may need more: 1021 for (int y = 1; y < 12; y++) { 1022 actual = QDate(year - y, month, day); 1023 if (actual.dayOfWeek() == dayofweek) 1024 return actual; 1025 actual = QDate(year + y, month, day); 1026 if (actual.dayOfWeek() == dayofweek) 1027 return actual; 1028 } 1029 } 1030 actual = QDate(year, month, day); // Restore from trial values. 1031 } 1032 1033 return actual; // It'll just have to do :-( 1034 } 1035 1036 /*! 1037 \internal 1038 */ 1039 1040 static QTime actualTime(QDateTimeParser::Sections known, 1041 int hour, int hour12, int ampm, 1042 int minute, int second, int msec) 1043 { 1044 // If we have no conflict, or don't know enough to diagonose one, use this: 1045 QTime actual(hour, minute, second, msec); 1046 if (hour12 < 0 || hour12 > 12) { // ignore bogus value 1047 known &= ~QDateTimeParser::Hour12Section; 1048 hour12 = hour % 12; 1049 } 1050 1051 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) { 1052 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12) 1053 return actual; 1054 1055 if ((known & QDateTimeParser::Hour24Section) == 0) 1056 hour = hour12 + (hour > 12 ? 12 : 0); 1057 } else { 1058 Q_ASSERT(ampm == 0 || ampm == 1); 1059 if (hour - hour12 == ampm * 12) 1060 return actual; 1061 1062 if ((known & QDateTimeParser::Hour24Section) == 0 1063 && known & QDateTimeParser::Hour12Section) { 1064 hour = hour12 + ampm * 12; 1065 } 1066 } 1067 actual = QTime(hour, minute, second, msec); 1068 return actual; 1069 } 1070 1071 /*! 1072 \internal 1073 */ 1074 QDateTimeParser::StateNode 1075 QDateTimeParser::scanString(const QDateTime &defaultValue, 1076 bool fixup, QString *input) const 1077 { 1078 State state = Acceptable; 1079 bool conflicts = false; 1080 const int sectionNodesCount = sectionNodes.size(); 1081 int padding = 0; 1082 int pos = 0; 1083 int year, month, day; 1084 const QDate defaultDate = defaultValue.date(); 1085 const QTime defaultTime = defaultValue.time(); 1086 defaultDate.getDate(&year, &month, &day); 1087 int year2digits = year % 100; 1088 int hour = defaultTime.hour(); 1089 int hour12 = -1; 1090 int minute = defaultTime.minute(); 1091 int second = defaultTime.second(); 1092 int msec = defaultTime.msec(); 1093 int dayofweek = defaultDate.dayOfWeek(); 1094 Qt::TimeSpec tspec = defaultValue.timeSpec(); 1095 int zoneOffset = 0; // In seconds; local - UTC 1096 #if QT_CONFIG(timezone) 1097 QTimeZone timeZone; 1098 #endif 1099 switch (tspec) { 1100 case Qt::OffsetFromUTC: // timeZone is ignored 1101 zoneOffset = defaultValue.offsetFromUtc(); 1102 break; 1103 #if QT_CONFIG(timezone) 1104 case Qt::TimeZone: 1105 timeZone = defaultValue.timeZone(); 1106 if (timeZone.isValid()) 1107 zoneOffset = timeZone.offsetFromUtc(defaultValue); 1108 // else: is there anything we can do about this ? 1109 break; 1110 #endif 1111 default: // zoneOffset and timeZone are ignored 1112 break; 1113 } 1114 1115 int ampm = -1; 1116 Sections isSet = NoSection; 1117 1118 for (int index = 0; index < sectionNodesCount; ++index) { 1119 Q_ASSERT(state != Invalid); 1120 const QString &separator = separators.at(index); 1121 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1122 if (input->midRef(pos, separator.size()) != separator) { 1123 QDTPDEBUG << "invalid because" << input->midRef(pos, separator.size()) 1124 << "!=" << separator 1125 << index << pos << currentSectionIndex; 1126 return StateNode(); 1127 } 1128 #else 1129 if (QStringView(*input).mid(pos, separator.size()) != separator) { 1130 QDTPDEBUG << "invalid because" << QStringView(*input).mid(pos, separator.size()) 1131 << "!=" << separator 1132 << index << pos << currentSectionIndex; 1133 return StateNode(); 1134 } 1135 #endif 1136 pos += separator.size(); 1137 sectionNodes[index].pos = pos; 1138 int *current = nullptr; 1139 const SectionNode sn = sectionNodes.at(index); 1140 ParsedSection sect; 1141 1142 { 1143 const QDate date = actualDate(isSet, year, year2digits, month, day, dayofweek); 1144 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec); 1145 sect = parseSection( 1146 #if QT_CONFIG(timezone) 1147 tspec == Qt::TimeZone ? QDateTime(date, time, timeZone) : 1148 #endif 1149 QDateTime(date, time, tspec, zoneOffset), 1150 index, pos, input); 1151 } 1152 1153 QDTPDEBUG << "sectionValue" << sn.name() << *input 1154 << "pos" << pos << "used" << sect.used << stateName(sect.state); 1155 1156 padding += sect.zeroes; 1157 if (fixup && sect.state == Intermediate && sect.used < sn.count) { 1158 const FieldInfo fi = fieldInfo(index); 1159 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) { 1160 const QString newText = QString::fromLatin1("%1").arg(sect.value, sn.count, 10, QLatin1Char('0')); 1161 input->replace(pos, sect.used, newText); 1162 sect.used = sn.count; 1163 } 1164 } 1165 1166 state = qMin<State>(state, sect.state); 1167 // QDateTimeEdit can fix Intermediate and zeroes, but input needing that didn't match format: 1168 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes))) 1169 return StateNode(); 1170 1171 switch (sn.type) { 1172 case TimeZoneSection: 1173 current = &zoneOffset; 1174 if (sect.used > 0) { 1175 #if QT_CONFIG(timezone) // Synchronize with what findTimeZone() found: 1176 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1177 QStringRef zoneName = input->midRef(pos, sect.used); 1178 #else 1179 QStringView zoneName = QStringView(*input).mid(pos, sect.used); 1180 #endif 1181 Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0 1182 const QByteArray latinZone(zoneName == QLatin1String("Z") 1183 ? QByteArray("UTC") : zoneName.toLatin1()); 1184 timeZone = QTimeZone(latinZone); 1185 tspec = timeZone.isValid() ? (QTimeZone::isTimeZoneIdAvailable(latinZone) ? Qt::TimeZone : Qt::OffsetFromUTC) : Qt::LocalTime; 1186 #else 1187 tspec = Qt::LocalTime; 1188 #endif 1189 } 1190 break; 1191 case Hour24Section: current = &hour; break; 1192 case Hour12Section: current = &hour12; break; 1193 case MinuteSection: current = &minute; break; 1194 case SecondSection: current = &second; break; 1195 case MSecSection: current = &msec; break; 1196 case YearSection: current = &year; break; 1197 case YearSection2Digits: current = &year2digits; break; 1198 case MonthSection: current = &month; break; 1199 case DayOfWeekSectionShort: 1200 case DayOfWeekSectionLong: current = &dayofweek; break; 1201 case DaySection: current = &day; sect.value = qMax<int>(1, sect.value); break; 1202 case AmPmSection: current = &m; break; 1203 default: 1204 qWarning("QDateTimeParser::parse Internal error (%s)", 1205 qPrintable(sn.name())); 1206 break; 1207 } 1208 1209 if (sect.used > 0) 1210 pos += sect.used; 1211 QDTPDEBUG << index << sn.name() << "is set to" 1212 << pos << "state is" << stateName(state); 1213 1214 if (!current) { 1215 qWarning("QDateTimeParser::parse Internal error 2"); 1216 return StateNode(); 1217 } 1218 if (isSet & sn.type && *current != sect.value) { 1219 QDTPDEBUG << "CONFLICT " << sn.name() << *current << sect.value; 1220 conflicts = true; 1221 if (index != currentSectionIndex || sect.state == Invalid) { 1222 continue; 1223 } 1224 } 1225 if (sect.state != Invalid) 1226 *current = sect.value; 1227 1228 // Record the present section: 1229 isSet |= sn.type; 1230 } 1231 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1232 if (input->midRef(pos) != separators.last()) { 1233 QDTPDEBUG << "invalid because" << input->midRef(pos) 1234 << "!=" << separators.last() << pos; 1235 return StateNode(); 1236 } 1237 #else 1238 if (QStringView(*input).mid(pos) != separators.last()) { 1239 QDTPDEBUG << "invalid because" << QStringView(*input).mid(pos) 1240 << "!=" << separators.last() << pos; 1241 return StateNode(); 1242 } 1243 #endif 1244 if (parserType != QMetaType::QTime) { 1245 if (year % 100 != year2digits && (isSet & YearSection2Digits)) { 1246 if (!(isSet & YearSection)) { 1247 year = (year / 100) * 100; 1248 year += year2digits; 1249 } else { 1250 conflicts = true; 1251 const SectionNode &sn = sectionNode(currentSectionIndex); 1252 if (sn.type == YearSection2Digits) { 1253 year = (year / 100) * 100; 1254 year += year2digits; 1255 } 1256 } 1257 } 1258 1259 const QDate date(year, month, day); 1260 const int diff = dayofweek - date.dayOfWeek(); 1261 if (diff != 0 && state == Acceptable && isSet & DayOfWeekSectionMask) { 1262 if (isSet & DaySection) 1263 conflicts = true; 1264 const SectionNode &sn = sectionNode(currentSectionIndex); 1265 if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) { 1266 // dayofweek should be preferred 1267 day += diff; 1268 if (day <= 0) { 1269 day += 7; 1270 } else if (day > date.daysInMonth()) { 1271 day -= 7; 1272 } 1273 QDTPDEBUG << year << month << day << dayofweek 1274 << diff << QDate(year, month, day).dayOfWeek(); 1275 } 1276 } 1277 1278 bool needfixday = false; 1279 if (sectionType(currentSectionIndex) & DaySectionMask) { 1280 cachedDay = day; 1281 } else if (cachedDay > day) { 1282 day = cachedDay; 1283 needfixday = true; 1284 } 1285 1286 if (!QDate::isValid(year, month, day)) { 1287 if (day < 32) { 1288 cachedDay = day; 1289 } 1290 if (day > 28 && QDate::isValid(year, month, 1)) { 1291 needfixday = true; 1292 } 1293 } 1294 if (needfixday) { 1295 if (context == FromString) { 1296 return StateNode(); 1297 } 1298 if (state == Acceptable && fixday) { 1299 day = qMin<int>(day, QDate(year, month, 1).daysInMonth()); 1300 1301 const QLocale loc = locale(); 1302 for (int i=0; i<sectionNodesCount; ++i) { 1303 const SectionNode sn = sectionNode(i); 1304 if (sn.type & DaySection) { 1305 input->replace(sectionPos(sn), sectionSize(i), loc.toString(day)); 1306 } else if (sn.type & DayOfWeekSectionMask) { 1307 const int dayOfWeek = QDate(year, month, day).dayOfWeek(); 1308 const QLocale::FormatType dayFormat = 1309 (sn.type == DayOfWeekSectionShort 1310 ? QLocale::ShortFormat : QLocale::LongFormat); 1311 const QString dayName(loc.dayName(dayOfWeek, dayFormat)); 1312 input->replace(sectionPos(sn), sectionSize(i), dayName); 1313 } 1314 } 1315 } else if (state > Intermediate) { 1316 state = Intermediate; 1317 } 1318 } 1319 } 1320 1321 if (parserType != QMetaType::QDate) { 1322 if (isSet & Hour12Section) { 1323 const bool hasHour = isSet & Hour24Section; 1324 if (ampm == -1) { 1325 if (hasHour) { 1326 ampm = (hour < 12 ? 0 : 1); 1327 } else { 1328 ampm = 0; // no way to tell if this is am or pm so I assume am 1329 } 1330 } 1331 hour12 = (ampm == 0 ? hour12 % 12 : (hour12 % 12) + 12); 1332 if (!hasHour) { 1333 hour = hour12; 1334 } else if (hour != hour12) { 1335 conflicts = true; 1336 } 1337 } else if (ampm != -1) { 1338 if (!(isSet & (Hour24Section))) { 1339 hour = (12 * ampm); // special case. Only ap section 1340 } else if ((ampm == 0) != (hour < 12)) { 1341 conflicts = true; 1342 } 1343 } 1344 1345 } 1346 1347 QDTPDEBUG << year << month << day << hour << minute << second << msec; 1348 Q_ASSERT(state != Invalid); 1349 1350 const QDate date(year, month, day); 1351 const QTime time(hour, minute, second, msec); 1352 const QDateTime when = 1353 #if QT_CONFIG(timezone) 1354 tspec == Qt::TimeZone ? QDateTime(date, time, timeZone) : 1355 #endif 1356 QDateTime(date, time, tspec, zoneOffset); 1357 1358 // If hour wasn't specified, check the default we're using exists on the 1359 // given date (which might be a spring-forward, skipping an hour). 1360 if (parserType == QMetaType::QDateTime && !(isSet & HourSectionMask) && !when.isValid()) { 1361 qint64 msecs = when.toMSecsSinceEpoch(); 1362 // Fortunately, that gets a useful answer ... 1363 const QDateTime replace = 1364 #if QT_CONFIG(timezone) 1365 tspec == Qt::TimeZone 1366 ? QDateTime::fromMSecsSinceEpoch(msecs, timeZone) : 1367 #endif 1368 QDateTime::fromMSecsSinceEpoch(msecs, tspec, zoneOffset); 1369 const QTime tick = replace.time(); 1370 if (replace.date() == date 1371 && (!(isSet & MinuteSection) || tick.minute() == minute) 1372 && (!(isSet & SecondSection) || tick.second() == second) 1373 && (!(isSet & MSecSection) || tick.msec() == msec)) { 1374 return StateNode(replace, state, padding, conflicts); 1375 } 1376 } 1377 1378 return StateNode(when, state, padding, conflicts); 1379 } 1380 1381 /*! 1382 \internal 1383 */ 1384 1385 QDateTimeParser::StateNode 1386 QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const 1387 { 1388 const QDateTime minimum = getMinimum(); 1389 const QDateTime maximum = getMaximum(); 1390 1391 QDTPDEBUG << "parse" << input; 1392 StateNode scan = scanString(defaultValue, fixup, &input); 1393 QDTPDEBUGN("'%s' => '%s'(%s)", input.toLatin1().constData(), 1394 scan.value.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(), 1395 stateName(scan.state).toLatin1().constData()); 1396 1397 if (scan.value.isValid() && scan.state != Invalid) { 1398 if (context != FromString && scan.value < minimum) { 1399 const QLatin1Char space(' '); 1400 if (scan.value >= minimum) 1401 qWarning("QDateTimeParser::parse Internal error 3 (%s %s)", 1402 qPrintable(scan.value.toString()), qPrintable(minimum.toString())); 1403 1404 bool done = false; 1405 scan.state = Invalid; 1406 const int sectionNodesCount = sectionNodes.size(); 1407 for (int i=0; i<sectionNodesCount && !done; ++i) { 1408 const SectionNode &sn = sectionNodes.at(i); 1409 QString t = sectionText(input, i, sn.pos).toLower(); 1410 if ((t.size() < sectionMaxSize(i) 1411 && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) 1412 || t.contains(space)) { 1413 switch (sn.type) { 1414 case AmPmSection: 1415 switch (findAmPm(t, i)) { 1416 case AM: 1417 case PM: 1418 scan.state = Acceptable; 1419 done = true; 1420 break; 1421 case Neither: 1422 scan.state = Invalid; 1423 done = true; 1424 break; 1425 case PossibleAM: 1426 case PossiblePM: 1427 case PossibleBoth: { 1428 const QDateTime copy(scan.value.addSecs(12 * 60 * 60)); 1429 if (copy >= minimum && copy <= maximum) { 1430 scan.state = Intermediate; 1431 done = true; 1432 } 1433 break; } 1434 } 1435 Q_FALLTHROUGH(); 1436 case MonthSection: 1437 if (sn.count >= 3) { 1438 const int finalMonth = scan.value.date().month(); 1439 int tmp = finalMonth; 1440 // I know the first possible month makes the date too early 1441 while ((tmp = findMonth(t, tmp + 1, i)) != -1) { 1442 const QDateTime copy(scan.value.addMonths(tmp - finalMonth)); 1443 if (copy >= minimum && copy <= maximum) 1444 break; // break out of while 1445 } 1446 if (tmp != -1) { 1447 scan.state = Intermediate; 1448 done = true; 1449 } 1450 break; 1451 } 1452 Q_FALLTHROUGH(); 1453 default: { 1454 int toMin; 1455 int toMax; 1456 1457 if (sn.type & TimeSectionMask) { 1458 if (scan.value.daysTo(minimum) != 0) { 1459 break; 1460 } 1461 const QTime time = scan.value.time(); 1462 toMin = time.msecsTo(minimum.time()); 1463 if (scan.value.daysTo(maximum) > 0) 1464 toMax = -1; // can't get to max 1465 else 1466 toMax = time.msecsTo(maximum.time()); 1467 } else { 1468 toMin = scan.value.daysTo(minimum); 1469 toMax = scan.value.daysTo(maximum); 1470 } 1471 const int maxChange = sn.maxChange(); 1472 if (toMin > maxChange) { 1473 QDTPDEBUG << "invalid because toMin > maxChange" << toMin 1474 << maxChange << t << scan.value << minimum; 1475 scan.state = Invalid; 1476 done = true; 1477 break; 1478 } else if (toMax > maxChange) { 1479 toMax = -1; // can't get to max 1480 } 1481 1482 const int min = getDigit(minimum, i); 1483 if (min == -1) { 1484 qWarning("QDateTimeParser::parse Internal error 4 (%s)", 1485 qPrintable(sn.name())); 1486 scan.state = Invalid; 1487 done = true; 1488 break; 1489 } 1490 1491 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value); 1492 int pos = position + scan.padded - sn.pos; 1493 if (pos < 0 || pos >= t.size()) 1494 pos = -1; 1495 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) { 1496 QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max 1497 << sn.name() << "returned" << toMax << toMin << pos; 1498 scan.state = Invalid; 1499 done = true; 1500 break; 1501 } 1502 scan.state = Intermediate; 1503 done = true; 1504 break; } 1505 } 1506 } 1507 } 1508 } else { 1509 if (context == FromString) { 1510 // optimization 1511 Q_ASSERT(maximum.date().toJulianDay() == 5373484); 1512 if (scan.value.date().toJulianDay() > 5373484) 1513 scan.state = Invalid; 1514 } else { 1515 if (scan.value > maximum) 1516 scan.state = Invalid; 1517 } 1518 1519 QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum; 1520 } 1521 } 1522 text = scan.input = input; 1523 // Set spec *after* all checking, so validity is a property of the string: 1524 scan.value = scan.value.toTimeSpec(spec); 1525 return scan; 1526 } 1527 1528 /* 1529 \internal 1530 \brief Returns the index in \a entries with the best prefix match to \a text 1531 1532 Scans \a entries looking for an entry overlapping \a text as much as possible 1533 (an exact match beats any prefix match; a match of the full entry as prefix of 1534 text beats any entry but one matching a longer prefix; otherwise, the match of 1535 longest prefix wins, earlier entries beating later on a draw). Records the 1536 length of overlap in *used (if \a used is non-NULL) and the first entry that 1537 overlapped this much in *usedText (if \a usedText is non-NULL). 1538 */ 1539 static int findTextEntry(const QString &text, const QVector<QString> &entries, QString *usedText, int *used) 1540 { 1541 if (text.isEmpty()) 1542 return -1; 1543 1544 int bestMatch = -1; 1545 int bestCount = 0; 1546 for (int n = 0; n < entries.size(); ++n) 1547 { 1548 const QString &name = entries.at(n); 1549 1550 const int limit = qMin(text.size(), name.size()); 1551 int i = 0; 1552 while (i < limit && text.at(i) == name.at(i).toLower()) 1553 ++i; 1554 // Full match beats an equal prefix match: 1555 if (i > bestCount || (i == bestCount && i == name.size())) { 1556 bestCount = i; 1557 bestMatch = n; 1558 if (i == name.size() && i == text.size()) 1559 break; // Exact match, name == text, wins. 1560 } 1561 } 1562 if (usedText && bestMatch != -1) 1563 *usedText = entries.at(bestMatch); 1564 if (used) 1565 *used = bestCount; 1566 1567 return bestMatch; 1568 } 1569 1570 /*! 1571 \internal 1572 finds the first possible monthname that \a str1 can 1573 match. Starting from \a index; str should already by lowered 1574 */ 1575 1576 int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex, 1577 QString *usedMonth, int *used) const 1578 { 1579 const SectionNode &sn = sectionNode(sectionIndex); 1580 if (sn.type != MonthSection) { 1581 qWarning("QDateTimeParser::findMonth Internal error"); 1582 return -1; 1583 } 1584 1585 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat; 1586 QLocale l = locale(); 1587 QVector<QString> monthNames; 1588 monthNames.reserve(13 - startMonth); 1589 for (int month = startMonth; month <= 12; ++month) 1590 monthNames.append(l.monthName(month, type)); 1591 1592 const int index = findTextEntry(str1, monthNames, usedMonth, used); 1593 return index < 0 ? index : index + startMonth; 1594 } 1595 1596 int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const 1597 { 1598 const SectionNode &sn = sectionNode(sectionIndex); 1599 if (!(sn.type & DaySectionMask)) { 1600 qWarning("QDateTimeParser::findDay Internal error"); 1601 return -1; 1602 } 1603 1604 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; 1605 QLocale l = locale(); 1606 QVector<QString> daysOfWeek; 1607 daysOfWeek.reserve(8 - startDay); 1608 for (int day = startDay; day <= 7; ++day) 1609 daysOfWeek.append(l.dayName(day, type)); 1610 1611 const int index = findTextEntry(str1, daysOfWeek, usedDay, used); 1612 return index < 0 ? index : index + startDay; 1613 } 1614 1615 /*! 1616 \internal 1617 1618 Return's .value is zone's offset, zone time - UTC time, in seconds. 1619 See QTimeZonePrivate::isValidId() for the format of zone names. 1620 */ 1621 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1622 QDateTimeParser::ParsedSection 1623 QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when, 1624 int maxVal, int minVal) const 1625 #else 1626 QDateTimeParser::ParsedSection 1627 QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when, 1628 int maxVal, int minVal) const 1629 #endif 1630 { 1631 #if QT_CONFIG(timezone) 1632 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1633 int index = startsWithLocalTimeZone(str); 1634 #else 1635 int index = startsWithLocalTimeZone(str, when); 1636 #endif 1637 int offset; 1638 1639 if (index > 0) { 1640 // We won't actually use this, but we need a valid return: 1641 offset = QDateTime(when.date(), when.time(), Qt::LocalTime).offsetFromUtc(); 1642 } else { 1643 int size = str.length(); 1644 offset = std::numeric_limits<int>::max(); // deliberately out of range 1645 Q_ASSERT(offset > QTimeZone::MaxUtcOffsetSecs); // cf. absoluteMax() 1646 1647 // Collect up plausibly-valid characters; let QTimeZone work out what's truly valid. 1648 while (index < size) { 1649 QChar here = str[index]; 1650 if (here < QChar(127) 1651 && (here.isLetterOrNumber() 1652 || here == QLatin1Char('/') || here == QLatin1Char('-') 1653 || here == QLatin1Char('_') || here == QLatin1Char('.') 1654 || here == QLatin1Char('+') || here == QLatin1Char(':'))) 1655 index++; 1656 else 1657 break; 1658 } 1659 1660 while (index > 0) { 1661 str.truncate(index); 1662 if (str == QLatin1String("Z")) { 1663 offset = 0; // "Zulu" time - a.k.a. UTC 1664 break; 1665 } 1666 QTimeZone zone(str.toLatin1()); 1667 if (zone.isValid()) { 1668 offset = zone.offsetFromUtc(when); 1669 break; 1670 } 1671 index--; // maybe we collected too much ... 1672 } 1673 } 1674 1675 if (index > 0 && maxVal >= offset && offset >= minVal) 1676 return ParsedSection(Acceptable, offset, index); 1677 1678 #endif // timezone 1679 return ParsedSection(); 1680 } 1681 1682 /*! 1683 \internal 1684 1685 Returns 1686 AM if str == tr("AM") 1687 PM if str == tr("PM") 1688 PossibleAM if str can become tr("AM") 1689 PossiblePM if str can become tr("PM") 1690 PossibleBoth if str can become tr("PM") and can become tr("AM") 1691 Neither if str can't become anything sensible 1692 */ 1693 QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const 1694 { 1695 const SectionNode &s = sectionNode(sectionIndex); 1696 if (s.type != AmPmSection) { 1697 qWarning("QDateTimeParser::findAmPm Internal error"); 1698 return Neither; 1699 } 1700 if (used) 1701 *used = str.size(); 1702 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1703 if (QStringRef(&str).trimmed().isEmpty()) { 1704 return PossibleBoth; 1705 } 1706 #else 1707 if (QStringView(str).trimmed().isEmpty()) { 1708 return PossibleBoth; 1709 } 1710 #endif 1711 const QLatin1Char space(' '); 1712 int size = sectionMaxSize(sectionIndex); 1713 1714 enum { 1715 amindex = 0, 1716 pmindex = 1 1717 }; 1718 QString ampm[2]; 1719 ampm[amindex] = getAmPmText(AmText, s.count == 1 ? UpperCase : LowerCase); 1720 ampm[pmindex] = getAmPmText(PmText, s.count == 1 ? UpperCase : LowerCase); 1721 for (int i=0; i<2; ++i) 1722 ampm[i].truncate(size); 1723 1724 QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1]; 1725 1726 if (str.indexOf(ampm[amindex], 0, Qt::CaseInsensitive) == 0) { 1727 str = ampm[amindex]; 1728 return AM; 1729 } else if (str.indexOf(ampm[pmindex], 0, Qt::CaseInsensitive) == 0) { 1730 str = ampm[pmindex]; 1731 return PM; 1732 } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) { 1733 return Neither; 1734 } 1735 size = qMin(size, str.size()); 1736 1737 bool broken[2] = {false, false}; 1738 for (int i=0; i<size; ++i) { 1739 if (str.at(i) != space) { 1740 for (int j=0; j<2; ++j) { 1741 if (!broken[j]) { 1742 int index = ampm[j].indexOf(str.at(i)); 1743 QDTPDEBUG << "looking for" << str.at(i) 1744 << "in" << ampm[j] << "and got" << index; 1745 if (index == -1) { 1746 if (str.at(i).category() == QChar::Letter_Uppercase) { 1747 index = ampm[j].indexOf(str.at(i).toLower()); 1748 QDTPDEBUG << "trying with" << str.at(i).toLower() 1749 << "in" << ampm[j] << "and got" << index; 1750 } else if (str.at(i).category() == QChar::Letter_Lowercase) { 1751 index = ampm[j].indexOf(str.at(i).toUpper()); 1752 QDTPDEBUG << "trying with" << str.at(i).toUpper() 1753 << "in" << ampm[j] << "and got" << index; 1754 } 1755 if (index == -1) { 1756 broken[j] = true; 1757 if (broken[amindex] && broken[pmindex]) { 1758 QDTPDEBUG << str << "didn't make it"; 1759 return Neither; 1760 } 1761 continue; 1762 } else { 1763 str[i] = ampm[j].at(index); // fix case 1764 } 1765 } 1766 ampm[j].remove(index, 1); 1767 } 1768 } 1769 } 1770 } 1771 if (!broken[pmindex] && !broken[amindex]) 1772 return PossibleBoth; 1773 return (!broken[amindex] ? PossibleAM : PossiblePM); 1774 } 1775 #endif // datestring 1776 1777 /*! 1778 \internal 1779 Max number of units that can be changed by this section. 1780 */ 1781 1782 int QDateTimeParser::SectionNode::maxChange() const 1783 { 1784 switch (type) { 1785 // Time. unit is msec 1786 case MSecSection: return 999; 1787 case SecondSection: return 59 * 1000; 1788 case MinuteSection: return 59 * 60 * 1000; 1789 case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000; 1790 1791 // Date. unit is day 1792 case DayOfWeekSectionShort: 1793 case DayOfWeekSectionLong: return 7; 1794 case DaySection: return 30; 1795 case MonthSection: return 365 - 31; 1796 case YearSection: return 9999 * 365; 1797 case YearSection2Digits: return 100 * 365; 1798 default: 1799 qWarning("QDateTimeParser::maxChange() Internal error (%s)", 1800 qPrintable(name())); 1801 } 1802 1803 return -1; 1804 } 1805 1806 QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const 1807 { 1808 FieldInfo ret = {}; 1809 const SectionNode &sn = sectionNode(index); 1810 switch (sn.type) { 1811 case MSecSection: 1812 ret |= Fraction; 1813 Q_FALLTHROUGH(); 1814 case SecondSection: 1815 case MinuteSection: 1816 case Hour24Section: 1817 case Hour12Section: 1818 case YearSection2Digits: 1819 ret |= AllowPartial; 1820 Q_FALLTHROUGH(); 1821 case YearSection: 1822 ret |= Numeric; 1823 if (sn.count != 1) 1824 ret |= FixedWidth; 1825 break; 1826 case MonthSection: 1827 case DaySection: 1828 switch (sn.count) { 1829 case 2: 1830 ret |= FixedWidth; 1831 Q_FALLTHROUGH(); 1832 case 1: 1833 ret |= (Numeric|AllowPartial); 1834 break; 1835 } 1836 break; 1837 case DayOfWeekSectionShort: 1838 case DayOfWeekSectionLong: 1839 if (sn.count == 3) 1840 ret |= FixedWidth; 1841 break; 1842 case AmPmSection: 1843 ret |= FixedWidth; 1844 break; 1845 case TimeZoneSection: 1846 break; 1847 default: 1848 qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %s %d)", 1849 index, qPrintable(sn.name()), sn.count); 1850 break; 1851 } 1852 return ret; 1853 } 1854 1855 QString QDateTimeParser::SectionNode::format() const 1856 { 1857 QChar fillChar; 1858 switch (type) { 1859 case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap"); 1860 case MSecSection: fillChar = QLatin1Char('z'); break; 1861 case SecondSection: fillChar = QLatin1Char('s'); break; 1862 case MinuteSection: fillChar = QLatin1Char('m'); break; 1863 case Hour24Section: fillChar = QLatin1Char('H'); break; 1864 case Hour12Section: fillChar = QLatin1Char('h'); break; 1865 case DayOfWeekSectionShort: 1866 case DayOfWeekSectionLong: 1867 case DaySection: fillChar = QLatin1Char('d'); break; 1868 case MonthSection: fillChar = QLatin1Char('M'); break; 1869 case YearSection2Digits: 1870 case YearSection: fillChar = QLatin1Char('y'); break; 1871 default: 1872 qWarning("QDateTimeParser::sectionFormat Internal error (%s)", 1873 qPrintable(name(type))); 1874 return QString(); 1875 } 1876 if (fillChar.isNull()) { 1877 qWarning("QDateTimeParser::sectionFormat Internal error 2"); 1878 return QString(); 1879 } 1880 return QString(count, fillChar); 1881 } 1882 1883 1884 /*! 1885 \internal 1886 1887 Returns \c true if str can be modified to represent a 1888 number that is within min and max. 1889 */ 1890 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1891 bool QDateTimeParser::potentialValue(const QStringRef &str, int min, int max, int index, 1892 const QDateTime ¤tValue, int insert) const 1893 #else 1894 bool QDateTimeParser::potentialValue(const QStringView &str, int min, int max, int index, 1895 const QDateTime ¤tValue, int insert) const 1896 #endif 1897 { 1898 if (str.isEmpty()) { 1899 return true; 1900 } 1901 const int size = sectionMaxSize(index); 1902 int val = (int)locale().toUInt(str); 1903 const SectionNode &sn = sectionNode(index); 1904 if (sn.type == YearSection2Digits) { 1905 const int year = currentValue.date().year(); 1906 val += year - (year % 100); 1907 } 1908 if (val >= min && val <= max && str.size() == size) { 1909 return true; 1910 } else if (val > max) { 1911 return false; 1912 } else if (str.size() == size && val < min) { 1913 return false; 1914 } 1915 1916 const int len = size - str.size(); 1917 for (int i=0; i<len; ++i) { 1918 for (int j=0; j<10; ++j) { 1919 if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) { 1920 return true; 1921 } else if (insert >= 0) { 1922 const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert); 1923 if (potentialValue(tmp, min, max, index, currentValue, insert)) 1924 return true; 1925 } 1926 } 1927 } 1928 1929 return false; 1930 } 1931 1932 /*! 1933 \internal 1934 */ 1935 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1936 bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QStringRef &text) const 1937 #else 1938 bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QStringView &text) const 1939 #endif 1940 { 1941 Q_ASSERT(text.size() < sectionMaxSize(index)); 1942 const SectionNode &node = sectionNode(index); 1943 int min = absoluteMin(index); 1944 int max = absoluteMax(index, current); 1945 // Time-zone field is only numeric if given as offset from UTC: 1946 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) { 1947 const QDateTime maximum = getMaximum(); 1948 const QDateTime minimum = getMinimum(); 1949 Q_ASSERT(current >= minimum && current <= maximum); 1950 1951 QDateTime tmp = current; 1952 if (!setDigit(tmp, index, min) || tmp < minimum) 1953 min = getDigit(minimum, index); 1954 1955 if (!setDigit(tmp, index, max) || tmp > maximum) 1956 max = getDigit(maximum, index); 1957 } 1958 int pos = cursorPosition() - node.pos; 1959 if (pos < 0 || pos >= text.size()) 1960 pos = -1; 1961 1962 /* 1963 If the value potentially can become another valid entry we don't want to 1964 skip to the next. E.g. In a M field (month without leading 0) if you type 1965 1 we don't want to autoskip (there might be [012] following) but if you 1966 type 3 we do. 1967 */ 1968 return !potentialValue(text, min, max, index, current, pos); 1969 } 1970 1971 /*! 1972 \internal 1973 For debugging. Returns the name of the section \a s. 1974 */ 1975 1976 QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s) 1977 { 1978 switch (s) { 1979 case QDateTimeParser::AmPmSection: return QLatin1String("AmPmSection"); 1980 case QDateTimeParser::DaySection: return QLatin1String("DaySection"); 1981 case QDateTimeParser::DayOfWeekSectionShort: return QLatin1String("DayOfWeekSectionShort"); 1982 case QDateTimeParser::DayOfWeekSectionLong: return QLatin1String("DayOfWeekSectionLong"); 1983 case QDateTimeParser::Hour24Section: return QLatin1String("Hour24Section"); 1984 case QDateTimeParser::Hour12Section: return QLatin1String("Hour12Section"); 1985 case QDateTimeParser::MSecSection: return QLatin1String("MSecSection"); 1986 case QDateTimeParser::MinuteSection: return QLatin1String("MinuteSection"); 1987 case QDateTimeParser::MonthSection: return QLatin1String("MonthSection"); 1988 case QDateTimeParser::SecondSection: return QLatin1String("SecondSection"); 1989 case QDateTimeParser::TimeZoneSection: return QLatin1String("TimeZoneSection"); 1990 case QDateTimeParser::YearSection: return QLatin1String("YearSection"); 1991 case QDateTimeParser::YearSection2Digits: return QLatin1String("YearSection2Digits"); 1992 case QDateTimeParser::NoSection: return QLatin1String("NoSection"); 1993 case QDateTimeParser::FirstSection: return QLatin1String("FirstSection"); 1994 case QDateTimeParser::LastSection: return QLatin1String("LastSection"); 1995 default: return QLatin1String("Unknown section ") + QString::number(int(s)); 1996 } 1997 } 1998 1999 /*! 2000 \internal 2001 For debugging. Returns the name of the state \a s. 2002 */ 2003 2004 QString QDateTimeParser::stateName(State s) const 2005 { 2006 switch (s) { 2007 case Invalid: return QLatin1String("Invalid"); 2008 case Intermediate: return QLatin1String("Intermediate"); 2009 case Acceptable: return QLatin1String("Acceptable"); 2010 default: return QLatin1String("Unknown state ") + QString::number(s); 2011 } 2012 } 2013 2014 #if QT_CONFIG(datestring) 2015 bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const 2016 { 2017 QDateTime val(QDate(1900, 1, 1), QDATETIMEEDIT_TIME_MIN); 2018 const StateNode tmp = parse(t, -1, val, false); 2019 if (tmp.state != Acceptable || tmp.conflicts) { 2020 return false; 2021 } 2022 if (time) { 2023 const QTime t = tmp.value.time(); 2024 if (!t.isValid()) { 2025 return false; 2026 } 2027 *time = t; 2028 } 2029 2030 if (date) { 2031 const QDate d = tmp.value.date(); 2032 if (!d.isValid()) { 2033 return false; 2034 } 2035 *date = d; 2036 } 2037 return true; 2038 } 2039 #endif // datestring 2040 2041 QDateTime QDateTimeParser::getMinimum() const 2042 { 2043 // Cache the most common case 2044 if (spec == Qt::LocalTime) { 2045 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, Qt::LocalTime); 2046 return localTimeMin; 2047 } 2048 return QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, spec); 2049 } 2050 2051 QDateTime QDateTimeParser::getMaximum() const 2052 { 2053 // Cache the most common case 2054 if (spec == Qt::LocalTime) { 2055 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, Qt::LocalTime); 2056 return localTimeMax; 2057 } 2058 return QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, spec); 2059 } 2060 2061 QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const 2062 { 2063 const QLocale loc = locale(); 2064 QString raw = ap == AmText ? loc.amText() : loc.pmText(); 2065 return cs == UpperCase ? raw.toUpper() : raw.toLower(); 2066 } 2067 2068 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 2069 QString qTzName(int dstIndex) 2070 { 2071 char name[512]; 2072 bool ok; 2073 #if defined(Q_CC_MSVC) 2074 size_t s = 0; 2075 { 2076 const auto locker = std::scoped_lock(environmentMutex); 2077 ok = _get_tzname(&s, name, 512, dstIndex) != 0; 2078 } 2079 #else 2080 { 2081 const auto locker = std::scoped_lock(environmentMutex); 2082 const char *const src = tzname[dstIndex]; 2083 ok = src != nullptr; 2084 if (ok) 2085 memcpy(name, src, std::min(sizeof(name), strlen(src) + 1)); 2086 } 2087 #endif // Q_OS_WIN 2088 return ok ? QString::fromLocal8Bit(name) : QString(); 2089 } 2090 2091 int QDateTimeParser::startsWithLocalTimeZone(QStringView name, const QDateTime &when) 2092 { 2093 // On MS-Win, at least when system zone is UTC, the tzname[]s may be empty. 2094 for (int i = 0; i < 2; ++i) { 2095 const QString zone(qTzName(i)); 2096 if (!zone.isEmpty() && name.startsWith(zone)) 2097 return zone.size(); 2098 } 2099 // Mimic what QLocale::toString() would have used, to ensure round-trips 2100 // work: 2101 const QString local = QDateTime(when.date(), when.time()).timeZoneAbbreviation(); 2102 if (name.startsWith(local)) 2103 return local.size(); 2104 return 0; 2105 } 2106 #endif 2107 2108 /* 2109 \internal 2110 2111 I give arg2 preference because arg1 is always a QDateTime. 2112 */ 2113 2114 bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2) 2115 { 2116 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count); 2117 } 2118 2119 QT_END_NAMESPACE