41 using namespace QtTemporalPattern;
43 ParsedDateTimeFormat result;
44 constexpr char32_t Invalid = ~
char32_t(0);
45 static_assert(Invalid > QChar::LastValidCodePoint);
46 const bool includeDate = form.testFlag(DateTimePart::Date);
47 const bool includeTime = form.testFlag(DateTimePart::Time);
48 const bool includeZone = form.testFlag(DateTimePart::Zone);
50 QStringIterator iter(pattern);
52 const auto countRepeats = [&pending, &iter, &result](
char32_t first, qsizetype bound) {
54 Q_ASSERT(!QChar::requiresSurrogates(first));
55 Q_ASSERT(pending == 0);
57 result.endIndex = iter.index();
58 while (iter.hasNext() && count < bound) {
59 const auto read = iter.next(Invalid);
60 if (read > QChar::LastValidCodePoint) {
69 result.endIndex = iter.index();
74 constexpr char32_t SingleQuote = U'\'';
75 static constexpr auto matchTimeFormats = QtPrivate::makeCharacterSetMatch<timeFormats>();
76 static constexpr auto matchDateFormats = QtPrivate::makeCharacterSetMatch<dateFormats>();
77 const auto isFormatChar = [includeDate, includeTime, includeZone](
char32_t ch) {
81 if (matchTimeFormats.matches(uchar(ch)) || ch == U'A' || ch == U'a')
84 if (includeZone && ch == U't')
86 return includeDate && matchDateFormats.matches(uchar(ch));
89 constexpr auto formatCategory = [](uchar ch,
int count) {
90 using Cat = TemporalFieldCategory;
94 return Cat::PeriodInDay;
95 case 'd':
return count < 3 ? Cat::DayOfMonth : Cat::DayOfWeek;
96 case 'H':
return Cat::Hour;
97 case 'h':
return Cat::HourMod12;
98 case 'M':
return Cat::Month;
99 case 'm':
return Cat::Minute;
100 case 's':
return Cat::Second;
101 case 't':
return Cat::TimeZone;
102 case 'y':
return count < 3 ? Cat::YearWithinCentury : Cat::Year;
103 case 'z':
return Cat::SecondFraction;
106 Q_UNREACHABLE_RETURN(Cat::Literal);
108 constexpr auto formatFlags = [](uchar ch,
int count) -> TemporalFieldFlags {
109 using F = TemporalFieldFlag;
110 constexpr TemporalFieldFlags TextCommon = F::IgnoreCase;
113 return count ? F::UpperCase | TextCommon : TextCommon;
115 return count ? F::LowerCase | TextCommon : TextCommon;
118 case 2:
return F::Numeric | F::ZeroPad;
119 case 3:
return F::Verbal | F::Abbreviated | TextCommon;
121 return count < 2 ? F::Numeric : F::Verbal | F::Wide | TextCommon;
125 case 'H':
case 'h':
case 'm':
case 's':
126 return count > 1 ? F::Numeric | F::ZeroPad : F::Numeric;
130 return F::AllowZSuffix | F::LocalTimeName;
136 return F::Iso8601 | F::Numeric | F::ZeroPad;
138 return F::Iso8601 | F::Verbal | F::ZeroPad;
140 return F::LocalizedZone | F::Verbal | F::Standalone | F::Wide | F::Short
148 return F::Numeric | F::ZeroPad | F::YearSignIso8601;
149 return F::Numeric | F::ZeroPad;
152 return F::Numeric | F::ZeroPad;
156 Q_UNREACHABLE_RETURN({});
159 const auto store = [&result](QString &&literal, qsizetype count,
160 TemporalFieldFlags flags,
161 TemporalFieldCategory category) {
162 result.fields.append(TemporalField{std::move(literal), count, flags, category});
165 bool seenDayPeriod =
false, seenHourMod12 =
false;
166 while (pending <= QChar::LastValidCodePoint && (pending || iter.hasNext())) {
169 ch = std::exchange(pending, 0);
171 result.endIndex = iter.index();
172 ch = iter.next(Invalid);
174 if (ch > QChar::LastValidCodePoint)
177 if (ch < 0x80 && includeTime) {
178 if (matchTimeFormats.matches(uchar(ch))) {
179 qsizetype count = countRepeats(ch, ch == U'z' ? 3 : 2);
180 if (ch == U'z' && count == 2)
182 store(QString(), count, formatFlags(uchar(ch), count),
183 formatCategory(uchar(ch), count));
185 seenHourMod12 =
true;
188 if (ch == U'A' || ch == U'a') {
190 qsizetype count = ch == U'a' ? 1 : 2;
193 result.endIndex = iter.index();
194 const auto read = iter.hasNext() ? iter.next(Invalid) : Invalid;
195 if (read > QChar::LastValidCodePoint) {
197 }
else if (read == U'P') {
200 result.endIndex = iter.index();
201 }
else if (read == U'p') {
204 result.endIndex = iter.index();
208 store(QString(), count, formatFlags(uchar(ch), count),
209 formatCategory(uchar(ch), count));
210 seenDayPeriod =
true;
215 if (ch == U'y' && includeDate) {
225 QStringView tail = pattern.sliced(iter.index() - 1);
227 tail = tail.first(4);
228 while (count < tail.size() &&
char32_t(tail[count].unicode()) == ch)
233 Q_ASSERT(count == 2 || count == 4);
235 iter.setPosition(iter.position() - 1 + count);
236 store(QString(), count, formatFlags(uchar(ch), count),
237 formatCategory(uchar(ch), count));
238 result.endIndex = iter.index();
242 }
else if (ch < 0x80 && ((includeDate && matchDateFormats.matches(uchar(ch)))
243 || (includeZone && ch == U't'))) {
244 qsizetype count = countRepeats(ch, 4);
245 store(QString(), count, formatFlags(uchar(ch), count),
246 formatCategory(uchar(ch), count));
249 Q_ASSERT(pending == 0);
254 if (ch == SingleQuote) {
257 literal = QString(QStringView(QChar::fromUcs4(ch)));
258 result.endIndex = iter.index();
260 while (pending <= QChar::LastValidCodePoint && (pending || iter.hasNext())) {
262 ch = std::exchange(pending, 0);
265 result.endIndex = iter.index();
266 ch = iter.next(Invalid);
268 if (ch > QChar::LastValidCodePoint)
271 if (ch == SingleQuote) {
272 if (quote.isNull()) {
279 result.endIndex = iter.index();
282 ch = iter.hasNext() ? iter.next(Invalid) : Invalid;
283 if (ch == SingleQuote) {
285 if (quote.isNull()) {
292 Q_ASSERT(quote.isEmpty());
294 literal.append(u'\'');
295 result.endIndex = iter.index();
300 if (ch > QChar::LastValidCodePoint) {
306 Q_ASSERT(ch != SingleQuote);
307 if (quote.isNull() && isFormatChar(ch)) {
311 if (ch <= QChar::LastValidCodePoint) {
312 if (quote.isNull()) {
313 result.endIndex = iter.index();
314 literal.append(QStringView(QChar::fromUcs4(ch)));
316 quote.append(QStringView(QChar::fromUcs4(ch)));
322 if (!literal.isEmpty())
323 store(std::move(literal), 0, {}, TemporalFieldCategory::Literal);
326 Q_ASSERT(quote.isNull() || !pending || pending > QChar::LastValidCodePoint);
331 if (seenHourMod12 && !seenDayPeriod) {
332 for (TemporalField &field : result.fields) {
333 if (field.category == TemporalFieldCategory::HourMod12)
334 field.category = TemporalFieldCategory::Hour;