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
#include "private/qttemporalpattern_p.h"
#include "private/qlocale_p.h"
#include "private/qtparseqttemporalformat_p.h"
#if QT_CONFIG(datetimeparser)
# include "private/qtparsetemporal_p.h"
#endif
#include <bitset>
QT_BEGIN_NAMESPACE
namespace QtTemporalPattern {
/*!
\internal
\since 6.12
\namespace QtTemporalPattern
\brief Supporting types and functions for temporal patterns.
Temporal patterns describe how a date, time or datetime may be serialized as
text or parsed from text. This namespace provides the common tools used by
the classes describing such patterns: \l QDateTimePattern, \l QTimePattern
and \l QDatePattern.
*/
/* Note (QTBUG-70516): For now (Qt 6.12) it suffices to cover everything that
the existing Qt datetime format strings are capable of. However, the intent
is to eventually expand this to fully support CLDR's datetime formats so that
we can switch from converting those to Qt formats when we scan CLDR to
actually storing the CLDR format in the qlocale_data_p.h tables and
constructing our Q(Date|Time)+Pattern objects from those formats. See comment
in header for a link to the relevant parts of LDML.
*/
/*!
\enum QtTemporalPattern::TemporalFieldCategory
This enumeration characterizes the information supplied by a field.
For the details of how that information is conveyed, see \l
{QtTemporalPattern::}{TemporalFieldFlag}. For the classification of fields
by whether they specify date, time or zone, see \l
{QtTemporalPattern::}{DateTimePart}.
\value Literal Describes a text fragment that frames the data, such as
separators and delimiters.
\value TimeZone Identifies the timezone or offset from UTC of a datetime.
\value SecondFraction The fraction of a second (an optional time field).
\value Second The second within the minute (an optional time field).
\value Minute The minute within the hour (a time field).
\value PeriodInDay A subdivision of the day, such as before noon vs after
noon (a time field). May serve to disambiguate the hour
when specified only modulo 12. (Currently only the am/pm
distinction is supported.)
\value HourMod12 The hour in the range 1 through 12 (a time field). May be
ambiguous on its own.
\value Hour The hour in the range from 0 through 23 (a time field).
\value DayOfWeek The day within the week (a date field). Usually specified
by name. Some locales may use numbers for their Narrow
Verbal and/or Standalone forms. No form is supported for
Numeric | DayOfWeek.
\value DayOfMonth The day number within the month (a date field). Runs from
1 for the first day of the month up to the length of the
month. Always numeric, regardless of field flags.
\value Month The month within the year (a date field). When specified
numerically, the first month of the year is numbered 1; there
is no month 0. Some locales may use numbers for their Narrow
Verbal form.
\value YearWithinCentury The two-digit year number (a date field). Widely
used in date formats, despite its potential for
ambiguity. May be disambiguated by other fields or
by specifying base year for the hundred years
presumed when only two digits are given and other
fields don't disambiguate.
\value Year The full year number (a date field).
Fields identified as numbers, along with hour, minute, second and fraction
of second, are always given in numeric form (regardless of Numeric, Verbal
or Standalone flags). Where a field is given in numeric form, its field
width is the smallest number of digits that can match it. Further digits are
accepted, up to the greatest number of significant digits that can appear in
a valid value for it. (If the field width exceeds this, fields of this width
are accepted, provided the numeric value is valid. This may require
zero-padding.)
The SecondFraction field describes the digits immediately following the
fractional-part separator in a decimal representation of the seconds with
fractional part. It is up to the format to position it suitably in relation
to a Second field and a Literal field for the separator. Leading zeros are
significant and not considered to be padding: trailing zeros are understood
as padding.
As QTime and QDateTime only handle times to millisecond precision, normally
their allowed range of values runs from 0 to 999 and no more than three
digits are accepted, or the field width if greater. If the \l
{QtTemporalPattern::TemporalFieldFlag} {RoundFraction} flag is passed,
digits beyond this are accepted and will affect the result only via
appropriate rounding. (If fewer than three digits are parsed, the value is
implicitly extended on the right with zeros to obtain milliseconds
precision. Whether such a field is accepted will depend on the setting of
the \c width of the field and the absence of \l
{QtTemporalPattern::TemporalFieldFlag} {ZeroPad} from its \l
{QtTemporalPattern::TemporalField} {\c options}, in the usual ways.)
Where a SecondFraction field accepts more than three digits (either because
its field width exceeds three or because the RoundFraction flag is
specified), the resulting fractional part is rounded to three significant
digits. If rounding would require increasing the Second field, however, the
Second field is retained unchanged and the fractional part is rounded to 999
milliseconds. For example, 29.9997 seconds is rounded to 29.999 seconds, to
preserve the fact that the time is strictly before the 30 second mark.
\note For negative years, the YearWithinCentury will be understood as the
number of completed years since the start of the most recent year that is a
multiple of 100. Thus year -1 (which Qt understands, when using the
Gregorian calendar, to mean 1 BCE) has a YearWithinCentury value of 99,
since the last year that's a multiple of 100 is -100 (for Gregorian, 100
BCE).
\omitvalue EndCategories
\sa {QtTemporalPattern::}{TemporalFieldFlag}, {QtTemporalPattern::}{DateTimePart}
*/
/* For numbers as narrow days of the week, at least three different numberings
are used, all of which are used in locales whose "first day of the week" is
Monday. (Furthermore, some locales with digits as narrow names only do that
for some days, with letters for others.) So there is no way to determine how
days of the week are numbered from one locale to the next, except in so far
as their narrow Verbal or Standalone forms use digits. A Numeric version of
the day of the week field would thus either not know what to do for most
locales, be inconsistent between locales or be inconsistent, for some
locales, between Narrow Verbal / Standalone formats and Numeric format.
Hence the lack of a meaning of Numeric for DayOfWeek.
Some locales use month numbers for the narrow format of months; but none are
known to do so inconsistently with the usual month numbering (although Pashto
does this only partially, using non-numeric narrow standalone forms for the
first two months). This does still conflict with the numeric form of months,
at least in some cases, that use ASCII digits for narrow forms of month
numbers but locale-appropriate digits for their numeric forms. However, the
conflict is only in how the numbers are represented, not in the numeric
values used for months.
*/
/*!
\internal
\namespace QtTemporalPattern::FieldGroup
\brief Masks identifying mutually-exclusive families of options.
Each constant in this namespace is of type \l {QtTemporalPattern::}
{TemporalFieldFlags} and combines a mutually-exclusive family of \l
{QtTemporalPattern::} {TemporalFieldFlag} values into a mask by which to
identify that family. Within each such family, if none of the flags is
specified, any of them may be applied but if any of them are specified then
only one of those specified may be applied.
\value FormMask The three general forms of a field: numeric, verbal and
standalone.
\value WidthMask The four field widths: wide, short, abbreviated, and
narrow.
\value UtcPrefixMask The two options for prefixes on offset forms of
timezones, with and without a UTC prefix.
\value PaddingMask The two padding options for when fields don't fill the
width allotted to them: spaces and zeros.
\value SeasonMask The three perspectives from which to describe a timezone:
the generic, regardless of the time of year, the standard,
and how it's described when exercising daylight-saving
time (if it does at all).
*/
/* Namespace QtParseCommon has helper functions, defined in qtparsetimezone_p.h,
to apply the above rule related to these masks.
*/
/*!
\enum QtTemporalPattern::TemporalFieldFlag
This enumeration describes how datetime fields are expressed in text.
A combination of these flags is used to qualify a \l {QtTemporalPattern::}
{TemporalFieldCategory}, indicating whether it is conveyed by words or
numbers, how tersely or in which terms, depending on the field
category. Interpretation of a flag in this enumeration varies with the field
category it is qualifying and with other flags combined with it.
Some flags, such as the four width options, are mutually exclusive. For each
group of mutually-exclusive flags, a mask constant is defined in namespace
QtTemporalPattern::FieldGroup, of type \l {QtTemporalPattern::}
{TemporalFieldFlags}, that combines the members of that group. If no member
of such a group is set in a flags variable, it is treated as if all members
of that group were set. When serializing, the behavior when two or more
conflicting flags, in a group relevant to the field, are combined is
unspecified. It may depend on other fields or variables passed to the
serialization function. When parsing, such conflicting flags allow the
parser to match any of the conflicting options specified. If this leads to
ambiguity, parsing prefers the option that consumes more of the text to
parse.
Unless otherwise indicated, fields are localized. For example, ZeroPad pads
with, and Numeric uses, locale-appropriate digits when localized, and the
names of months, days of the week and timezones are translated into the
appropriate language (if possible).
The primary distinction in how fields are expressed is the form of the
field. These first three options are mutually incompatible and are grouped
together as the \l {QtTemporalPattern::FieldGroup::} {FormMask} constant:
\value Numeric Express the field numerically as a series of digits.
\value Verbal Name the field value in its usual in-format grammatical form.
\value Standalone Name the field value in its stand-alone grammatical form.
Next comes the width of the field. The meanings of these fields depends on
other flags and the field category, where relevant. Some field categories
may ignore these entirely, others may draw fewer distinctions and use the
same meaning for some widths. These four options are mutually exclusive and
grouped together as the \l {QtTemporalPattern::FieldGroup::} {WidthMask}
constant:
\value Narrow Use a the narrowest supported form for the field. In some
locales, the Narrow Verbal forms of some fields may use
numbers, potentially conflicting with one of its Numeric
forms.
\value Abbreviated Use an abbreviated form of the field.
\value Short Use a short form of the field.
\value Wide Use a wide form of the field.
\section2 Padding
Where a \c width is specified for a field but the field's value is naturally
shorter, it is necessary to indicate how to pad it to the desired width.
These two options are mutually exclusive and grouped together as the \l
{QtTemporalPattern::FieldGroup::} {PaddingMask} constant:
\value ZeroPad Only for numeric fields: pad to \c width with zeros. When
parsing, accept zeros that don't affect value, reject fields
that are narrower than their specified \c width.
\value SpacePad For a field with a positive \c width, pad to that \c width
with spaces. When parsing, allow leading and trailing
spacing characters.
If neither form of padding is indicated, the natural representation of a
value is used even if it fails to reach the \c width specified and parsing
will accept a narrower field (although it will prefer a match with full
width). In a \l {QtTemporalPattern::TemporalFieldCategory} {TimeZone} field
(see below) using an offset form, SpacePad is ignored and ZeroPad or its
absence only has its usual meaning for hour fields, while controlling the
presence of zero minute and second offsets following the hour field.
\section2 Space
Where a text to be matched (for example, a literal or the name of a month or
day of the week) contains spaces, by default plain space (ASCII SPACE,
U+0020) and the various Unicode spacing characters outside the ASCII range
(of various widths, some of them non-breaking) are all accepted as matches
for one another, while the number and placement of such short horizontal
spaces must match. Other spacing characters (newline, tab, etc.) must match
exactly. Since users commonly do not distinguish the short horizontal
spaces, this default usually suffices. In some cases, there may be reason to
insist on exact matching of spaces, without treating short horizontal spaces
as equivalent. Where the text to be parsed is taken from a larger text,
however, it's also possible that this larger text has been flowed, as a
paragraph, which may have turned some spaces into line breaks, possibly with
added indentation, or the text may be split across a page boundary. Coping
with such cases is supported by the options
\value FlexSpace Where a field to be matched contains spacing characters, or
a run of them, any spacing character or run of them will be
accepted as matching. A character is deemed to be a spacing
character if \l QChar::isSpace() is true for it.
\value StrictSpace Spacing characters must be matched exactly.
Ignored if FlexSpace is also set.
\section2 Letter case
The following options are only relevant to Verbal and Standalone
fields. They are not treated as a group or descrbed by a mask, as the locale
provides relevant fields with the appropriate (possibly mixed) case for the
lcoale. By default, the locale's form is used when serializing and matched
(case-sensitively) when parsing. The first two of these are mutually
exclusive. They change that default, forcing the case when serializing and
requiring the specified case (or one of the specified cases) when
parsing. The third has no effect when serializing and overrides the other
two, if either is present, when parsing.
\value LowerCase When serializing, force lower-case.
\value UpperCase When serializing, force upper-case.
\value IgnoreCase When parsing, match the field case-insensitively.
\section2 Extended fields
The following options modify the handling of specific fields where there is
no intrinsic limit to the number of significant digits in a valid value.
They are described in more detail below.
\value RoundFraction Allows excess precision in a fractional part.
\value YearSignIso8601 Requires a sign if there are extra digits in a year.
By default a \l {QtTemporalPattern::TemporalFieldCategory} {SecondFraction}
field only accepts up to three digits or as many digits as its field width
specifies. The RoundFraction modifies the field to allow more digits than
this, using the excess precision only to resolve rounding to the appropriate
precision, as described above for the SecondFraction field.
Modern revisions of ISO 8601 permit years outside the range from 0 through
9999 but require that they have a sign. By default a + sign on a positive
year is allowed and silently ignored (without counting towards its \c
width), but the YearSignIso8601 applies a modified version of the ISO rule:
the year field will only match a text with more digits than its specified \c
width if that text starts with a sign. When the field's \c width is 4, this
implements the ISO rule, but it can be applied to other widths, if needed.
In contrast to ISO's use of year 0000 to indicate 1 BCE, with negative year
values representing successively earlier years, where the calendar in use
has no year zero, Qt describes the year before year 1 as year -1, skipping
over 0 and treating 0 as an invalid value for the year. In particular, this
applies to the Gregorian calendar, which is used by default: 1 CE is
represented as year 1, with 1 BC as year -1. If the calendar in use reports
\c true from \l {QCalendar::} {hasYearZero()}, Qt duly accepts a year 0
between years -1 and +1. ISO also specifies that years before 1583 (the
first full year after the Gregorian calendar came into play) or after 9999
should not be used except by prior agreement between the producer and
consumer of the serialized date or datetime. Qt leaves such agreement as a
matter between the user and those they communicate with, simply accepting
any year number within the range of QDate or QDateTime, as appropriate.
\section2 Timezone representation
The following options are only relevant to timezones. If used with other
categories of field, they are ignored. By default, when the other fields of
a timestamp determine a datetime, the zone's localized name in effect at
that datetime is used when serializing and matched when parsing.
There are both localized and international standard formats available. The
following pair of options select which of those to use. They are grouped
together as the \l {QtTemporalPattern::FieldGroup::} {LocalizationMask}
constant:
\value LocalizedZone For a TimeZone, use localized forms.
\value Iso8601 For a TimeZone, use an ISO 8601 offset format.
When both are specified, or neither is, localized forms are preferred. See
\l {Locale-independent offset forms}, below, for details of the ISO
8601-based formats. See \c{Standalone | Short} below for the IANA DB and
\l{Local time} for a system-dependent form, both of which ignore the choice
between LocalizedZone and Iso8601.
The following three options, selecting seasonal variations, are mutually
exclusive and only relevant to Verbal or Standalone forms. They are
independent of the choice between LocalizedZone and Iso8601. They are
grouped together as the \l {QtTemporalPattern::FieldGroup::} {SeasonMask}
constant:
\value GenericTime For a non-Numeric TimeZone, use its generic name.
\value StandardTime For a non-Numeric TimeZone, use its standard-time name.
\value DaylightSavingTime For a non-Numeric TimeZone, use its
daylight-saving name.
For localized timezones, Numeric selects a basic offset format or an offset
format with a base prefix (typically a localized form of UTC or GMT), Verbal
selects a form of the zone's name, and Standalone selects the IANA ID or a
localized name based on a city that serves as exemplar for the zone (for the
given locale). The effects of the width options above then depend on which
of these is used. When LocalizedZone is set, the following forms are
available:
\list
\li For Numeric, the timezone offset is used:
\list
\li Wide uses the hour, minute and second, as for Short, but when
parsing it will accept a fractional part of the seconds (following
the locale-appropriate separator, after the whole number part of
the seconds), if present. As Qt only supports whole numbers of
seconds as offsets, this fractional part's only effect is to round
up the second part if it is at least a half second. (See note on
rounding in the next section.)
\li Short uses the hour, minute and second, rounding to the nearest
second if the offset is not a whole number of seconds.
\li Abbreviated uses the hour and minute, rounding to the nearest
minute if the offset is not a whole number of minutes.
\li Narrow uses the hour, rounding to nearest if the actual offset is
not a whole number of hours.
\endlist
\li For Verbal, the zone name is used:
\list
\li Wide uses the full name of the zone, e.g. "Pacific Time", "Pacific
Standard Time" or "Pacific Daylight Time", depending on season.
\li Short is treated as Wide.
\li Abbreviated uses the zone abbreviation, e.g. "PT", "PST" or "PDT".
Note that these cannot be reliably parsed.
\li Narrow is treated as Abbreviated.
\endlist
\li For Standalone, the city name:
\list
\li Wide uses the locale's generic location format, such as "Los
Angeles Time".
\li Short uses the full IANA ID, such as America/Los_Angeles.
This is not localized (the LocalizedZone flag is ignored).
\li Abbreviated just gives the localized exemplar city, e.g. "Los
Angeles".
\li Narrow is treated as Abbreviated.
\endlist
\endlist
Where a timezone is specified simply in terms of an offset from UTC, it does
not necessarily have an associated city or a name other than its offset
representations. For such zones, when serializing, Verbal and Standalone are
treated as Numeric, with the exception of \c{Staldalone | Short}, as the
IANA ID is not localized and this form, like an IANA ID, is accepted by the
QTimeZone(QByteArray) constructor. (Note that these do not include ZeroPad:
see the next section for its effect on offset forms.) When parsing,
therefore, these offset formats are accepted for the Verbal and Standalone
forms that would map to them for offset zones. This applies to \l
{QTimeZone::fromDurationAheadOfUtc()} lightweight time representations (for
which \l {QTimeZone::}{isUtcOrFixedOffset()} is true) as well as to those,
with a UTC-offset backend, constructed by QTimeZone(int) or, with an ID of
form \c{"UTC"} or \c{"UTC±HH:mm"}, by QTimeZone(QByteArray),
\section3 Offset modifiers
In the various timezone offset formats (both above and below), there are
potentially hour, minute and second parts of the offset, depending on the
width option selected. The minute and second parts, when present, always use
two digits, rendering the usual meaning of ZeroPad redundant. Instead, for
these parts, ZeroPad controls whether trailing zero parts are included.
When ZeroPad is set they are included when serializing and required to be
present when parsing. When ZeroPad is not set, they are omitted when
serializing and, if trailng parts are missing when parsing, they are taken
to have value zero. Zero parts are always accepted when parsing, even when
ZeroPad is not set.
The hour part of an offset is never omitted when serializing and is always
required when parsing. When ZeroPad is not set, when parsing offset formats
with separators between parts, a single-digit hour is accepted. (For the
localized offset formats above, whether there are separators between parts
depends on the locale.) When ZeroPad is not set, serialization only produces
a single-digit hour if there are no later parts (typically because they were
all zero, hence omitted). Otherwise, the hour part is always serialized with
two digits and parsing requires it to have two digits.
As ever, if more than one width is specified, any given is allowed. On
serializing, narrower formats are preferred unless they lose precision in
the offset that an allowed longer format would include. Thus \c{Narrow |
Short} would include only the hours for a whole-hour offset, but would
include hours, minutes and seconds for an hour-and-a-half offset (albeit
omitting the zero seconds unless ZeroPad is set). Where the width thus
selected requires rounding due to omitting a part of the offset that isn't
zero, exact halves round away from zero. On parsing, the longest allowed
form for which a match is found will be used.
Offset formats may also include a prefix (localized in the forms above) that
identifies the offset as being relative to UTC or GMT. The following pair of
mutually exclusive options control whether that is omitted or included.
They are grouped together as the \l {QtTemporalPattern::FieldGroup::}
{UtcPrefixMask} constant:
\value AcceptUtcPrefix Include a UTC prefix on offsets when serializing and
require it when parsing.
\value NeedNoUtcPrefix Leave off the UTC prefix when serializing and reject
it when parsing.
If neither or both of these are specified, serializing omits the prefix and
parsing permits it but does not require it.
\section3 Locale-independent offset forms
In addition to these localized forms, a timestamp may also represent its
zone in a locale-independent offset form. In effect, this use the C locale,
with ASCII digits 0 through 9, the ASCII + and - as signs, and the ASCII
colon as separator (where relevant). These are selected by the \c Iso8601
flag.
\value AllowZSuffix Modifies Iso8601 to allow use of a \c{Z} suffix to
denote a zero UTC offset. See RFC 9557 note below.
If AllowZSuffix is set and ZeroPad is not, serialization will use \c{Z} to
represent a zero offset. When ZeroPad is set, serialization ignores
AllowZSuffix. If AllowZSufix is set, regardless of ZeroPad, parsing will
accept a \c{Z} suffix as meaning zero offset.
The Iso8601 format is modified by other flags as follows (when not using
\c{Z} to represent a zero offset):
\list
\li With Numeric it uses a basic format (with no separators), with Verbal
or Standalone it uses separators between hours, minutes and seconds,
in so far as these appear.
\li The width options Wide, Short, Abbreviated and Narrow have the same
meanings as for Numeric localized offset formats, modified by the
ZeroPad option, as described above.
\endlist
\note In some contexts, notably where \l
indicator on a timestamp may be understood as giving context to the
information in which it appears, as opposed to only indicating the zone with
respect to which the timestamp itself is expressed. In such contexts, a
\c{Z} suffix, applied by \c AllowZSuffix, only indicates that the timestamp
itself is given with respect to UTC and does not give any context to the
information in which it appears. For contrast, in such contexts, a +00:00
suffix does indicate not only that the timestamp is given in UTC but also
that UTC is the relevant zone for the context of the information. Prior to
RFC 9557, some RFCs (incompatibly with ISO 8601) proposed using -00:00 to
indicate what RFC 9557 has specified as the meaning for a \c{Z}
suffix. Nothing in Qt attends to this distinction.
\section3 Local time
Where Qt can determine how the local system describes its local time, Qt has
no control over the form in which the system supplies it, nor does Qt know
whether, or how, it is localized, with the result that this representation
is system-dependent. As another system may be using a different local time,
or representing it differently, there is no guarantee that what one system
supplies for local time can be successfully understood on another system. To
opt in to using this system-dependent representation of local time, supply
the following option. Even then, when serializing, if the options given
permit any other available representation of the timezone, that is preferred
over this.
\value LocalTimeName Allow the system-supplied local time name to describe
the system's local time.
The system local time may come in separate forms for standard time and
daylight-saving time. When it does, the options above for selecting between
these affect LocalTimeName as usual, with GenericTime ignored.
\sa {QtTemporalPattern::}{TemporalFieldCategory}, {QtTemporalPattern::}{TemporalFieldFlags}
*/
// RFC 9557: see QTBUG-114172
// TemporalFieldFlags and DateTimeParts: docs taken care of automagically by
// QDoc, recognizing QFLAG() usage.
/*!
\fn constexpr bool QtTemporalPattern::matchesFlagWithin(QtTemporalPattern::TemporalFieldFlags flags, QtTemporalPattern::TemporalFieldFlag sought, QtTemporalPattern::TemporalFieldFlags group)
Tests \a flags for a \a sought flag within a \a group.
Implements the per-flag check for a single TemporalFieldFlag with respect to
its FieldGroup entry. If no flag in a group is given in \a flags, then all
flags in the group are allowed; otherwise, only the given flags in the group
match.
*/
/*!
\fn constexpr bool QtTemporalPattern::matchesFlagsWithin(QtTemporalPattern::TemporalFieldFlags flags, QtTemporalPattern::TemporalFieldFlags sought, QtTemporalPattern::TemporalFieldFlags group)
Similar to \l matchesFlagWithin(), but if any of those \a sought is found in
\a flags, or if none of group are, it's counted as a match.
In some contexts, some flags are treated as equivalent, so to check for what
they represent pass the equivalent flags |-joined as \a sought.
*/
/*!
\enum QtTemporalPattern::DateTimePart
\brief This enumeraction classifies the various field categories.
The classification addresses which parts of a datetime a field contributes
data to.
\value None A literal field contributes no data.
\value Date Various fields contribute to the date.
\value Time Various fields contribute to the time.
\value Zone Only the \l {QtTemporalPattern::TemporalFieldFlag::}{TimeZone}
field contributes information about the timezone.
These may be combined in a \l {QtTemporalPattern::}{DateTimeParts} to
express a set of parts that a set of fields might suffice to describe,
whether fully or only in part.
\sa {QtTemporalPattern::}{TemporalFieldCategory}, {QtTemporalPattern::}{supports()}
*/
/*!
\fn QtTemporalPattern::classify(QtTemporalPattern::TemporalFieldCategory category) noexcept
\brief Classify a field category according to the part of a datetime it contributes to.
This maps a \l {QtTemporalPattern::}{TemporalFieldCategory} to the \l
{QtTemporalPattern::}{DateTimePart} for which it supplies data.
*/
/*!
\class QtTemporalPattern::TemporalField
\brief Describes one field in a temporal pattern.
A single field is characterized by its \l
{QtTemporalPattern::TemporalFieldCategory}{category}, some \l
{QtTemporalPattern::TemporalFieldFlags}{options} identifying how the field
is to be expressed and, where relevant, a datum. For a Literal field, the
datum is the string to be matched.
For numeric fields, a width may optionally be specified, indicating
the expected number of digits in the field. If options specifies
zero-padding, this is a minimum number of digits; otherwise, shorter fields
are accepted. In any case, longer values are accepted, where the resulting
numeric value is valid for the field. A zero width does not mean an empty
field will be accepted: it is effectively equivalent to a width of 1.
\note in some locales, the digits may require surrogate pairs to encode, so
the UTF-16 length of the field may exceed the number of digits. In a year
field with a sign, the sign does not count towards the width of the
field. The width only counts digits. The width is, in any case, a lower
bound: more digits may be read, if present and not consumed by some later
field. The \c endIndex of any parse result is the only reliable source of
truth on the UTF-16 end of the text parsed.
*/
// TODO: should we support digit grouping in year numbers, at least with > 4 digits ?
/*!
\fn QtTemporalPattern::hasFieldsFor(QSpan<const QtTemporalPattern::TemporalField> range)
\brief Identify the parts to which the given fields contribute data.
Takes a \a range of TemporalField instances and returns a \l
{QtTemporalPattern::}{DateTimeParts} indicating which parts \l classify()
says any of the fields contribute to. Note that this only tessts for
contribution to a part, not for full coverage of the part. See \l supports()
for that.
\sa classify(), supports()
*/
/*!
\enum QtTemporalPattern::SupportType
\brief This enumeration identifies how well some fields describe requested datetime parts.
The fields of a pattern may contain partial or complete data on each of the
\l {QtTemporalPattern::TemporalFieldPart}{parts of} a datetime. When parsing
or serializing only a date or only a time, fields for the other or for
timezone are extraneous, making the pattern unable to serialize just the
intended type, as it lacks the data for those fields. It would also, when
parsing, be obliged discard some of the data it parses, as the type it
returns cannot express it.
When serializing a datetime, if the fields present do not suffice to fully
encode the date or time, it will not be possible for a reader of the
resulting text to unambiguously determine the datetime. If timezone is not
specified, it is possible to convert the datetime to some specific choice of
zone: provided both ends of the communication use the same zone, it is then
possible to recover the exact point in time, albeit without knowing the
timezone originally used to encode it.
Where data is partially supplied, it is possible that the partial data
suffices to meet the readers needs, although this typically involves the
reader in making some default assumptions about the missing fields. For
example, a two-digit year may need some assumptions about the century
(possibly aided by information about the date and day of the week) to
determine what year to presume the sender intended.
\value None No fields were found.
\value HasStrays Some field not relevant to the requested parts was present.
\value Partial Either some requested part is present and some other is
missing or some fields for a requested part are present but
not enough to fully describe that part.
\value Clear Enough fields of the requested parts were found to fully
determine them and no fields of unwanted parts were present.
Where partial fields for the requested parts were found along with fields
for unwanted parts, HasStrays is used in preference to Partial, as it is
considered a more significant defect.
\sa {QtTemporalPattern::}{TemporalFieldPart}, {QtTemporalPattern::}{supports()}
*/
/*!
\fn QtTemporalPattern::supports(QtTemporalPattern::DateTimeParts wanted, QSpan<QtTemporalPattern::TemporalField> range, bool hasBaseYear) noexcept
\brief Assess how well the given fields support the \a wanted parts.
If any field in \a range belongs to a part not included in \a wanted,
returns \l {SupportType::}{HasStrays}. Otherwise,
\list
\li If no fields are present, aside from \l
{TemporalFieldCategory::}{Literal} ones, returns \l
{SupportType::}{None}.
\li If the fields present completely specify all parts in \a wanted,
returns \l {SupportType::}{Clear}.
\li If some fields are specified but some \a wanted part is inadequately
specified, or entirely unspecified, returns \l
{SupportType::}{Partial}.
\endlist
For these purposes,
\list
\li The \l {DateTimePart::}{Zone} part is specified by the \l
{TemporalFieldCategory::}{TimeZone}.
\li The \l {DateTimePart::}{Time} part is specified by any combination of
fields that identify the hour and minute. If the \l
{TemporalFieldCategory::}{SecondFractions} field is present, the part is
considered incompletely specified unless the \l
{TemporalFieldCategory::}{Seconds} field is also present.
\li The \l {DateTimePart::}{Date} part is specified by any combination of
fields that identify a date. This usually means \l
{TemporalFieldCategory::}{Year}, \l {TemporalFieldCategory::}{Month}
and \l {TemporalFieldCategory::}{DayOfMonth} although Year may be
indirectly specified by \l
{TemporalFieldCategory::}{YearWithinCentury} if the presence of \l
{TemporalFieldCategory::}{DayOfWeek} enables disambiguation among
centuries close to the present, given the other date fields.
\endlist
*/
template <typename Enum, size_t Size>
class EnumIndexedBitSet
{
std::bitset<Size> bits;
static size_t map(Enum cat) { return qToUnderlying(cat); }
public:
void set(Enum cat) { bits[map(cat)] = true; }
bool test(Enum cat) const { return bits[map(cat)]; }
};
SupportType supports(DateTimeParts wanted, QSpan<const TemporalField> range,
bool hasBaseYear) noexcept
{
// TODO: may need to take into account calendar (and perhaps locale).
SupportType support = SupportType::HasStrays;
SupportType zone = SupportType::None;
EnumIndexedBitSet<TemporalFieldCategory, EndTemporalFieldCategories> categories;
for (const TemporalField &field : range) {
const DateTimePart part = field.part();
switch (part) {
case DateTimePart::None: // Literal contributes no data.
continue;
case DateTimePart::Date:
case DateTimePart::Time:
categories.set(field.category);
break;
case DateTimePart::Zone:
if (field.options.testAnyFlags(TemporalFieldFlag::Wide | TemporalFieldFlag::Short)
|| field.options.testAnyFlags(TemporalFieldFlag::Numeric
| TemporalFieldFlag::Standalone
| TemporalFieldFlag::Iso8601)) {
zone = SupportType::Clear;
} else if (zone == SupportType::None) {
// Zone name abbreviation: does not uniquely identify zone.
zone = SupportType::Partial;
}
break;
}
if (!wanted.testFlag(part))
return support;
}
bool partsSeen = wanted.testFlag(DateTimePart::Zone);
support = partsSeen ? zone : SupportType::None;
constexpr auto join = [](SupportType lhs, SupportType rhs) -> SupportType {
Q_PRE(lhs != SupportType::HasStrays);
Q_PRE(rhs != SupportType::HasStrays);
// For combining SupportTypes from different Parts to determine support
// for their composite. Simplest case is when they're the same:
if (lhs == rhs)
return lhs;
// Every combination of distinct values among the other three is partial:
return SupportType::Partial;
};
#define CHECK(field) (categories.test(TemporalFieldCategory::field))
if (wanted.testFlag(DateTimePart::Date)) {
const SupportType hasDate = [&] {
// if (CHECK(JulianDay)) return SupportType::Clear;
int fields = 0;
bool partial = false;
if (CHECK(Year)) {
++fields;
} else if (CHECK(YearWithinCentury)) {
if (hasBaseYear) {
++fields;
// } else if (CHECK(Century)) { ++fields;
} else if (CHECK(DayOfWeek) && CHECK(DayOfMonth) && CHECK(Month)) {
// We can infer century from those three by assuming it's
// close to the present.
++fields;
} else {
partial = true;
}
// } else if (CHECK(Century)) { partial = true;
}
if (CHECK(Month))
++fields;
if (CHECK(DayOfMonth))
++fields;
else if (CHECK(DayOfWeek))
partial = true;
if (fields == 3 && !partial)
return SupportType::Clear;
if (fields || partial)
return SupportType::Partial;
return SupportType::None;
}();
support = partsSeen ? join(support, hasDate) : hasDate;
partsSeen = true;
}
if (wanted.testFlag(DateTimePart::Time)) {
const SupportType hasTime = [&] {
// if (CHECK(MillisecondInDay)) return SupportType::Clear;
int fields = 0;
bool partial = false;
if (CHECK(Hour)) {
++fields;
} else if (CHECK(HourMod12)) {
if (CHECK(PeriodInDay))
++fields;
else
partial = true;
} else if (CHECK(PeriodInDay)) {
partial = true;
}
if (CHECK(Minute))
++fields;
// Hour and minute are required, but seconds and later are optional;
// however, having a finer field without an coarser one leaves a gap => Partial.
if (CHECK(Second))
partial = fields < 2;
else if (CHECK(SecondFraction))
partial = true;
if (fields == 2 && !partial)
return SupportType::Clear;
if (fields || partial)
return SupportType::Partial;
return SupportType::None;
}();
support = partsSeen ? join(support, hasTime) : hasTime;
partsSeen = true;
}
#undef CHECK
// Shall still be None if we've seen no fields:
Q_ASSERT(partsSeen || support == SupportType::None);
return support;
}
} // namespace QtTemporalPattern
/*!
\internal
\since 6.12
\class QDateTimePattern
\brief A description of a serialization format for a datetime
*/
/*!
\fn QDateTimePattern::forLocale(const QLocale &locale, QLocale::FormatType format)
Construct a QDateTimePattern appropriate to the given \a locale.
The \a format can be used to select how compact or expansive the pattern is.
\sa fromQtFormat()
*/
/*!
\fn QDateTimePattern::isNull() const noexcept
Returns \c true if this pattern has no fields, otherwise \a false.
\sa fromQtFormat()
*/
/*
//! [is-valid]
Returns \c true if this pattern can be used to reliably transmit {\1}s.
Returns \c false if there is unresolved ambiguity in the texts it will
produce when serializing or the texts that will match it when parsing.
Some apparent ambiguities may be resolved by interactions between other
fields or the \c{defaults} parameter to \l{parse()}.
//! [is-valid]
*/
// TODO: currently (see supports(), above) doesn't take locale or calendar into account.
/*!
\fn QDateTimePattern::isValid() const noexcept
\include qttemporalpattern.cpp {is-valid} {datetime}
\include qttemporalpattern.cpp base-year-disambiguates
Timezone abbreviations are ambiguous.
\sa isNull()
*/
/*!
\fn QDateTimePattern::setLocale(const QLocale &loc) noexcept
//! [set-locale]
Sets the locale, for use for fields whose representation depends on locale,
to \a loc. By default the application's current default locale is used.
\sa QLocale::setDefault()
//! [set-locale]
*/
/*!
\fn QDateTimePattern::locale() const noexcep
Returns the current locale in use by this pattern.
\sa setLocale()
*/
/*!
\fn QDateTimePattern::setCalendar(QCalendar calendar) noexcept
\include qttemporalpattern.cpp set-calendar
*/
/*!
\fn QDateTimePattern::calendar(QCalendar calendar) const noexcept
Returns the current calendar in use by this pattern.
\sa setCalendar()
*/
/*!
\fn QDateTimePattern::setBaseYear(int centuryStart) noexcept
\include qttemporalpattern.cpp set-base-year
*/
/*!
\fn QDateTimePattern::clearBaseYear() noexcept
\include qttemporalpattern.cpp clear-base-year
*/
/*!
\fn QDateTimePattern::baseYear() const noexcept
\include qttemporalpattern.cpp get-base-year
*/
/*!
Parse and return the datetime represented by \a text.
//! [parser-defaults]
If \a defaults is provided and valid, any fields the format described by
this pattern does not provide will be copied from \a defaults to construct
the returned value.
//! [parser-defaults]
*/
QtTemporalPattern::ParseResult<QDateTime>
QDateTimePattern::parse(QStringView text, const QDateTime &defaults) const
{
#if QT_CONFIG(datetimeparser)
const auto seen = QtParseTemporal::prefix(text, m_fields, m_locale, m_calendar, m_baseYear);
if (!m_fields.isEmpty() && !seen) // Failed to parse.
return {};
const QDate date = seen.date(m_calendar, defaults.date());
const QTime time = seen.time(defaults.time());
if (QDateTime result(date, time, seen.zone, seen.resolveType()); result.isValid())
return {std::move(result), seen.size()};
// Fall back to default transition resolution:
return {QDateTime(date, time, seen.zone), seen.size()};
#else
Q_UNUSED(text);
Q_UNUSED(defaults);
return {};
#endif // datetimeparser
}
/*!
Serialize the given \a datetime to a string representation.
*/
QString QDateTimePattern::serialize(const QDateTime &datetime) const
{
Q_UNUSED(datetime);
return {};
}
/*!
Construct a QDateTimePattern described by the given \a format string.
If the format is empty or malformed, the returned pattern's \c isNull() shall
be \c true. Otherwise, \c isNull() shall be \c false.
\sa forLocale(), isNull()
*/
QDateTimePattern QDateTimePattern::fromQtFormat(QStringView format)
{
using namespace QtTemporalPattern;
constexpr DateTimeParts form = DateTimePart::Date | DateTimePart::Time | DateTimePart::Zone;
const auto qt = QtParseQtTemporalFormat::prefix(format, form);
if (qt.size() == format.size())
return QDateTimePattern(qt.fields);
return QDateTimePattern({});
}
/*!
\internal
\since 6.12
\class QTimePattern
\brief A description of a serialization format for a time
*/
/*!
\fn QTimePattern::forLocale(const QLocale &locale, QLocale::FormatType format)
Construct a QTimePattern appropriate to the given \a locale.
The \a format can be used to select how compact or expansive the pattern is.
\sa fromQtFormat()
*/
/*!
\fn QTimePattern::isNull() const noexcept
Returns \c true if this pattern has no fields, otherwise \a false.
\sa fromQtFormat()
*/
/*!
\fn QTimePattern::isValid() const noexcept
\include qttemporalpattern.cpp {is-valid} {time}
\sa isNull()
*/
/*!
\fn QTimePattern::setLocale(const QLocale &loc) noexcept
\include qttemporalpattern.cpp set-locale
*/
/*!
\fn QTimePattern::locale() const noexcep
Returns the current locale in use by this pattern.
\sa setLocale()
*/
/*!
Parse and return the time represented by \a text.
\include qttemporalpattern.cpp parser-defaults
*/
QtTemporalPattern::ParseResult<QTime>
QTimePattern::parse(QStringView text, QTime defaults) const
{
#if QT_CONFIG(datetimeparser)
const auto seen = QtParseTemporal::prefix(text, m_fields, m_locale, QCalendar());
if (!m_fields.isEmpty() && !seen) // Failed to parse.
return {};
return {seen.time(defaults), seen.size()};
#else
Q_UNUSED(text);
Q_UNUSED(defaults);
return {};
#endif // datetimeparser
}
/*!
Serialize the given \a time to a string representation.
*/
QString QTimePattern::serialize(const QTime &time) const
{
Q_UNUSED(time);
return {};
}
/*!
Construct a QTimePattern described by the given \a format string.
If the format is empty or malformed, the returned pattern's \c isNull() shall
be \c true. Otherwise, \c isNull() shall be \c false.
\sa forLocale(), isNull()
*/
QTimePattern QTimePattern::fromQtFormat(QStringView format)
{
using namespace QtTemporalPattern;
constexpr DateTimeParts form{DateTimePart::Time};
const auto qt = QtParseQtTemporalFormat::prefix(format, form);
if (qt.size() == format.size())
return QTimePattern(qt.fields);
return QTimePattern({});
}
/*!
\internal
\since 6.12
\class QDatePattern
\brief A description of a serialization format for a date
*/
/*!
\fn QDatePattern::forLocale(const QLocale &locale, QLocale::FormatType format)
Construct a QDatePattern appropriate to the given \a locale.
The \a format can be used to select how compact or expansive the pattern is.
\sa fromQtFormat()
*/
/*!
\fn QDatePattern::isNull() const noexcept
Returns \c true if this pattern has no fields, otherwise \a false.
\sa fromQtFormat()
*/
/*!
\fn QDatePattern::isValid() const noexcept
\include qttemporalpattern.cpp {is-valid} {date}
//! [base-year-disambiguates]
The ambiguity of two-digit years may be resolved by configuring the hundred
years among which to select a matching years.
//! [base-year-disambiguates]
\sa setBaseYear(), isNull()
*/
/*!
\fn QDatePattern::setLocale(const QLocale &loc) noexcept
\include qttemporalpattern.cpp set-locale
*/
/*!
\fn QDatePattern::locale() const noexcep
Returns the current locale in use by this pattern.
\sa setLocale()
*/
/*!
\fn QDatePattern::setCalendar(QCalendar calendar) noexcept
//! [set-calendar]
Sets the calendar used by this pattern. This influences the names and
lengths of months and how these vary from year to year. In some cases it may
also influence the number of months in the year or even the pattern and
names of days of the week.
By default the Gregorian calendar is used.
\sa getCalendar(), QCalendar
//! [set-calendar]
*/
/*!
\fn QDatePattern::calendar(QCalendar calendar) const noexcept
Returns the current calendar in use by this pattern.
\sa setCalendar()
*/
/*!
\fn QDatePattern::setBaseYear(int centuryStart) noexcept
//! [set-base-year]
Configures the handling of two-digit years, if any are present in the pattern.
If a two-digit year is present, the year ending in those two digits in the
range from \a centuryStart to \c {centuryStart + 99} shall be its default
interpretation. This may be amended if the month, day of the month and day
of the week indicate some other nearby century.
\note the years here, including \a centuryStart, are expressed with respect
to \c{calendar()}, whose year numbering need not match that of the Gregorian
calendar.
Calling this method makes no difference unless the pattern does in fact use
a two-digit year, nor does it affect serialization.
\sa clearBaseYear()
//! [set-base-year]
*/
// TODO: should the default state be clear or some specific year ?
// That year could depend on the present, e.g. present minus 49 years.
/*!
\fn QDatePattern::clearBaseYear() noexcept
//! [clear-base-year]
Leaves the handling of two-digit years to other fields to disambiguate.
This is the default state, so has no effect unless \l setBaseYear() has
previously been called.
If a two-digit year is present, this leaves unspecified which hundred years
to presume it lies within. If a two-digit year is present and other fields
of the pattern do not suffice to make clear which hundred years to select
based on the given last two digits, the text produced when serializing with
this format shall generally be ambiguous. While readers of the text may be
able to disambiguate the year anyway, there is scope for misunderstanding if
their heuristics for doing so do not match your expectation. On parsing, the
pattern may deliver a result that is off by some whole number of centuries
from what the parsed text's author intended.
\sa setBaseYear(), isValid()
//! [clear-base-year]
*/
/*!
\fn QDatePattern::baseYear() const noexcept
//! [get-base-year]
Returns the current base year, with respect to \c{calendar()}, used by this
pattern if it needs to resolve a two-digit year. The result is \c
{std::nullopt} when no base year is set, which is the default state.
\sa setBaseYear()
//! [get-base-year]
*/
/*!
Parse and return the date represented by \a text.
\include qttemporalpattern.cpp parser-defaults
*/
QtTemporalPattern::ParseResult<QDate>
QDatePattern::parse(QStringView text, QDate defaults) const
{
#if QT_CONFIG(datetimeparser)
const auto seen = QtParseTemporal::prefix(text, m_fields, m_locale, m_calendar, m_baseYear);
if (!m_fields.isEmpty() && !seen) // Failed to parse.
return {};
return {seen.date(m_calendar, defaults), seen.size()};
#else
Q_UNUSED(text);
Q_UNUSED(defaults);
return {};
#endif // datetimeparser
}
/*!
Serialize the given \a date to a string representation.
\sa parse()
*/
QString QDatePattern::serialize(const QDate &date) const
{
Q_UNUSED(date);
return {};
}
/*!
Construct a QDatePattern described by the given \a format string.
If the format is empty or malformed, the returned pattern's \c isNull() shall
be \c true. Otherwise, \c isNull() shall be \c false.
\sa forLocale(), isNull()
*/
QDatePattern QDatePattern::fromQtFormat(QStringView format)
{
using namespace QtTemporalPattern;
constexpr DateTimeParts form{DateTimePart::Date};
const auto qt = QtParseQtTemporalFormat::prefix(format, form);
if (qt.size() == format.size())
return QDatePattern(qt.fields);
return QDatePattern({});
}
QT_END_NAMESPACE