Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#ifndef QTTEMPORALPATTERN_P_H
#define QTTEMPORALPATTERN_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an implementation
// detail. This header file may change from version to version without notice,
// or even be removed.
//
// We mean it.
//
#include <QtCore/qcalendar.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qflags.h>
#include <QtCore/qlist.h>
#include <QtCore/qlocale.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qspan.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringview.h>
#include <optional>
QT_REQUIRE_CONFIG(datestring);
QT_BEGIN_NAMESPACE
namespace QtTemporalPattern {
// Model the LDML spec's range of possible fields, see QTBUG-70516 and
enum class TemporalFieldCategory : quint8 {
Literal = 0,
// Special:
TimeZone = 4,
// Time:
// MillisecondInDay = 17,
Second = 20, SecondFraction = 21,
Minute = 24, // MinuteFraction = 25,
HourMod12 = 30, Hour = 31, // HourFraction = 32,
PeriodInDay = 33, // am/pm; LDML also has noon, midnight, "at night" and others.
// Date:
DayOfWeek = 64, DayOfMonth = 65, // DayOfYear = 68, JulianDay = 69,
// WeekOfMonth = 72,
// WeekOfYear = 73, // QTBUG-57212, QTBUG-83678, QTBUG-117055
Month = 80,
// Quarter = 84,
// RelatedGregorianYear = 90, Century = 91,
YearWithinCentury = 92, Year = 93,
// Era = 100,
EndCategories
};
constexpr inline
auto EndTemporalFieldCategories = qToUnderlying(TemporalFieldCategory::EndCategories);
enum class TemporalFieldFlag : quint32 {
Numeric = 1, Verbal = 2, Standalone = 4,
Narrow = 0x10, Abbreviated = 0x20, Short = 0x40, Wide = 0x80,
ZeroPad = 0x0100, // For TimeZone, ZeroPad applies only to hour fields.
SpacePad = 0x0200, FlexSpace = 0x0400, StrictSpace = 0x0800,
// Qt used to impose case on am/pm fields:
LowerCase = 0x1000, UpperCase = 0x2000, // Otherwise follow locale-supplied case.
IgnoreCase = 0x4000, // Parsing Literal or Verbal: match case insensitively
// (0x8000 free)
// Special cases:
RoundFraction = 0x01'0000, // Fractional part accepts extra digits, rounding suitably
// (0x02'0000, 0x04'0000 free)
YearSignIso8601 = 0x08'0000, // Require sign when excess digits
// Zone-specific:
LocalizedZone = 0x10'0000, // Localized forms, various.
Iso8601 = 0x20'0000, // Non-localized standard offset forms.
AcceptUtcPrefix = 0x40'0000, NeedNoUtcPrefix = 0x80'0000, // On offsets
AllowZSuffix = 0x0100'0000, // Iso8601 zero-offset may be indicated by Z.
// Time-types (if none specified, infer time type from date/time fields):
GenericTime = 0x0200'0000, StandardTime = 0x0400'0000, DaylightSavingTime = 0x0800'0000,
// (0x1000'0000, 0x2000'0000, 0x4000'0000 free)
// Local time from system info:
LocalTimeName = 0x8000'0000 // That's the last bit available to us.
};
Q_DECLARE_FLAGS(TemporalFieldFlags, TemporalFieldFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(TemporalFieldFlags)
namespace FieldGroup {
constexpr TemporalFieldFlags FormMask = TemporalFieldFlag::Numeric
| TemporalFieldFlag::Verbal | TemporalFieldFlag::Standalone;
constexpr TemporalFieldFlags WidthMask
= TemporalFieldFlag::Narrow | TemporalFieldFlag::Abbreviated
| TemporalFieldFlag::Short | TemporalFieldFlag::Wide;
constexpr TemporalFieldFlags PaddingMask
= TemporalFieldFlag::ZeroPad | TemporalFieldFlag::SpacePad;
constexpr TemporalFieldFlags LocalizationMask
= TemporalFieldFlag::LocalizedZone | TemporalFieldFlag::Iso8601;
constexpr TemporalFieldFlags UtcPrefixMask
= TemporalFieldFlag::AcceptUtcPrefix | TemporalFieldFlag::NeedNoUtcPrefix;
constexpr TemporalFieldFlags SeasonMask = TemporalFieldFlag::GenericTime
| TemporalFieldFlag::StandardTime | TemporalFieldFlag::DaylightSavingTime;
}
// Implement group-related tests:
constexpr inline
bool matchesFlagWithin(QtTemporalPattern::TemporalFieldFlags flags,
QtTemporalPattern::TemporalFieldFlag sought,
QtTemporalPattern::TemporalFieldFlags group)
{
Q_ASSERT(group.testFlag(sought)); // In case caller passed the wrong group.
return flags.testFlag(sought) || !flags.testAnyFlags(group);
}
constexpr inline
bool matchesFlagsWithin(QtTemporalPattern::TemporalFieldFlags flags,
QtTemporalPattern::TemporalFieldFlags sought,
QtTemporalPattern::TemporalFieldFlags group)
{
Q_ASSERT(!(sought & ~group)); // i.e. all of sought are in group.
return flags.testAnyFlags(sought) || !flags.testAnyFlags(group);
}
// Classify the categories:
enum class DateTimePart { None, Date = 1, Time = 2, Zone = 4 };
Q_DECLARE_FLAGS(DateTimeParts, DateTimePart)
Q_DECLARE_OPERATORS_FOR_FLAGS(DateTimeParts)
#ifdef QT_BUILD_INTERNAL
Q_NAMESPACE_EXPORT(Q_CORE_EXPORT)
Q_FLAG_NS(TemporalFieldFlags)
Q_FLAG_NS(DateTimeParts)
#endif // For testing.
inline DateTimePart classify(TemporalFieldCategory category) noexcept
{
if (category == TemporalFieldCategory::Literal)
return DateTimePart::None;
if (quint8(category) < 16)
return DateTimePart::Zone;
if (quint8(category) < 64) // a.k.a. 0x40
return DateTimePart::Time;
return DateTimePart::Date;
}
// Combine with related information:
struct TemporalField {
QString literal; // Only relevant to Literal
qsizetype width; // Lower bound, usually only relevant to Numeric.
TemporalFieldFlags options;
TemporalFieldCategory category;
DateTimePart part() const noexcept { return classify(category); }
friend bool comparesEqual(const TemporalField &lhs, const TemporalField &rhs) noexcept
{
return lhs.literal == rhs.literal
&& lhs.width == rhs.width
&& lhs.options == rhs.options
&& lhs.category == rhs.category;
}
Q_DECLARE_EQUALITY_COMPARABLE(TemporalField)
};
inline DateTimeParts hasFieldsFor(QSpan<const TemporalField> range)
{
DateTimeParts result = DateTimePart::None;
for (const TemporalField &it : range)
result |= it.part();
return result;
}
enum class SupportType { Partial = -1, None = 0, Clear = 1, HasStrays };
Q_CORE_EXPORT
SupportType supports(DateTimeParts wanted, QSpan<const TemporalField> range,
bool hasBaseYear = false) noexcept;
} // namespace QtTemporalPattern
/* We may eventually want to make the following public API (along with a
forward-declaration of QtTemporalPattern::TemporalField), while leaving the
above in a private header. See QTBUG-70516, QTBUG-81056.
This may, of course, involve splitting the types below into a public facade
with the actual data members hidden from view behind a shared d-pointer to a
private internal class.
Please bear that in mind when considering the design of the APIs below, and
any future changes thereto.
*/
namespace QtTemporalPattern {
template <typename Payload>
struct ParseResult
{
Payload payload;
qsizetype size = 0;
};
} // namespace QtTemporalPattern
class QDateTimePattern
{
using Field = QtTemporalPattern::TemporalField;
QList<Field> m_fields;
QLocale m_locale;
QCalendar m_calendar;
std::optional<int> m_baseYear;
explicit QDateTimePattern(const QList<Field> &fs) : m_fields(fs) {}
explicit QDateTimePattern(const QList<Field> &fs, int centuryStart)
: m_fields(fs), m_baseYear(centuryStart) {}
QtTemporalPattern::SupportType dateTimeSupport() const noexcept
{
using namespace QtTemporalPattern;
constexpr DateTimeParts NeededParts = DateTimePart::Date | DateTimePart::Time;
const DateTimeParts got = hasFieldsFor(m_fields);
// Must have date and time (zone optional) and support what it has:
if (got.testFlags(NeededParts))
return supports(got, m_fields, m_baseYear.has_value());
return got ? SupportType::Partial : SupportType::None;
}
public:
bool isNull() const noexcept { return m_fields.isEmpty(); }
bool isValid() const noexcept
{
return dateTimeSupport() == QtTemporalPattern::SupportType::Clear;
}
void setLocale(const QLocale &loc) { m_locale = loc; }
const QLocale &locale() const noexcept { return m_locale; }
void setBaseYear(int centuryStart) { m_baseYear = centuryStart; }
void clearBaseYear() noexcept { m_baseYear = std::nullopt; }
std::optional<int> baseYear() const noexcept { return m_baseYear; }
void setCalendar(QCalendar cal) { m_calendar = cal; }
QCalendar calendar() const noexcept { return m_calendar; }
Q_CORE_EXPORT QtTemporalPattern::ParseResult<QDateTime>
parse(QStringView text, const QDateTime &defaults = {}) const;
Q_CORE_EXPORT QString serialize(const QDateTime &datetime) const;
static Q_CORE_EXPORT QDateTimePattern fromQtFormat(QStringView format);
static
QDateTimePattern forLocale(const QLocale &locale,
QLocale::FormatType format = QLocale::LongFormat)
{
auto pat = fromQtFormat(locale.dateTimeFormat(format));
pat.setLocale(locale);
return pat;
}
};
class QTimePattern
{
using Field = QtTemporalPattern::TemporalField;
QList<Field> m_fields;
QLocale m_locale;
explicit QTimePattern(const QList<Field> &fs) : m_fields(fs) {}
QtTemporalPattern::SupportType timeSupport() const noexcept
{
using namespace QtTemporalPattern;
return supports({DateTimePart::Time}, m_fields);
}
public:
bool isNull() const noexcept { return m_fields.isEmpty(); }
bool isValid() const noexcept
{
return timeSupport() == QtTemporalPattern::SupportType::Clear;
}
void setLocale(const QLocale &loc) { m_locale = loc; }
const QLocale &locale() const noexcept { return m_locale; }
Q_CORE_EXPORT QtTemporalPattern::ParseResult<QTime>
parse(QStringView text, QTime defaults = {}) const;
Q_CORE_EXPORT QString serialize(const QTime &time) const;
static Q_CORE_EXPORT QTimePattern fromQtFormat(QStringView format);
static
QTimePattern forLocale(const QLocale &locale, QLocale::FormatType format = QLocale::LongFormat)
{
auto pat = fromQtFormat(locale.timeFormat(format));
pat.setLocale(locale);
return pat;
}
};
class QDatePattern
{
using Field = QtTemporalPattern::TemporalField;
QList<Field> m_fields;
QLocale m_locale;
QCalendar m_calendar;
std::optional<int> m_baseYear;
explicit QDatePattern(const QList<Field> &fs) : m_fields(fs) {}
explicit QDatePattern(const QList<Field> &fs, int centuryStart)
: m_fields(fs), m_baseYear(centuryStart) {}
QtTemporalPattern::SupportType dateSupport() const noexcept
{
using namespace QtTemporalPattern;
return supports({DateTimePart::Date}, m_fields, m_baseYear.has_value());
}
public:
bool isNull() const noexcept { return m_fields.isEmpty(); }
bool isValid() const noexcept
{
return dateSupport() == QtTemporalPattern::SupportType::Clear;
}
void setLocale(const QLocale &loc) { m_locale = loc; }
const QLocale &locale() const noexcept { return m_locale; }
void setBaseYear(int centuryStart) { m_baseYear = centuryStart; }
void clearBaseYear() noexcept { m_baseYear = std::nullopt; }
std::optional<int> baseYear() const noexcept { return m_baseYear; }
void setCalendar(QCalendar cal) { m_calendar = cal; }
QCalendar calendar() const noexcept { return m_calendar; }
Q_CORE_EXPORT QtTemporalPattern::ParseResult<QDate>
parse(QStringView text, QDate defaults = {}) const;
Q_CORE_EXPORT QString serialize(const QDate &date) const;
static Q_CORE_EXPORT QDatePattern fromQtFormat(QStringView format);
static
QDatePattern forLocale(const QLocale &locale, QLocale::FormatType format = QLocale::LongFormat)
{
auto pat = fromQtFormat(locale.dateFormat(format));
pat.setLocale(locale);
return pat;
}
};
QT_END_NAMESPACE
#endif // QTTEMPORALPATTERN_P_H