QDateTime: disambiguate times in a zone transition
Previously, requesting a time that got repeated - on the given date, due to a fall-back transition - would get one of the two repeats, giving the caller (no hint that there was a choice and) no way to select the other. Add a flags parameter that captures the available ways to resolve such ambiguity or select a suitable time near a gap. Add such a parameter to relevant QDateTime methods, including constructors, to enable callers to indicate their preference in the same way. This replaces DST-hint parameters in various internal functions, including QTimeZonePrivate's dataForLocalTime(). Adapted tst_QDateTime to test the new feature. Adapt to gap-times no longer being invalid (by default; or, when they are, no longer having a useful toMSecsSinceEpoch() value). Instead, they don't match what was asked for. Amend documentation to reflect that. Most of the code change for this is to QDTParser and QDTEdit. [ChangeLog][QtCore][QDateTime] Added a TransitionResolution parameter to various QDateTime methods to enable the caller to indicate, when the indicated datetime falls in a time-zone transition, which side of the transition to fall or whether to produce an invalid result. [ChangeLog][QtCore][Possibly Significant Behavior Change] When QDateTime is instantiated for a combination of date and time that was skipped, by local time or a time-zone, for example during a spring-forward DST transition, the result is no longer marked invalid. Whether the selected nearby date-time is before or after the skipped interval may have changed on some platforms; unless overridden by an explicit TransitionResolution, it is now a date-time as long after the previous day's noon as a naive reading of the requested date and time would expect. This was the prior behavior at least on Linux. Fixes: QTBUG-79923 Change-Id: I11d5339abef9e7125c4e0dc95a09a7cd4f169dab Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
38994ab9ac
commit
a49ccc08c3
@ -618,6 +618,19 @@ QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const
|
|||||||
|
|
||||||
#if QT_CORE_REMOVED_SINCE(6, 7)
|
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||||
|
|
||||||
|
#include "qdatetime.h"
|
||||||
|
|
||||||
|
QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone)
|
||||||
|
: QDateTime(date, time, timeZone, TransitionResolution::LegacyBehavior) {}
|
||||||
|
QDateTime::QDateTime(QDate date, QTime time)
|
||||||
|
: QDateTime(date, time, TransitionResolution::LegacyBehavior) {}
|
||||||
|
void QDateTime::setDate(QDate date) { setDate(date, TransitionResolution::LegacyBehavior); }
|
||||||
|
void QDateTime::setTime(QTime time) { setTime(time, TransitionResolution::LegacyBehavior); }
|
||||||
|
void QDateTime::setTimeZone(const QTimeZone &toZone)
|
||||||
|
{
|
||||||
|
setTimeZone(toZone, TransitionResolution::LegacyBehavior);
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
|
||||||
#include "qjniobject.h"
|
#include "qjniobject.h"
|
||||||
|
@ -853,7 +853,10 @@ static bool inDateTimeRange(qint64 jd, DaySide side)
|
|||||||
static QDateTime toEarliest(QDate day, const QTimeZone &zone)
|
static QDateTime toEarliest(QDate day, const QTimeZone &zone)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!zone.isUtcOrFixedOffset());
|
Q_ASSERT(!zone.isUtcOrFixedOffset());
|
||||||
const auto moment = [=](QTime time) { return QDateTime(day, time, zone); };
|
// And the day starts in a gap. First find a moment not in that gap.
|
||||||
|
const auto moment = [=](QTime time) {
|
||||||
|
return QDateTime(day, time, zone, QDateTime::TransitionResolution::Reject);
|
||||||
|
};
|
||||||
// Longest routine time-zone transition is 2 hours:
|
// Longest routine time-zone transition is 2 hours:
|
||||||
QDateTime when = moment(QTime(2, 0));
|
QDateTime when = moment(QTime(2, 0));
|
||||||
if (!when.isValid()) {
|
if (!when.isValid()) {
|
||||||
@ -871,7 +874,8 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone)
|
|||||||
// Binary chop to the right minute
|
// Binary chop to the right minute
|
||||||
while (high > low + 1) {
|
while (high > low + 1) {
|
||||||
const int mid = (high + low) / 2;
|
const int mid = (high + low) / 2;
|
||||||
const QDateTime probe = moment(QTime(mid / 60, mid % 60));
|
const QDateTime probe = QDateTime(day, QTime(mid / 60, mid % 60), zone,
|
||||||
|
QDateTime::TransitionResolution::PreferBefore);
|
||||||
if (probe.isValid() && probe.date() == day) {
|
if (probe.isValid() && probe.date() == day) {
|
||||||
high = mid;
|
high = mid;
|
||||||
when = probe;
|
when = probe;
|
||||||
@ -933,24 +937,26 @@ QDateTime QDate::startOfDay(const QTimeZone &zone) const
|
|||||||
if (!inDateTimeRange(jd, DaySide::Start) || !zone.isValid())
|
if (!inDateTimeRange(jd, DaySide::Start) || !zone.isValid())
|
||||||
return QDateTime();
|
return QDateTime();
|
||||||
|
|
||||||
QDateTime when(*this, QTime(0, 0), zone);
|
QDateTime when(*this, QTime(0, 0), zone,
|
||||||
if (Q_LIKELY(when.isValid()))
|
QDateTime::TransitionResolution::RelativeToBefore);
|
||||||
return when;
|
if (Q_UNLIKELY(!when.isValid() || when.date() != *this)) {
|
||||||
|
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
// The start of the day must have fallen in a spring-forward's gap; find the spring-forward:
|
// The start of the day must have fallen in a spring-forward's gap; find the spring-forward:
|
||||||
if (zone.timeSpec() == Qt::TimeZone && zone.hasTransitions()) {
|
if (zone.timeSpec() == Qt::TimeZone && zone.hasTransitions()) {
|
||||||
QTimeZone::OffsetData tran
|
QTimeZone::OffsetData tran
|
||||||
// There's unlikely to be another transition before noon tomorrow.
|
// There's unlikely to be another transition before noon tomorrow.
|
||||||
// However, the whole of today may have been skipped !
|
// However, the whole of today may have been skipped !
|
||||||
= zone.previousTransition(QDateTime(addDays(1), QTime(12, 0), zone));
|
= zone.previousTransition(QDateTime(addDays(1), QTime(12, 0), zone));
|
||||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||||
if (at.isValid() && at.date() == *this)
|
if (at.isValid() && at.date() == *this)
|
||||||
return at;
|
return at;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return toEarliest(*this, zone);
|
when = toEarliest(*this, zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
return when;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -1002,7 +1008,10 @@ QDateTime QDate::startOfDay(Qt::TimeSpec spec, int offsetSeconds) const
|
|||||||
static QDateTime toLatest(QDate day, const QTimeZone &zone)
|
static QDateTime toLatest(QDate day, const QTimeZone &zone)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!zone.isUtcOrFixedOffset());
|
Q_ASSERT(!zone.isUtcOrFixedOffset());
|
||||||
const auto moment = [=](QTime time) { return QDateTime(day, time, zone); };
|
// And the day ends in a gap. First find a moment not in that gap:
|
||||||
|
const auto moment = [=](QTime time) {
|
||||||
|
return QDateTime(day, time, zone, QDateTime::TransitionResolution::Reject);
|
||||||
|
};
|
||||||
// Longest routine time-zone transition is 2 hours:
|
// Longest routine time-zone transition is 2 hours:
|
||||||
QDateTime when = moment(QTime(21, 59, 59, 999));
|
QDateTime when = moment(QTime(21, 59, 59, 999));
|
||||||
if (!when.isValid()) {
|
if (!when.isValid()) {
|
||||||
@ -1020,7 +1029,8 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone)
|
|||||||
// Binary chop to the right minute
|
// Binary chop to the right minute
|
||||||
while (high > low + 1) {
|
while (high > low + 1) {
|
||||||
const int mid = (high + low) / 2;
|
const int mid = (high + low) / 2;
|
||||||
const QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999));
|
const QDateTime probe = QDateTime(day, QTime(mid / 60, mid % 60, 59, 999), zone,
|
||||||
|
QDateTime::TransitionResolution::PreferAfter);
|
||||||
if (probe.isValid() && probe.date() == day) {
|
if (probe.isValid() && probe.date() == day) {
|
||||||
low = mid;
|
low = mid;
|
||||||
when = probe;
|
when = probe;
|
||||||
@ -1083,24 +1093,25 @@ QDateTime QDate::endOfDay(const QTimeZone &zone) const
|
|||||||
if (!inDateTimeRange(jd, DaySide::End) || !zone.isValid())
|
if (!inDateTimeRange(jd, DaySide::End) || !zone.isValid())
|
||||||
return QDateTime();
|
return QDateTime();
|
||||||
|
|
||||||
QDateTime when(*this, QTime(23, 59, 59, 999), zone);
|
QDateTime when(*this, QTime(23, 59, 59, 999), zone,
|
||||||
if (Q_LIKELY(when.isValid()))
|
QDateTime::TransitionResolution::RelativeToAfter);
|
||||||
return when;
|
if (Q_UNLIKELY(!when.isValid() || when.date() != *this)) {
|
||||||
|
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
// The end of the day must have fallen in a spring-forward's gap; find the spring-forward:
|
// The end of the day must have fallen in a spring-forward's gap; find the spring-forward:
|
||||||
if (zone.timeSpec() == Qt::TimeZone && zone.hasTransitions()) {
|
if (zone.timeSpec() == Qt::TimeZone && zone.hasTransitions()) {
|
||||||
QTimeZone::OffsetData tran
|
QTimeZone::OffsetData tran
|
||||||
// It's unlikely there's been another transition since yesterday noon.
|
// It's unlikely there's been another transition since yesterday noon.
|
||||||
// However, the whole of today may have been skipped !
|
// However, the whole of today may have been skipped !
|
||||||
= zone.nextTransition(QDateTime(addDays(-1), QTime(12, 0), zone));
|
= zone.nextTransition(QDateTime(addDays(-1), QTime(12, 0), zone));
|
||||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||||
if (at.isValid() && at.date() == *this)
|
if (at.isValid() && at.date() == *this)
|
||||||
return at;
|
return at;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return toLatest(*this, zone);
|
when = toLatest(*this, zone);
|
||||||
|
}
|
||||||
|
return when;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -2728,11 +2739,99 @@ static auto millisToWithinRange(qint64 millis)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
\enum QDateTimePrivate::TransitionOption
|
||||||
|
|
||||||
|
This enumeration is used to resolve datetime combinations which fall in \l
|
||||||
|
{Timezone transitions}. The transition is described as a "gap" if there are
|
||||||
|
time representations skipped over by the zone, as is common in the "spring
|
||||||
|
forward" transitions in many zones on entering daylight-saving time. The
|
||||||
|
transition is described as a "fold" if there are time representations
|
||||||
|
repeated in the zone, as in a "fall back" transition out of daylight-saving
|
||||||
|
time.
|
||||||
|
|
||||||
|
When the options specified do not determine a resolution for a datetime, it
|
||||||
|
is marked invalid.
|
||||||
|
|
||||||
|
The prepared option sets above are in fact composed from low-level atomic
|
||||||
|
options. For each of gap and fold you can chose between two candidate times,
|
||||||
|
one before or after the transition, based on the time requested; or you can
|
||||||
|
pick the moment of transition, or the start or end of the transition
|
||||||
|
interval. For a gap, the start and end of the interval are the moment of the
|
||||||
|
transition, but for a repeated interval the start of the first pass is the
|
||||||
|
start of the transition interval, the end of the second pass is the end of
|
||||||
|
the transition interval and the moment of the transition itself is both the
|
||||||
|
end of the first pass and the start of the second.
|
||||||
|
|
||||||
|
\value GapUseBefore For a time in a gap, use a time before the transition,
|
||||||
|
as if stepping back from a later time.
|
||||||
|
\value GapUseAfter For a time in a gap, use a time after the transition, as
|
||||||
|
if stepping forward from an earlier time.
|
||||||
|
\value FoldUseBefore For a repeated time, use the first candidate, which is
|
||||||
|
before the transition.
|
||||||
|
\value FoldUseAfter For a repeated time, use the second candidate, which is
|
||||||
|
after the transition.
|
||||||
|
\value FlipForReverseDst For "reversed" DST, this reverses the preceding
|
||||||
|
four options (see below).
|
||||||
|
|
||||||
|
The last has no effect unless the "daylight-saving" time side of the
|
||||||
|
transition is known to have a lower offset from UTC than the standard time
|
||||||
|
side. (This is the "reversed" DST case of \l {Timezone transitions}.) In
|
||||||
|
that case, if other options would select a time after the transition, a time
|
||||||
|
before is used instead, and vice versa. This effectively turns a preference
|
||||||
|
for the side with lower offset into a preference for the side that is
|
||||||
|
officially standard time, even if it has higher offset; and conversely a
|
||||||
|
preference for higher offset into a preference for daylight-saving time,
|
||||||
|
even if it has a lower offset. This option has no effect on a resolution
|
||||||
|
that selects the moment of transition or the start or end of the transition
|
||||||
|
interval.
|
||||||
|
|
||||||
|
The result of combining more than one of the \c GapUse* options is
|
||||||
|
undefined; likewise for the \c FoldUse*. Each of QDateTime's
|
||||||
|
TransitionResolution values, aside from Reject, maps to a combination that
|
||||||
|
incorporates one from each of these sets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
constexpr static QDateTimePrivate::TransitionOptions
|
||||||
|
toTransitionOptions(QDateTime::TransitionResolution res)
|
||||||
|
{
|
||||||
|
switch (res) {
|
||||||
|
case QDateTime::TransitionResolution::RelativeToBefore:
|
||||||
|
return QDateTimePrivate::GapUseAfter | QDateTimePrivate::FoldUseBefore;
|
||||||
|
case QDateTime::TransitionResolution::RelativeToAfter:
|
||||||
|
return QDateTimePrivate::GapUseBefore | QDateTimePrivate::FoldUseAfter;
|
||||||
|
case QDateTime::TransitionResolution::PreferBefore:
|
||||||
|
return QDateTimePrivate::GapUseBefore | QDateTimePrivate::FoldUseBefore;
|
||||||
|
case QDateTime::TransitionResolution::PreferAfter:
|
||||||
|
return QDateTimePrivate::GapUseAfter | QDateTimePrivate::FoldUseAfter;
|
||||||
|
case QDateTime::TransitionResolution::PreferStandard:
|
||||||
|
return QDateTimePrivate::GapUseBefore
|
||||||
|
| QDateTimePrivate::FoldUseAfter
|
||||||
|
| QDateTimePrivate::FlipForReverseDst;
|
||||||
|
case QDateTime::TransitionResolution::PreferDaylightSaving:
|
||||||
|
return QDateTimePrivate::GapUseAfter
|
||||||
|
| QDateTimePrivate::FoldUseBefore
|
||||||
|
| QDateTimePrivate::FlipForReverseDst;
|
||||||
|
case QDateTime::TransitionResolution::Reject: break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static QDateTimePrivate::TransitionOptions
|
||||||
|
toTransitionOptions(QDateTimePrivate::DaylightStatus dst)
|
||||||
|
{
|
||||||
|
return toTransitionOptions(dst == QDateTimePrivate::DaylightTime
|
||||||
|
? QDateTime::TransitionResolution::PreferDaylightSaving
|
||||||
|
: QDateTime::TransitionResolution::PreferStandard);
|
||||||
|
}
|
||||||
|
|
||||||
QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
||||||
{
|
{
|
||||||
|
const QDateTimePrivate::TransitionOptions resolve = toTransitionOptions(dst);
|
||||||
QString abbreviation;
|
QString abbreviation;
|
||||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||||
abbreviation = QLocalTime::localTimeAbbbreviationAt(millis, dst);
|
abbreviation = QLocalTime::localTimeAbbbreviationAt(millis, resolve);
|
||||||
if (!abbreviation.isEmpty())
|
if (!abbreviation.isEmpty())
|
||||||
return abbreviation;
|
return abbreviation;
|
||||||
}
|
}
|
||||||
@ -2742,7 +2841,7 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
|||||||
// Use the system zone:
|
// Use the system zone:
|
||||||
const auto sys = QTimeZone::systemTimeZone();
|
const auto sys = QTimeZone::systemTimeZone();
|
||||||
if (sys.isValid()) {
|
if (sys.isValid()) {
|
||||||
ZoneState state = zoneStateAtMillis(sys, millis, dst);
|
ZoneState state = zoneStateAtMillis(sys, millis, resolve);
|
||||||
if (state.valid)
|
if (state.valid)
|
||||||
return sys.d->abbreviation(state.when - state.offset * MSECS_PER_SEC);
|
return sys.d->abbreviation(state.when - state.offset * MSECS_PER_SEC);
|
||||||
}
|
}
|
||||||
@ -2752,19 +2851,20 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
|||||||
// Use a time in the system range with the same day-of-week pattern to its year:
|
// Use a time in the system range with the same day-of-week pattern to its year:
|
||||||
auto fake = millisToWithinRange(millis);
|
auto fake = millisToWithinRange(millis);
|
||||||
if (Q_LIKELY(fake.good))
|
if (Q_LIKELY(fake.good))
|
||||||
return QLocalTime::localTimeAbbbreviationAt(fake.shifted, dst);
|
return QLocalTime::localTimeAbbbreviationAt(fake.shifted, resolve);
|
||||||
|
|
||||||
// Overflow, apparently.
|
// Overflow, apparently.
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the offset from UTC at the given local time as millis.
|
// Determine the offset from UTC at the given local time as millis.
|
||||||
QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis, DaylightStatus dst)
|
QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(
|
||||||
|
qint64 millis, QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
// First, if millis is within a day of the viable range, try mktime() in
|
// First, if millis is within a day of the viable range, try mktime() in
|
||||||
// case it does fall in the range and gets useful information:
|
// case it does fall in the range and gets useful information:
|
||||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||||
auto result = QLocalTime::mapLocalTime(millis, dst);
|
auto result = QLocalTime::mapLocalTime(millis, resolve);
|
||||||
if (result.valid)
|
if (result.valid)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -2774,14 +2874,14 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
|
|||||||
// Use the system zone:
|
// Use the system zone:
|
||||||
const auto sys = QTimeZone::systemTimeZone();
|
const auto sys = QTimeZone::systemTimeZone();
|
||||||
if (sys.isValid())
|
if (sys.isValid())
|
||||||
return zoneStateAtMillis(sys, millis, dst);
|
return zoneStateAtMillis(sys, millis, resolve);
|
||||||
#endif // timezone
|
#endif // timezone
|
||||||
|
|
||||||
// Kludge
|
// Kludge
|
||||||
// Use a time in the system range with the same day-of-week pattern to its year:
|
// Use a time in the system range with the same day-of-week pattern to its year:
|
||||||
auto fake = millisToWithinRange(millis);
|
auto fake = millisToWithinRange(millis);
|
||||||
if (Q_LIKELY(fake.good)) {
|
if (Q_LIKELY(fake.good)) {
|
||||||
auto result = QLocalTime::mapLocalTime(fake.shifted, dst);
|
auto result = QLocalTime::mapLocalTime(fake.shifted, resolve);
|
||||||
if (result.valid) {
|
if (result.valid) {
|
||||||
qint64 adjusted;
|
qint64 adjusted;
|
||||||
if (Q_UNLIKELY(qAddOverflow(result.when, millis - fake.shifted, &adjusted))) {
|
if (Q_UNLIKELY(qAddOverflow(result.when, millis - fake.shifted, &adjusted))) {
|
||||||
@ -2799,33 +2899,26 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
// For a TimeZone and a time expressed in zone msecs encoding, possibly with a
|
// For a TimeZone and a time expressed in zone msecs encoding, compute the
|
||||||
// hint to DST-ness, compute the actual DST-ness and offset, adjusting the time
|
// actual DST-ness and offset, adjusting the time if needed to escape a
|
||||||
// if needed to escape a spring-forward.
|
// spring-forward.
|
||||||
QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(const QTimeZone &zone,
|
QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(
|
||||||
qint64 millis, DaylightStatus dst)
|
const QTimeZone &zone, qint64 millis, QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
Q_ASSERT(zone.isValid());
|
Q_ASSERT(zone.isValid());
|
||||||
Q_ASSERT(zone.timeSpec() == Qt::TimeZone);
|
Q_ASSERT(zone.timeSpec() == Qt::TimeZone);
|
||||||
// Get the effective data from QTimeZone
|
return zone.d->stateAtZoneTime(millis, resolve);
|
||||||
QTimeZonePrivate::Data data = zone.d->dataForLocalTime(millis, int(dst));
|
|
||||||
if (data.offsetFromUtc == QTimeZonePrivate::invalidSeconds())
|
|
||||||
return {millis};
|
|
||||||
Q_ASSERT(zone.d->offsetFromUtc(data.atMSecsSinceEpoch) == data.offsetFromUtc);
|
|
||||||
return ZoneState(data.atMSecsSinceEpoch + data.offsetFromUtc * MSECS_PER_SEC,
|
|
||||||
data.offsetFromUtc,
|
|
||||||
data.daylightTimeOffset ? DaylightTime : StandardTime);
|
|
||||||
}
|
}
|
||||||
#endif // timezone
|
#endif // timezone
|
||||||
|
|
||||||
static inline QDateTimePrivate::ZoneState stateAtMillis(const QTimeZone &zone, qint64 millis,
|
static inline QDateTimePrivate::ZoneState stateAtMillis(const QTimeZone &zone, qint64 millis,
|
||||||
QDateTimePrivate::DaylightStatus dst)
|
QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
if (zone.timeSpec() == Qt::LocalTime)
|
if (zone.timeSpec() == Qt::LocalTime)
|
||||||
return QDateTimePrivate::localStateAtMillis(millis, dst);
|
return QDateTimePrivate::localStateAtMillis(millis, resolve);
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
if (zone.timeSpec() == Qt::TimeZone && zone.isValid())
|
if (zone.timeSpec() == Qt::TimeZone && zone.isValid())
|
||||||
return QDateTimePrivate::zoneStateAtMillis(zone, millis, dst);
|
return QDateTimePrivate::zoneStateAtMillis(zone, millis, resolve);
|
||||||
#endif
|
#endif
|
||||||
return {millis};
|
return {millis};
|
||||||
}
|
}
|
||||||
@ -2938,7 +3031,8 @@ static inline bool usesSameOffset(const QDateTimeData &a, const QDateTimeData &b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the LocalTime or TimeZone validity and offset
|
// Refresh the LocalTime or TimeZone validity and offset
|
||||||
static void refreshZonedDateTime(QDateTimeData &d, const QTimeZone &zone)
|
static void refreshZonedDateTime(QDateTimeData &d, const QTimeZone &zone,
|
||||||
|
QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
Q_ASSERT(zone.timeSpec() == Qt::TimeZone || zone.timeSpec() == Qt::LocalTime);
|
Q_ASSERT(zone.timeSpec() == Qt::TimeZone || zone.timeSpec() == Qt::LocalTime);
|
||||||
auto status = getStatus(d);
|
auto status = getStatus(d);
|
||||||
@ -2958,27 +3052,19 @@ static void refreshZonedDateTime(QDateTimeData &d, const QTimeZone &zone)
|
|||||||
if (!status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime)) {
|
if (!status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime)) {
|
||||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||||
} else {
|
} else {
|
||||||
// We have a valid date and time and a Qt::LocalTime or Qt::TimeZone that needs calculating
|
// We have a valid date and time and a Qt::LocalTime or Qt::TimeZone
|
||||||
// LocalTime and TimeZone might fall into a "missing" DST transition hour
|
// that might fall into a "missing" DST transition hour.
|
||||||
// Calling toEpochMSecs will adjust the returned date/time if it does
|
|
||||||
qint64 msecs = getMSecs(d);
|
qint64 msecs = getMSecs(d);
|
||||||
QDateTimePrivate::ZoneState state = stateAtMillis(zone, msecs,
|
QDateTimePrivate::ZoneState state = stateAtMillis(zone, msecs, resolve);
|
||||||
extractDaylightStatus(status));
|
|
||||||
// Save the offset to use in offsetFromUtc() &c., even if the next check
|
|
||||||
// marks invalid; this lets toMSecsSinceEpoch() give a useful fallback
|
|
||||||
// for times in spring-forward gaps.
|
|
||||||
offsetFromUtc = state.offset;
|
|
||||||
Q_ASSERT(!state.valid || (state.offset >= -SECS_PER_DAY && state.offset <= SECS_PER_DAY));
|
Q_ASSERT(!state.valid || (state.offset >= -SECS_PER_DAY && state.offset <= SECS_PER_DAY));
|
||||||
if (Q_LIKELY(state.valid && msecs == state.when)) {
|
if (state.dst == QDateTimePrivate::UnknownDaylightTime) { // Overflow
|
||||||
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
|
||||||
} else { // msecs changed: gap, or failed to convert (e.g. overflow)
|
|
||||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||||
if (state.valid) { // gap
|
} else if (state.valid) {
|
||||||
/* Make sure our offset and msecs do produce the selected UTC
|
status = mergeDaylightStatus(status, state.dst);
|
||||||
secs, if queried. When d isn't short, we record offset, so
|
offsetFromUtc = state.offset;
|
||||||
need msecs to match; when d is short, consistency demands we
|
status.setFlag(QDateTimePrivate::ValidDateTime, true);
|
||||||
also update msecs, which will at least mean we don't hit the
|
if (Q_UNLIKELY(msecs != state.when)) {
|
||||||
gap again, if we ever recompute offset. */
|
// Update msecs to the resolution:
|
||||||
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
||||||
if (msecsCanBeSmall(state.when)) {
|
if (msecsCanBeSmall(state.when)) {
|
||||||
d.data.msecs = qintptr(state.when);
|
d.data.msecs = qintptr(state.when);
|
||||||
@ -2991,6 +3077,8 @@ static void refreshZonedDateTime(QDateTimeData &d, const QTimeZone &zone)
|
|||||||
if (!status.testFlag(QDateTimePrivate::ShortData))
|
if (!status.testFlag(QDateTimePrivate::ShortData))
|
||||||
d->m_msecs = state.when;
|
d->m_msecs = state.when;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3017,7 +3105,7 @@ static void refreshSimpleDateTime(QDateTimeData &d)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up and set status after assorted set-up or reworking:
|
// Clean up and set status after assorted set-up or reworking:
|
||||||
static void checkValidDateTime(QDateTimeData &d)
|
static void checkValidDateTime(QDateTimeData &d, QDateTime::TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
auto spec = extractSpec(getStatus(d));
|
auto spec = extractSpec(getStatus(d));
|
||||||
switch (spec) {
|
switch (spec) {
|
||||||
@ -3030,12 +3118,13 @@ static void checkValidDateTime(QDateTimeData &d)
|
|||||||
case Qt::LocalTime:
|
case Qt::LocalTime:
|
||||||
// For these, we need to check whether (the zone is valid and) the time
|
// For these, we need to check whether (the zone is valid and) the time
|
||||||
// is valid for the zone. Expensive, but we have no other option.
|
// is valid for the zone. Expensive, but we have no other option.
|
||||||
refreshZonedDateTime(d, d.timeZone());
|
refreshZonedDateTime(d, d.timeZone(), toTransitionOptions(resolve));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reviseTimeZone(QDateTimeData &d, QTimeZone zone)
|
static void reviseTimeZone(QDateTimeData &d, QTimeZone zone,
|
||||||
|
QDateTime::TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
Qt::TimeSpec spec = zone.timeSpec();
|
Qt::TimeSpec spec = zone.timeSpec();
|
||||||
auto status = mergeSpec(getStatus(d), spec);
|
auto status = mergeSpec(getStatus(d), spec);
|
||||||
@ -3074,7 +3163,7 @@ static void reviseTimeZone(QDateTimeData &d, QTimeZone zone)
|
|||||||
if (QTimeZone::isUtcOrFixedOffset(spec))
|
if (QTimeZone::isUtcOrFixedOffset(spec))
|
||||||
refreshSimpleDateTime(d);
|
refreshSimpleDateTime(d);
|
||||||
else
|
else
|
||||||
refreshZonedDateTime(d, zone);
|
refreshZonedDateTime(d, zone, toTransitionOptions(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setDateTime(QDateTimeData &d, QDate date, QTime time)
|
static void setDateTime(QDateTimeData &d, QDate date, QTime time)
|
||||||
@ -3319,14 +3408,15 @@ inline QDateTimePrivate *QDateTime::Data::operator->()
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
Q_NEVER_INLINE
|
Q_NEVER_INLINE
|
||||||
QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTimeZone &zone)
|
QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTimeZone &zone,
|
||||||
|
QDateTime::TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
QDateTime::Data result(zone);
|
QDateTime::Data result(zone);
|
||||||
setDateTime(result, toDate, toTime);
|
setDateTime(result, toDate, toTime);
|
||||||
if (zone.isUtcOrFixedOffset())
|
if (zone.isUtcOrFixedOffset())
|
||||||
refreshSimpleDateTime(result);
|
refreshSimpleDateTime(result);
|
||||||
else
|
else
|
||||||
refreshZonedDateTime(result, zone);
|
refreshZonedDateTime(result, zone, toTransitionOptions(resolve));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3356,17 +3446,17 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
UTC of +3600 seconds is one hour ahead of UTC (usually written in ISO
|
UTC of +3600 seconds is one hour ahead of UTC (usually written in ISO
|
||||||
standard notation as "UTC+01:00"), with no daylight-saving
|
standard notation as "UTC+01:00"), with no daylight-saving
|
||||||
complications. When using either local time or a specified time zone,
|
complications. When using either local time or a specified time zone,
|
||||||
time-zone transitions (see \l {Daylight-Saving Time (DST)}{below}) are taken
|
time-zone transitions (see \l {Timezone transitions}{below}) are taken into
|
||||||
into account. A QDateTime's timeSpec() will tell you which of the four types
|
account. A QDateTime's timeSpec() will tell you which of the four types of
|
||||||
of time representation is in use; its timeRepresentation() provides a full
|
time representation is in use; its timeRepresentation() provides a full
|
||||||
representation of that time representation, as a QTimeZone.
|
description of that time representation, as a QTimeZone.
|
||||||
|
|
||||||
A QDateTime object is typically created either by giving a date and time
|
A QDateTime object is typically created either by giving a date and time
|
||||||
explicitly in the constructor, or by using a static function such as
|
explicitly in the constructor, or by using a static function such as
|
||||||
currentDateTime() or fromMSecsSinceEpoch(). The date and time can be changed
|
currentDateTime() or fromMSecsSinceEpoch(). The date and time can be changed
|
||||||
with setDate() and setTime(). A datetime can also be set using the
|
with setDate() and setTime(). A datetime can also be set using the
|
||||||
setMSecsSinceEpoch() function that takes the time, in milliseconds, since
|
setMSecsSinceEpoch() function that takes the time, in milliseconds, since
|
||||||
the start, in UTC of the year 1970. The fromString() function returns a
|
the start, in UTC, of the year 1970. The fromString() function returns a
|
||||||
QDateTime, given a string and a date format used to interpret the date
|
QDateTime, given a string and a date format used to interpret the date
|
||||||
within the string.
|
within the string.
|
||||||
|
|
||||||
@ -3400,10 +3490,10 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
(whose \l {QTimeZone::timeSpec()}{timeSpec()} is \c {Qt::TimeZone}) to use
|
(whose \l {QTimeZone::timeSpec()}{timeSpec()} is \c {Qt::TimeZone}) to use
|
||||||
that instead.
|
that instead.
|
||||||
|
|
||||||
\note QDateTime does not account for leap seconds.
|
|
||||||
|
|
||||||
\section1 Remarks
|
\section1 Remarks
|
||||||
|
|
||||||
|
\note QDateTime does not account for leap seconds.
|
||||||
|
|
||||||
\note All conversion to and from string formats is done using the C locale.
|
\note All conversion to and from string formats is done using the C locale.
|
||||||
For localized conversions, see QLocale.
|
For localized conversions, see QLocale.
|
||||||
|
|
||||||
@ -3411,6 +3501,12 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
considered invalid. The year -1 is the year "1 before Christ" or "1 before
|
considered invalid. The year -1 is the year "1 before Christ" or "1 before
|
||||||
common era." The day before 1 January 1 CE is 31 December 1 BCE.
|
common era." The day before 1 January 1 CE is 31 December 1 BCE.
|
||||||
|
|
||||||
|
\note Using local time (the default) or a specified time zone implies a need
|
||||||
|
to resolve any issues around \l {Timezone transitions}{transitions}. As a
|
||||||
|
result, operations on such QDateTime instances (notably including
|
||||||
|
constructing them) may be more expensive than the equivalent when using UTC
|
||||||
|
or a fixed offset from it.
|
||||||
|
|
||||||
\section2 Range of Valid Dates
|
\section2 Range of Valid Dates
|
||||||
|
|
||||||
The range of values that QDateTime can represent is dependent on the
|
The range of values that QDateTime can represent is dependent on the
|
||||||
@ -3440,23 +3536,67 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
library will equipe QTimeZone with the same timezone database as is used on
|
library will equipe QTimeZone with the same timezone database as is used on
|
||||||
Unix.
|
Unix.
|
||||||
|
|
||||||
\section2 Daylight-Saving Time (DST)
|
\section2 Timezone transitions
|
||||||
|
|
||||||
QDateTime takes into account transitions between Standard Time and
|
QDateTime takes into account timezone transitions, both the transitions
|
||||||
Daylight-Saving Time. For example, if the transition is at 2am and the clock
|
between Standard Time and Daylight-Saving Time (DST) and the transitions
|
||||||
goes forward to 3am, then there is a "missing" hour from 02:00:00 to
|
that arise when a zone changes its standard offset. For example, if the
|
||||||
02:59:59.999 which QDateTime considers to be invalid. Any date arithmetic
|
transition is at 2am and the clock goes forward to 3am, then there is a
|
||||||
performed will take this missing hour into account and return a valid
|
"missing" hour from 02:00:00 to 02:59:59.999. Such a transition is known as
|
||||||
result. For example, adding one second to 01:59:59 will get 03:00:00.
|
a "spring forward" and the times skipped over have no meaning. When a
|
||||||
|
transition goes the other way, known as a "fall back", a time interval is
|
||||||
|
repeated, first in the old zone (usually DST), then in the new zone (usually
|
||||||
|
Standard Time), so times in this interval are ambiguous.
|
||||||
|
|
||||||
|
Some zones use "reversed" DST, using standard time in summer and
|
||||||
|
daylight-saving time (with a lowered offset) in winter. For such zones, the
|
||||||
|
spring forward still happens in spring and skips an hour, but is a
|
||||||
|
transition \e{out of} daylight-saving time, while the fall back still
|
||||||
|
repeats an autumn hour but is a transition \e to daylight-saving time.
|
||||||
|
|
||||||
|
When converting from a UTC time (or a time at fixed offset from UTC), there
|
||||||
|
is always an unambiguous valid result in any timezone. However, when
|
||||||
|
combining a date and time to make a datetime, expressed with respect to
|
||||||
|
local time or a specific time-zone, the nominal result may fall in a
|
||||||
|
transition, making it either invalid or ambiguous. Methods where this
|
||||||
|
situation may arise take a \c resolve parameter: this is always ignored if
|
||||||
|
the requested datetime is valid and unambiguous. See \l TransitionResolution
|
||||||
|
for the options it lets you control. Prior to Qt 6.7, the equivalent of its
|
||||||
|
\l LegacyBehavior was selected.
|
||||||
|
|
||||||
|
For a spring forward's skipped interval, interpreting the requested time
|
||||||
|
with either offset yields an actual time at which the other offset was in
|
||||||
|
use; so passing \c TransitionResolution::RelativeToBefore for \c resolve
|
||||||
|
will actually result in a time after the transition, that would have had the
|
||||||
|
requested representation had the transition not happened. Likewise, \c
|
||||||
|
TransitionResolution::RelativeToAfter for \c resolve results in a time
|
||||||
|
before the transition, that would have had the requested representation, had
|
||||||
|
the transition happened earlier.
|
||||||
|
|
||||||
|
When QDateTime performs arithmetic, as with addDay() or addSecs(), it takes
|
||||||
|
care to produce a valid result. For example, on a day when there is a spring
|
||||||
|
forward from 02:00 to 03:00, adding one second to 01:59:59 will get
|
||||||
|
03:00:00. Adding one day to 02:30 on the preceding day will get 03:30 on the
|
||||||
|
day of the transition, while subtracting one day, by calling \c{addDay(-1)},
|
||||||
|
to 02:30 on the following day will get 01:30 on the day of the transition.
|
||||||
|
While addSecs() will deliver a time offset by the given number of seconds,
|
||||||
|
addDays() adjusts the date and only adjusts time if it would otherwise get
|
||||||
|
an invalid result. Applying \c{addDays(1)} to 03:00 on the day before the
|
||||||
|
spring-forward will simply get 03:00 on the day of the transition, even
|
||||||
|
though the latter is only 23 hours after the former; but \c{addSecs(24 * 60
|
||||||
|
* 60)} will get 04:00 on the day of the transition, since that's 24 hours
|
||||||
|
later. Typical transitions make some days 23 or 25 hours long.
|
||||||
|
|
||||||
For datetimes that the system \c time_t can represent (from 1901-12-14 to
|
For datetimes that the system \c time_t can represent (from 1901-12-14 to
|
||||||
2038-01-18 on systems with 32-bit \c time_t; for the full range QDateTime
|
2038-01-18 on systems with 32-bit \c time_t; for the full range QDateTime
|
||||||
can represent if the type is 64-bit), the standard system APIs are used to
|
can represent if the type is 64-bit), the standard system APIs are used to
|
||||||
determine local time's offset from UTC. For datetimes not handled by these
|
determine local time's offset from UTC. For datetimes not handled by these
|
||||||
system APIs, QTimeZone::systemTimeZone() is used. In either case, the offset
|
system APIs (potentially including some within the \c time_t range),
|
||||||
information used depends on the system and may be incomplete or, for past
|
QTimeZone::systemTimeZone() is used, if available, or a best effort is made
|
||||||
times, historically inaccurate. In any case, for future dates, the local
|
to estimate. In any case, the offset information used depends on the system
|
||||||
time zone's offsets and DST rules may change before that date comes around.
|
and may be incomplete or, for past times, historically
|
||||||
|
inaccurate. Furthermore, for future dates, the local time zone's offsets and
|
||||||
|
DST rules may change before that date comes around.
|
||||||
|
|
||||||
\section2 Offsets From UTC
|
\section2 Offsets From UTC
|
||||||
|
|
||||||
@ -3471,7 +3611,8 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
which use a ±hh:mm format, effectively limiting the range to ± 99 hours and
|
which use a ±hh:mm format, effectively limiting the range to ± 99 hours and
|
||||||
59 minutes and whole minutes only. Note that currently no time zone has an
|
59 minutes and whole minutes only. Note that currently no time zone has an
|
||||||
offset outside the range of ±14 hours and all known offsets are multiples of
|
offset outside the range of ±14 hours and all known offsets are multiples of
|
||||||
five minutes.
|
five minutes. Historical time zones have a wider range and may have offsets
|
||||||
|
including seconds; these last cannot be faithfully represented in strings.
|
||||||
|
|
||||||
\sa QDate, QTime, QDateTimeEdit, QTimeZone
|
\sa QDate, QTime, QDateTimeEdit, QTimeZone
|
||||||
*/
|
*/
|
||||||
@ -3496,6 +3637,148 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
|||||||
\sa isValid(), QDate
|
\sa isValid(), QDate
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 6.7
|
||||||
|
\enum QDateTime::TransitionResolution
|
||||||
|
|
||||||
|
This enumeration is used to resolve datetime combinations which fall in \l
|
||||||
|
{Timezone transitions}.
|
||||||
|
|
||||||
|
When constructing a datetime, specified in terms of local time or a
|
||||||
|
time-zone that has daylight-saving time, or revising one with setDate(),
|
||||||
|
setTime() or setTimeZone(), the given parameters may imply a time
|
||||||
|
representation that either has no meaning or has two meanings in the
|
||||||
|
zone. Such time representations are described as being in the transition. In
|
||||||
|
either case, we can simply return an invalid datetime, to indicate that the
|
||||||
|
operation is ill-defined. In the ambiguous case, we can alternatively select
|
||||||
|
one of the two times that could be meant. When there is no meaning, we can
|
||||||
|
select a time either side of it that might plausibly have been meant. For
|
||||||
|
example, when advancing from an earlier time, we can select the time after
|
||||||
|
the transition that is actually the specified amount of time after the
|
||||||
|
earlier time in question. The options specified here configure how such
|
||||||
|
selection is performed.
|
||||||
|
|
||||||
|
\value Reject
|
||||||
|
Treat any time in a transition as invalid. Either it really is, or it
|
||||||
|
is ambiguous.
|
||||||
|
\value RelativeToBefore
|
||||||
|
Selects a time as if stepping forward from a time before the
|
||||||
|
transition. This interprets the requested time using the offset in
|
||||||
|
effect before the transition and, if necessary, converts the result
|
||||||
|
to the offset in effect at the resulting time.
|
||||||
|
\value RelativeToAfter
|
||||||
|
Select a time as if stepping backward from a time after the
|
||||||
|
transition. This interprets the requested time using the offset in
|
||||||
|
effect after the transition and, if necessary, converts the result to
|
||||||
|
the offset in effect at the resulting time.
|
||||||
|
\value PreferBefore
|
||||||
|
Selects a time before the transition,
|
||||||
|
\value PreferAfter
|
||||||
|
Selects a time after the transition.
|
||||||
|
\value PreferStandard
|
||||||
|
Selects a time on the standard time side of the transition.
|
||||||
|
\value PreferDaylightSaving
|
||||||
|
Selects a time on the daylight-saving-time side of the transition.
|
||||||
|
\value LegacyBehavior
|
||||||
|
An alias for RelativeToBefore, which is used as default for
|
||||||
|
TransitionResolution parameters, as this most closely matches the
|
||||||
|
behavior prior to Qt 6.7.
|
||||||
|
|
||||||
|
For \l addDays(), \l addMonths() or \l addYears(), the behavior is and
|
||||||
|
(mostly) was to use \c RelativeToBefore if adding a positive adjustment and \c
|
||||||
|
RelativeToAfter if adding a negative adjustment.
|
||||||
|
|
||||||
|
\note In time zones where daylight-saving increases the offset from UTC in
|
||||||
|
summer (known as "positive DST"), PreferStandard is an alias for
|
||||||
|
RelativeToAfter and PreferDaylightSaving for RelativeToBefore. In time zones
|
||||||
|
where the daylight-saving mechanism is a decrease in offset from UTC in
|
||||||
|
winter (known as "negative DST"), the reverse applies, provided the
|
||||||
|
operating system reports - as it does on most platforms - whether a datetime
|
||||||
|
is in DST or standard time. For some platforms, where transition times are
|
||||||
|
unavailable even for Qt::TimeZone datetimes, QTimeZone is obliged to presume
|
||||||
|
that the side with lower offset from UTC is standard time, effectively
|
||||||
|
assuming positive DST.
|
||||||
|
|
||||||
|
The following tables illustrate how a QDateTime constructor resolves a
|
||||||
|
request for 02:30 on a day when local time has a transition between 02:00
|
||||||
|
and 03:00, with a nominal standard time LST and daylight-saving time LDT on
|
||||||
|
the two sides, in the various possible cases. The transition type may be to
|
||||||
|
skip an hour or repeat it. The type of transition and value of a parameter
|
||||||
|
\c resolve determine which actual time on the given date is selected. First,
|
||||||
|
the common case of positive daylight-saving, where:
|
||||||
|
|
||||||
|
\table
|
||||||
|
\header \li Before \li 02:00--03:00 \li After \li \c resolve \li selected
|
||||||
|
\row \li LST \li skip \li LDT \li RelativeToBefore \li 03:30 LDT
|
||||||
|
\row \li LST \li skip \li LDT \li RelativeToAfter \li 01:30 LST
|
||||||
|
\row \li LST \li skip \li LDT \li PreferBefore \li 01:30 LST
|
||||||
|
\row \li LST \li skip \li LDT \li PreferAfter \li 03:30 LDT
|
||||||
|
\row \li LST \li skip \li LDT \li PreferStandard \li 01:30 LST
|
||||||
|
\row \li LST \li skip \li LDT \li PreferDaylightSaving \li 03:30 LDT
|
||||||
|
\row \li LDT \li repeat \li LST \li RelativeToBefore \li 02:30 LDT
|
||||||
|
\row \li LDT \li repeat \li LST \li RelativeToAfter \li 02:30 LST
|
||||||
|
\row \li LDT \li repeat \li LST \li PreferBefore \li 02:30 LDT
|
||||||
|
\row \li LDT \li repeat \li LST \li PreferAfter \li 02:30 LST
|
||||||
|
\row \li LDT \li repeat \li LST \li PreferStandard \li 02:30 LST
|
||||||
|
\row \li LDT \li repeat \li LST \li PreferDaylightSaving \li 02:30 LDT
|
||||||
|
\endtable
|
||||||
|
|
||||||
|
Second, the case for negative daylight-saving, using LDT in winter and
|
||||||
|
skipping an hour to transition to LST in summer, then repeating an hour at
|
||||||
|
the transition back to winter:
|
||||||
|
|
||||||
|
\table
|
||||||
|
\row \li LDT \li skip \li LST \li RelativeToBefore \li 03:30 LST
|
||||||
|
\row \li LDT \li skip \li LST \li RelativeToAfter \li 01:30 LDT
|
||||||
|
\row \li LDT \li skip \li LST \li PreferBefore \li 01:30 LDT
|
||||||
|
\row \li LDT \li skip \li LST \li PreferAfter \li 03:30 LST
|
||||||
|
\row \li LDT \li skip \li LST \li PreferStandard \li 03:30 LST
|
||||||
|
\row \li LDT \li skip \li LST \li PreferDaylightSaving \li 01:30 LDT
|
||||||
|
\row \li LST \li repeat \li LDT \li RelativeToBefore \li 02:30 LST
|
||||||
|
\row \li LST \li repeat \li LDT \li RelativeToAfter \li 02:30 LDT
|
||||||
|
\row \li LST \li repeat \li LDT \li PreferBefore \li 02:30 LST
|
||||||
|
\row \li LST \li repeat \li LDT \li PreferAfter \li 02:30 LDT
|
||||||
|
\row \li LST \li repeat \li LDT \li PreferStandard \li 02:30 LST
|
||||||
|
\row \li LST \li repeat \li LDT \li PreferDaylightSaving \li 02:30 LDT
|
||||||
|
\endtable
|
||||||
|
|
||||||
|
Reject can be used to prompt relevant QDateTime APIs to return an invalid
|
||||||
|
datetime object so that your code can deal with transitions for itself, for
|
||||||
|
example by alerting a user to the fact that the datetime they have selected
|
||||||
|
is in a transition interval, to offer them the opportunity to resolve a
|
||||||
|
conflict or ambiguity. Code using this may well find the other options above
|
||||||
|
useful to determine relevant information to use in its own (or the user's)
|
||||||
|
resolution. If the start or end of the transition, or the moment of the
|
||||||
|
transition itself, is the right resolution, QTimeZone's transition APIs can
|
||||||
|
be used to obtain that information. You can determine whether the transition
|
||||||
|
is a repeated or skipped interval by using \l secsTo() to measure the actual
|
||||||
|
time between noon on the previous and following days. The result will be
|
||||||
|
less than 48 hours for a skipped interval (such as a spring-forward) and
|
||||||
|
more than 48 hours for a repeated interval (such as a fall-back).
|
||||||
|
|
||||||
|
\note When a resolution other than Reject is specified, a valid QDateTime
|
||||||
|
object is returned, if possible. If the requested date-time falls in a gap,
|
||||||
|
the returned date-time will not have the time() requested - or, in some
|
||||||
|
cases, the date(), if a whole day was skipped. You can thus detect when a
|
||||||
|
gap is hit by comparing date() and time() to what was requested.
|
||||||
|
|
||||||
|
\section2 Relation to other datetime software
|
||||||
|
|
||||||
|
The Python programming language's datetime APIs have a \c fold parameter
|
||||||
|
that corresponds to \c RelativeToBefore (\c{fold = True}) and \c
|
||||||
|
RelativeToAfter (\c{fold = False}).
|
||||||
|
|
||||||
|
The \c Temporal proposal to replace JavaScript's \c Date offers four options
|
||||||
|
for how to resolve a transition, as value for a \c disambiguation
|
||||||
|
parameter. Its \c{'reject'} raises an exception, which roughly corresponds
|
||||||
|
to \c Reject producing an invalid result. Its \c{'earlier'} and \c{'later'}
|
||||||
|
options correspond to \c PreferBefore and \c PreferAfter. Its
|
||||||
|
\c{'compatible'} option corresponds to \c RelativeToBefore (and Python's
|
||||||
|
\c{fold = True}).
|
||||||
|
|
||||||
|
\sa {Timezone transitions}, QDateTime::TransitionResolution
|
||||||
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Constructs a null datetime, nominally using local time.
|
Constructs a null datetime, nominally using local time.
|
||||||
|
|
||||||
@ -3534,7 +3817,8 @@ QDateTime::QDateTime() noexcept
|
|||||||
skipped over the given date and time, the result is invalid.
|
skipped over the given date and time, the result is invalid.
|
||||||
*/
|
*/
|
||||||
QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSeconds)
|
QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSeconds)
|
||||||
: d(QDateTimePrivate::create(date, time, asTimeZone(spec, offsetSeconds, "QDateTime")))
|
: d(QDateTimePrivate::create(date, time, asTimeZone(spec, offsetSeconds, "QDateTime"),
|
||||||
|
TransitionResolution::LegacyBehavior))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
#endif // 6.9 deprecation
|
#endif // 6.9 deprecation
|
||||||
@ -3546,24 +3830,36 @@ QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSecond
|
|||||||
representation described by \a timeZone.
|
representation described by \a timeZone.
|
||||||
|
|
||||||
If \a date is valid and \a time is not, the time will be set to midnight.
|
If \a date is valid and \a time is not, the time will be set to midnight.
|
||||||
If \a timeZone is invalid then the datetime will be invalid.
|
If \a timeZone is invalid then the datetime will be invalid. If \a date and
|
||||||
|
\a time describe a moment close to a transition for \a timeZone, \a resolve
|
||||||
|
controls how that situation is resolved.
|
||||||
|
|
||||||
|
//! [pre-resolve-note]
|
||||||
|
\note Prior to Qt 6.7, the version of this function lacked the \a resolve
|
||||||
|
parameter so had no way to resolve the ambiguities related to transitions.
|
||||||
|
//! [pre-resolve-note]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone)
|
QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone, TransitionResolution resolve)
|
||||||
: d(QDateTimePrivate::create(date, time, timeZone))
|
: d(QDateTimePrivate::create(date, time, timeZone, resolve))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\since 6.5
|
\since 6.5
|
||||||
|
\overload
|
||||||
|
|
||||||
Constructs a datetime with the given \a date and \a time, using local time.
|
Constructs a datetime with the given \a date and \a time, using local time.
|
||||||
|
|
||||||
If \a date is valid and \a time is not, midnight will be used as the time.
|
If \a date is valid and \a time is not, midnight will be used as the
|
||||||
|
time. If \a date and \a time describe a moment close to a transition for
|
||||||
|
local time, \a resolve controls how that situation is resolved.
|
||||||
|
|
||||||
|
\include qdatetime.cpp pre-resolve-note
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QDateTime::QDateTime(QDate date, QTime time)
|
QDateTime::QDateTime(QDate date, QTime time, TransitionResolution resolve)
|
||||||
: d(QDateTimePrivate::create(date, time, QTimeZone::LocalTime))
|
: d(QDateTimePrivate::create(date, time, QTimeZone::LocalTime, resolve))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3754,8 +4050,8 @@ int QDateTime::offsetFromUtc() const
|
|||||||
auto spec = extractSpec(status);
|
auto spec = extractSpec(status);
|
||||||
if (spec == Qt::LocalTime) {
|
if (spec == Qt::LocalTime) {
|
||||||
// We didn't cache the value, so we need to calculate it:
|
// We didn't cache the value, so we need to calculate it:
|
||||||
auto dst = extractDaylightStatus(status);
|
const auto resolve = toTransitionOptions(extractDaylightStatus(status));
|
||||||
return QDateTimePrivate::localStateAtMillis(getMSecs(d), dst).offset;
|
return QDateTimePrivate::localStateAtMillis(getMSecs(d), resolve).offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(spec == Qt::UTC);
|
Q_ASSERT(spec == Qt::UTC);
|
||||||
@ -3840,8 +4136,10 @@ bool QDateTime::isDaylightTime() const
|
|||||||
#endif // timezone
|
#endif // timezone
|
||||||
case Qt::LocalTime: {
|
case Qt::LocalTime: {
|
||||||
auto dst = extractDaylightStatus(getStatus(d));
|
auto dst = extractDaylightStatus(getStatus(d));
|
||||||
if (dst == QDateTimePrivate::UnknownDaylightTime)
|
if (dst == QDateTimePrivate::UnknownDaylightTime) {
|
||||||
dst = QDateTimePrivate::localStateAtMillis(getMSecs(d), dst).dst;
|
dst = QDateTimePrivate::localStateAtMillis(
|
||||||
|
getMSecs(d), toTransitionOptions(TransitionResolution::LegacyBehavior)).dst;
|
||||||
|
}
|
||||||
return dst == QDateTimePrivate::DaylightTime;
|
return dst == QDateTimePrivate::DaylightTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3849,16 +4147,24 @@ bool QDateTime::isDaylightTime() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Sets the date part of this datetime to \a date. If no time is set yet, it
|
Sets the date part of this datetime to \a date.
|
||||||
is set to midnight. If \a date is invalid, this QDateTime becomes invalid.
|
|
||||||
|
If no time is set yet, it is set to midnight. If \a date is invalid, this
|
||||||
|
QDateTime becomes invalid.
|
||||||
|
|
||||||
|
If \a date and time() describe a moment close to a transition for this
|
||||||
|
datetime's time representation, \a resolve controls how that situation is
|
||||||
|
resolved.
|
||||||
|
|
||||||
|
\include qdatetime.cpp pre-resolve-note
|
||||||
|
|
||||||
\sa date(), setTime(), setTimeZone()
|
\sa date(), setTime(), setTimeZone()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void QDateTime::setDate(QDate date)
|
void QDateTime::setDate(QDate date, TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
setDateTime(d, date, time());
|
setDateTime(d, date, time());
|
||||||
checkValidDateTime(d);
|
checkValidDateTime(d, resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -3871,13 +4177,19 @@ void QDateTime::setDate(QDate date)
|
|||||||
dt.setTime(QTime());
|
dt.setTime(QTime());
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
|
If date() and \a time describe a moment close to a transition for this
|
||||||
|
datetime's time representation, \a resolve controls how that situation is
|
||||||
|
resolved.
|
||||||
|
|
||||||
|
\include qdatetime.cpp pre-resolve-note
|
||||||
|
|
||||||
\sa time(), setDate(), setTimeZone()
|
\sa time(), setDate(), setTimeZone()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void QDateTime::setTime(QTime time)
|
void QDateTime::setTime(QTime time, TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
setDateTime(d, date(), time);
|
setDateTime(d, date(), time);
|
||||||
checkValidDateTime(d);
|
checkValidDateTime(d, resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_DEPRECATED_SINCE(6, 9)
|
#if QT_DEPRECATED_SINCE(6, 9)
|
||||||
@ -3901,7 +4213,8 @@ void QDateTime::setTime(QTime time)
|
|||||||
|
|
||||||
void QDateTime::setTimeSpec(Qt::TimeSpec spec)
|
void QDateTime::setTimeSpec(Qt::TimeSpec spec)
|
||||||
{
|
{
|
||||||
reviseTimeZone(d, asTimeZone(spec, 0, "QDateTime::setTimeSpec"));
|
reviseTimeZone(d, asTimeZone(spec, 0, "QDateTime::setTimeSpec"),
|
||||||
|
TransitionResolution::LegacyBehavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -3922,7 +4235,8 @@ void QDateTime::setTimeSpec(Qt::TimeSpec spec)
|
|||||||
|
|
||||||
void QDateTime::setOffsetFromUtc(int offsetSeconds)
|
void QDateTime::setOffsetFromUtc(int offsetSeconds)
|
||||||
{
|
{
|
||||||
reviseTimeZone(d, QTimeZone::fromSecondsAheadOfUtc(offsetSeconds));
|
reviseTimeZone(d, QTimeZone::fromSecondsAheadOfUtc(offsetSeconds),
|
||||||
|
TransitionResolution::Reject);
|
||||||
}
|
}
|
||||||
#endif // 6.9 deprecations
|
#endif // 6.9 deprecations
|
||||||
|
|
||||||
@ -3938,12 +4252,17 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds)
|
|||||||
If \a toZone is invalid then the datetime will be invalid. Otherwise, this
|
If \a toZone is invalid then the datetime will be invalid. Otherwise, this
|
||||||
datetime's timeSpec() after the call will match \c{toZone.timeSpec()}.
|
datetime's timeSpec() after the call will match \c{toZone.timeSpec()}.
|
||||||
|
|
||||||
|
If date() and time() describe a moment close to a transition for \a toZone,
|
||||||
|
\a resolve controls how that situation is resolved.
|
||||||
|
|
||||||
|
\include qdatetime.cpp pre-resolve-note
|
||||||
|
|
||||||
\sa timeRepresentation(), timeZone(), Qt::TimeSpec
|
\sa timeRepresentation(), timeZone(), Qt::TimeSpec
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void QDateTime::setTimeZone(const QTimeZone &toZone)
|
void QDateTime::setTimeZone(const QTimeZone &toZone, TransitionResolution resolve)
|
||||||
{
|
{
|
||||||
reviseTimeZone(d, toZone);
|
reviseTimeZone(d, toZone, resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -3983,8 +4302,8 @@ qint64 QDateTime::toMSecsSinceEpoch() const
|
|||||||
case Qt::LocalTime:
|
case Qt::LocalTime:
|
||||||
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
||||||
// Short form has nowhere to cache the offset, so recompute.
|
// Short form has nowhere to cache the offset, so recompute.
|
||||||
auto dst = extractDaylightStatus(status);
|
const auto resolve = toTransitionOptions(extractDaylightStatus(getStatus(d)));
|
||||||
auto state = QDateTimePrivate::localStateAtMillis(getMSecs(d), dst);
|
const auto state = QDateTimePrivate::localStateAtMillis(getMSecs(d), resolve);
|
||||||
return state.when - state.offset * MSECS_PER_SEC;
|
return state.when - state.offset * MSECS_PER_SEC;
|
||||||
}
|
}
|
||||||
// Use the offset saved by refreshZonedDateTime() on creation.
|
// Use the offset saved by refreshZonedDateTime() on creation.
|
||||||
@ -4248,19 +4567,11 @@ QString QDateTime::toString(QStringView format, QCalendar cal) const
|
|||||||
}
|
}
|
||||||
#endif // datestring
|
#endif // datestring
|
||||||
|
|
||||||
static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime time)
|
static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime time, bool forward)
|
||||||
{
|
{
|
||||||
/*
|
const QDateTimePrivate::TransitionOptions resolve = toTransitionOptions(
|
||||||
If we have just adjusted to a day with a DST transition, our given time
|
forward ? QDateTime::TransitionResolution::RelativeToBefore
|
||||||
may lie in the transition hour (either missing or duplicated). For any
|
: QDateTime::TransitionResolution::RelativeToAfter);
|
||||||
other time, telling mktime() or QTimeZone what we know about DST-ness, of
|
|
||||||
the time we adjusted from, will make no difference; it'll just tell us the
|
|
||||||
actual DST-ness of the given time. When landing in a transition that
|
|
||||||
repeats an hour, passing the prior DST-ness - when known - will get us the
|
|
||||||
indicated side of the duplicate (either local or zone). When landing in a
|
|
||||||
gap, the zone gives us the other side of the gap and mktime() is wrapped
|
|
||||||
to coax it into doing the same (which it does by default on Unix).
|
|
||||||
*/
|
|
||||||
auto status = getStatus(d);
|
auto status = getStatus(d);
|
||||||
Q_ASSERT(status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime
|
Q_ASSERT(status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime
|
||||||
| QDateTimePrivate::ValidDateTime));
|
| QDateTimePrivate::ValidDateTime));
|
||||||
@ -4270,13 +4581,13 @@ static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime t
|
|||||||
refreshSimpleDateTime(d);
|
refreshSimpleDateTime(d);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto dst = extractDaylightStatus(status);
|
|
||||||
qint64 local = timeToMSecs(date, time);
|
qint64 local = timeToMSecs(date, time);
|
||||||
const QDateTimePrivate::ZoneState state = stateAtMillis(d.timeZone(), local, dst);
|
const QDateTimePrivate::ZoneState state = stateAtMillis(d.timeZone(), local, resolve);
|
||||||
if (state.valid)
|
Q_ASSERT(state.valid || state.dst == QDateTimePrivate::UnknownDaylightTime);
|
||||||
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
if (state.dst == QDateTimePrivate::UnknownDaylightTime)
|
||||||
else
|
|
||||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||||
|
else
|
||||||
|
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
||||||
|
|
||||||
if (status & QDateTimePrivate::ShortData) {
|
if (status & QDateTimePrivate::ShortData) {
|
||||||
d.data.msecs = state.when;
|
d.data.msecs = state.when;
|
||||||
@ -4303,7 +4614,7 @@ static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime t
|
|||||||
aiming between 2am and 3am will be adjusted to fall before 2am (if \c{ndays
|
aiming between 2am and 3am will be adjusted to fall before 2am (if \c{ndays
|
||||||
< 0}) or after 3am (otherwise).
|
< 0}) or after 3am (otherwise).
|
||||||
|
|
||||||
\sa daysTo(), addMonths(), addYears(), addSecs()
|
\sa daysTo(), addMonths(), addYears(), addSecs(), {Timezone transitions}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QDateTime QDateTime::addDays(qint64 ndays) const
|
QDateTime QDateTime::addDays(qint64 ndays) const
|
||||||
@ -4313,7 +4624,7 @@ QDateTime QDateTime::addDays(qint64 ndays) const
|
|||||||
|
|
||||||
QDateTime dt(*this);
|
QDateTime dt(*this);
|
||||||
QPair<QDate, QTime> p = getDateTime(d);
|
QPair<QDate, QTime> p = getDateTime(d);
|
||||||
massageAdjustedDateTime(dt.d, p.first.addDays(ndays), p.second);
|
massageAdjustedDateTime(dt.d, p.first.addDays(ndays), p.second, ndays >= 0);
|
||||||
return dt;
|
return dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4329,7 +4640,7 @@ QDateTime QDateTime::addDays(qint64 ndays) const
|
|||||||
aiming between 2am and 3am will be adjusted to fall before 2am (if
|
aiming between 2am and 3am will be adjusted to fall before 2am (if
|
||||||
\c{nmonths < 0}) or after 3am (otherwise).
|
\c{nmonths < 0}) or after 3am (otherwise).
|
||||||
|
|
||||||
\sa daysTo(), addDays(), addYears(), addSecs()
|
\sa daysTo(), addDays(), addYears(), addSecs(), {Timezone transitions}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QDateTime QDateTime::addMonths(int nmonths) const
|
QDateTime QDateTime::addMonths(int nmonths) const
|
||||||
@ -4339,7 +4650,7 @@ QDateTime QDateTime::addMonths(int nmonths) const
|
|||||||
|
|
||||||
QDateTime dt(*this);
|
QDateTime dt(*this);
|
||||||
QPair<QDate, QTime> p = getDateTime(d);
|
QPair<QDate, QTime> p = getDateTime(d);
|
||||||
massageAdjustedDateTime(dt.d, p.first.addMonths(nmonths), p.second);
|
massageAdjustedDateTime(dt.d, p.first.addMonths(nmonths), p.second, nmonths >= 0);
|
||||||
return dt;
|
return dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4355,7 +4666,7 @@ QDateTime QDateTime::addMonths(int nmonths) const
|
|||||||
aiming between 2am and 3am will be adjusted to fall before 2am (if \c{nyears
|
aiming between 2am and 3am will be adjusted to fall before 2am (if \c{nyears
|
||||||
< 0}) or after 3am (otherwise).
|
< 0}) or after 3am (otherwise).
|
||||||
|
|
||||||
\sa daysTo(), addDays(), addMonths(), addSecs()
|
\sa daysTo(), addDays(), addMonths(), addSecs(), {Timezone transitions}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QDateTime QDateTime::addYears(int nyears) const
|
QDateTime QDateTime::addYears(int nyears) const
|
||||||
@ -4365,7 +4676,7 @@ QDateTime QDateTime::addYears(int nyears) const
|
|||||||
|
|
||||||
QDateTime dt(*this);
|
QDateTime dt(*this);
|
||||||
QPair<QDate, QTime> p = getDateTime(d);
|
QPair<QDate, QTime> p = getDateTime(d);
|
||||||
massageAdjustedDateTime(dt.d, p.first.addYears(nyears), p.second);
|
massageAdjustedDateTime(dt.d, p.first.addYears(nyears), p.second, nyears >= 0);
|
||||||
return dt;
|
return dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5109,7 +5420,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs
|
|||||||
QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
|
QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
|
||||||
{
|
{
|
||||||
QDateTime dt;
|
QDateTime dt;
|
||||||
reviseTimeZone(dt.d, timeZone);
|
reviseTimeZone(dt.d, timeZone, TransitionResolution::Reject);
|
||||||
if (timeZone.isValid())
|
if (timeZone.isValid())
|
||||||
dt.setMSecsSinceEpoch(msecs);
|
dt.setMSecsSinceEpoch(msecs);
|
||||||
return dt;
|
return dt;
|
||||||
@ -5141,7 +5452,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs)
|
|||||||
QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
|
QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
|
||||||
{
|
{
|
||||||
QDateTime dt;
|
QDateTime dt;
|
||||||
reviseTimeZone(dt.d, timeZone);
|
reviseTimeZone(dt.d, timeZone, TransitionResolution::Reject);
|
||||||
if (timeZone.isValid())
|
if (timeZone.isValid())
|
||||||
dt.setSecsSinceEpoch(secs);
|
dt.setSecsSinceEpoch(secs);
|
||||||
return dt;
|
return dt;
|
||||||
@ -5355,10 +5666,8 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
|
|||||||
|
|
||||||
If the format is not satisfied, an invalid QDateTime is returned. If the
|
If the format is not satisfied, an invalid QDateTime is returned. If the
|
||||||
format is satisfied but \a string represents an invalid datetime (e.g. in a
|
format is satisfied but \a string represents an invalid datetime (e.g. in a
|
||||||
gap skipped by a time-zone transition), an invalid QDateTime is returned,
|
gap skipped by a time-zone transition), an valid QDateTime is returned, that
|
||||||
whose toMSecsSinceEpoch() represents a near-by datetime that is
|
represents a near-by datetime that is valid.
|
||||||
valid. Passing that to fromMSecsSinceEpoch() will produce a valid datetime
|
|
||||||
that isn't faithfully represented by the string parsed.
|
|
||||||
|
|
||||||
The expressions that don't have leading zeroes (d, M, h, m, s, z) will be
|
The expressions that don't have leading zeroes (d, M, h, m, s, z) will be
|
||||||
greedy. This means that they will use two digits (or three, for z) even if this will
|
greedy. This means that they will use two digits (or three, for z) even if this will
|
||||||
@ -5609,6 +5918,7 @@ QDataStream &operator>>(QDataStream &in, QDateTime &dateTime)
|
|||||||
in >> zone;
|
in >> zone;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Note: no way to resolve transition ambiguity, when relevant; use default.
|
||||||
dateTime = QDateTime(dt, tm, zone);
|
dateTime = QDateTime(dt, tm, zone);
|
||||||
|
|
||||||
} else if (in.version() == QDataStream::Qt_5_0) {
|
} else if (in.version() == QDataStream::Qt_5_0) {
|
||||||
|
@ -311,12 +311,31 @@ class Q_CORE_EXPORT QDateTime
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
QDateTime() noexcept;
|
QDateTime() noexcept;
|
||||||
|
|
||||||
|
enum class TransitionResolution {
|
||||||
|
Reject = 0,
|
||||||
|
RelativeToBefore,
|
||||||
|
RelativeToAfter,
|
||||||
|
PreferBefore,
|
||||||
|
PreferAfter,
|
||||||
|
PreferStandard,
|
||||||
|
PreferDaylightSaving,
|
||||||
|
// Closest match to behavior prior to introducing TransitionResolution:
|
||||||
|
LegacyBehavior = RelativeToBefore
|
||||||
|
};
|
||||||
|
|
||||||
#if QT_DEPRECATED_SINCE(6, 9)
|
#if QT_DEPRECATED_SINCE(6, 9)
|
||||||
QT_DEPRECATED_VERSION_X_6_9("Pass QTimeZone instead")
|
QT_DEPRECATED_VERSION_X_6_9("Pass QTimeZone instead")
|
||||||
QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSeconds = 0);
|
QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSeconds = 0);
|
||||||
#endif
|
#endif
|
||||||
|
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||||
QDateTime(QDate date, QTime time, const QTimeZone &timeZone);
|
QDateTime(QDate date, QTime time, const QTimeZone &timeZone);
|
||||||
QDateTime(QDate date, QTime time);
|
QDateTime(QDate date, QTime time);
|
||||||
|
#endif
|
||||||
|
QDateTime(QDate date, QTime time, const QTimeZone &timeZone,
|
||||||
|
TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||||
|
QDateTime(QDate date, QTime time,
|
||||||
|
TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||||
QDateTime(const QDateTime &other) noexcept;
|
QDateTime(const QDateTime &other) noexcept;
|
||||||
QDateTime(QDateTime &&other) noexcept;
|
QDateTime(QDateTime &&other) noexcept;
|
||||||
~QDateTime();
|
~QDateTime();
|
||||||
@ -343,15 +362,24 @@ public:
|
|||||||
qint64 toMSecsSinceEpoch() const;
|
qint64 toMSecsSinceEpoch() const;
|
||||||
qint64 toSecsSinceEpoch() const;
|
qint64 toSecsSinceEpoch() const;
|
||||||
|
|
||||||
|
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||||
void setDate(QDate date);
|
void setDate(QDate date);
|
||||||
void setTime(QTime time);
|
void setTime(QTime time);
|
||||||
|
#endif
|
||||||
|
void setDate(QDate date, TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||||
|
void setTime(QTime time, TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||||
|
|
||||||
#if QT_DEPRECATED_SINCE(6, 9)
|
#if QT_DEPRECATED_SINCE(6, 9)
|
||||||
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
||||||
void setTimeSpec(Qt::TimeSpec spec);
|
void setTimeSpec(Qt::TimeSpec spec);
|
||||||
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
||||||
void setOffsetFromUtc(int offsetSeconds);
|
void setOffsetFromUtc(int offsetSeconds);
|
||||||
#endif
|
#endif
|
||||||
|
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||||
void setTimeZone(const QTimeZone &toZone);
|
void setTimeZone(const QTimeZone &toZone);
|
||||||
|
#endif
|
||||||
|
void setTimeZone(const QTimeZone &toZone,
|
||||||
|
TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||||
void setMSecsSinceEpoch(qint64 msecs);
|
void setMSecsSinceEpoch(qint64 msecs);
|
||||||
void setSecsSinceEpoch(qint64 secs);
|
void setSecsSinceEpoch(qint64 secs);
|
||||||
|
|
||||||
|
@ -73,6 +73,22 @@ public:
|
|||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(StatusFlags, StatusFlag)
|
Q_DECLARE_FLAGS(StatusFlags, StatusFlag)
|
||||||
|
|
||||||
|
|
||||||
|
enum TransitionOption {
|
||||||
|
// Handling of a spring-forward (or other gap):
|
||||||
|
GapUseBefore = 2,
|
||||||
|
GapUseAfter = 4,
|
||||||
|
// Handling of a fall-back (or other repeated period):
|
||||||
|
FoldUseBefore = 0x20,
|
||||||
|
FoldUseAfter = 0x40,
|
||||||
|
// Quirk for negative DST:
|
||||||
|
FlipForReverseDst = 0x400,
|
||||||
|
|
||||||
|
GapMask = GapUseBefore | GapUseAfter,
|
||||||
|
FoldMask = FoldUseBefore | FoldUseAfter,
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(TransitionOptions, TransitionOption)
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
TimeSpecShift = 4,
|
TimeSpecShift = 4,
|
||||||
};
|
};
|
||||||
@ -89,14 +105,16 @@ public:
|
|||||||
: when(w), offset(o), dst(d), valid(v) {}
|
: when(w), offset(o), dst(d), valid(v) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
static QDateTime::Data create(QDate toDate, QTime toTime, const QTimeZone &timeZone);
|
static QDateTime::Data create(QDate toDate, QTime toTime, const QTimeZone &timeZone,
|
||||||
|
QDateTime::TransitionResolution resolve);
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
static ZoneState zoneStateAtMillis(const QTimeZone &zone, qint64 millis, DaylightStatus dst);
|
static ZoneState zoneStateAtMillis(const QTimeZone &zone, qint64 millis,
|
||||||
|
TransitionOptions resolve);
|
||||||
#endif // timezone
|
#endif // timezone
|
||||||
|
|
||||||
static ZoneState expressUtcAsLocal(qint64 utcMSecs);
|
static ZoneState expressUtcAsLocal(qint64 utcMSecs);
|
||||||
|
|
||||||
static ZoneState localStateAtMillis(qint64 millis, DaylightStatus dst);
|
static ZoneState localStateAtMillis(qint64 millis, TransitionOptions resolve);
|
||||||
static QString localNameAtMillis(qint64 millis, DaylightStatus dst); // empty if unknown
|
static QString localNameAtMillis(qint64 millis, DaylightStatus dst); // empty if unknown
|
||||||
|
|
||||||
StatusFlags m_status = StatusFlag(Qt::LocalTime << TimeSpecShift);
|
StatusFlags m_status = StatusFlag(Qt::LocalTime << TimeSpecShift);
|
||||||
@ -106,6 +124,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimePrivate::StatusFlags)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimePrivate::StatusFlags)
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimePrivate::TransitionOptions)
|
||||||
|
|
||||||
namespace QtPrivate {
|
namespace QtPrivate {
|
||||||
namespace DateTimeConstants {
|
namespace DateTimeConstants {
|
||||||
|
@ -798,11 +798,6 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||||||
&& m_text.at(offset) == u'-');
|
&& m_text.at(offset) == u'-');
|
||||||
const int negativeYearOffset = negate ? 1 : 0;
|
const int negativeYearOffset = negate ? 1 : 0;
|
||||||
|
|
||||||
// If the fields we've read thus far imply a time in a spring-forward,
|
|
||||||
// coerce to a nearby valid time:
|
|
||||||
const QDateTime defaultValue = currentValue.isValid() ? currentValue
|
|
||||||
: QDateTime::fromMSecsSinceEpoch(currentValue.toMSecsSinceEpoch());
|
|
||||||
|
|
||||||
QStringView sectionTextRef =
|
QStringView sectionTextRef =
|
||||||
QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
|
QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
|
||||||
|
|
||||||
@ -838,7 +833,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||||||
m_text.replace(offset, used, sectiontext.constData(), used);
|
m_text.replace(offset, used, sectiontext.constData(), used);
|
||||||
break; }
|
break; }
|
||||||
case TimeZoneSection:
|
case TimeZoneSection:
|
||||||
result = findTimeZone(sectionTextRef, defaultValue,
|
result = findTimeZone(sectionTextRef, currentValue,
|
||||||
absoluteMax(sectionIndex),
|
absoluteMax(sectionIndex),
|
||||||
absoluteMin(sectionIndex), sn.count);
|
absoluteMin(sectionIndex), sn.count);
|
||||||
break;
|
break;
|
||||||
@ -850,7 +845,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||||||
int num = 0, used = 0;
|
int num = 0, used = 0;
|
||||||
if (sn.type == MonthSection) {
|
if (sn.type == MonthSection) {
|
||||||
const QDate minDate = getMinimum().date();
|
const QDate minDate = getMinimum().date();
|
||||||
const int year = defaultValue.date().year(calendar);
|
const int year = currentValue.date().year(calendar);
|
||||||
const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
|
const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
|
||||||
num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
|
num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
|
||||||
} else {
|
} else {
|
||||||
@ -955,7 +950,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
|
} else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
|
||||||
if (skipToNextSection(sectionIndex, defaultValue, digitsStr)) {
|
if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
|
||||||
const int missingZeroes = sectionmaxsize - digitsStr.size();
|
const int missingZeroes = sectionmaxsize - digitsStr.size();
|
||||||
result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
|
result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
|
||||||
m_text.insert(offset, QString(missingZeroes, u'0'));
|
m_text.insert(offset, QString(missingZeroes, u'0'));
|
||||||
@ -1432,31 +1427,40 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
|
|||||||
const QTime time(hour, minute, second, msec);
|
const QTime time(hour, minute, second, msec);
|
||||||
const QDateTime when = QDateTime(date, time, timeZone);
|
const QDateTime when = QDateTime(date, time, timeZone);
|
||||||
|
|
||||||
// If hour wasn't specified, check the default we're using exists on the
|
if (when.time() != time || when.date() != date) {
|
||||||
// given date (which might be a spring-forward, skipping an hour).
|
// In a spring-forward, if we hit the skipped hour, we may have been
|
||||||
if (!(isSet & HourSectionMask) && !when.isValid()) {
|
// shunted out of it.
|
||||||
switch (parserType) {
|
|
||||||
case QMetaType::QDateTime: {
|
// If hour wasn't specified, so we're using our default, changing it may
|
||||||
qint64 msecs = when.toMSecsSinceEpoch();
|
// fix that.
|
||||||
// Fortunately, that gets a useful answer, even though when is invalid ...
|
if (!(isSet & HourSectionMask)) {
|
||||||
const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
|
switch (parserType) {
|
||||||
const QTime tick = replace.time();
|
case QMetaType::QDateTime: {
|
||||||
if (replace.date() == date
|
qint64 msecs = when.toMSecsSinceEpoch();
|
||||||
&& (!(isSet & MinuteSection) || tick.minute() == minute)
|
// Fortunately, that gets a useful answer, even though when is invalid ...
|
||||||
&& (!(isSet & SecondSection) || tick.second() == second)
|
const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
|
||||||
&& (!(isSet & MSecSection) || tick.msec() == msec)) {
|
const QTime tick = replace.time();
|
||||||
return StateNode(replace, state, padding, conflicts);
|
if (replace.date() == date
|
||||||
|
&& (!(isSet & MinuteSection) || tick.minute() == minute)
|
||||||
|
&& (!(isSet & SecondSection) || tick.second() == second)
|
||||||
|
&& (!(isSet & MSecSection) || tick.msec() == msec)) {
|
||||||
|
return StateNode(replace, state, padding, conflicts);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case QMetaType::QDate:
|
||||||
|
// Don't care about time, so just use start of day (and ignore spec):
|
||||||
|
return StateNode(date.startOfDay(QTimeZone::UTC),
|
||||||
|
state, padding, conflicts);
|
||||||
|
break;
|
||||||
|
case QMetaType::QTime:
|
||||||
|
// Don't care about date or representation, so pick a safe representation:
|
||||||
|
return StateNode(QDateTime(date, time, QTimeZone::UTC),
|
||||||
|
state, padding, conflicts);
|
||||||
|
default:
|
||||||
|
Q_UNREACHABLE_RETURN(StateNode());
|
||||||
}
|
}
|
||||||
} break;
|
} else if (state > Intermediate) {
|
||||||
case QMetaType::QDate:
|
state = Intermediate;
|
||||||
// Don't care about time, so just use start of day (and ignore spec):
|
|
||||||
return StateNode(date.startOfDay(QTimeZone::UTC), state, padding, conflicts);
|
|
||||||
break;
|
|
||||||
case QMetaType::QTime:
|
|
||||||
// Don't care about date or representation, so pick a safe representation:
|
|
||||||
return StateNode(QDateTime(date, time, QTimeZone::UTC), state, padding, conflicts);
|
|
||||||
default:
|
|
||||||
Q_UNREACHABLE_RETURN(StateNode());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1607,12 +1611,8 @@ QDateTimeParser::parse(const QString &input, int position,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// An invalid time should only arise if we set the state to less than acceptable:
|
||||||
We might have ended up with an invalid datetime: the non-existent hour
|
Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
|
||||||
during dst changes, for instance.
|
|
||||||
*/
|
|
||||||
if (!scan.value.isValid() && scan.state == Acceptable)
|
|
||||||
scan.state = Intermediate;
|
|
||||||
|
|
||||||
return scan;
|
return scan;
|
||||||
}
|
}
|
||||||
@ -2233,7 +2233,7 @@ bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime) const
|
|||||||
const StateNode tmp = parse(t, -1, defaultLocalTime, false);
|
const StateNode tmp = parse(t, -1, defaultLocalTime, false);
|
||||||
if (datetime)
|
if (datetime)
|
||||||
*datetime = tmp.value;
|
*datetime = tmp.value;
|
||||||
return tmp.state == Acceptable && !tmp.conflicts && tmp.value.isValid();
|
return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime QDateTimeParser::getMinimum() const
|
QDateTime QDateTimeParser::getMinimum() const
|
||||||
|
@ -274,13 +274,17 @@ MkTimeResult hopAcrossGap(const MkTimeResult &outside, const struct tm &base)
|
|||||||
|
|
||||||
Q_DECL_COLD_FUNCTION
|
Q_DECL_COLD_FUNCTION
|
||||||
MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
||||||
QDateTimePrivate::DaylightStatus dst)
|
QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
// May result from a time outside the supported range of system time_t
|
// May result from a time outside the supported range of system time_t
|
||||||
// functions, or from a gap (on a platform where mktime() rejects them).
|
// functions, or from a gap (on a platform where mktime() rejects them).
|
||||||
// QDateTime filters on times well outside the supported range, but may
|
// QDateTime filters on times well outside the supported range, but may
|
||||||
// pass values only slightly outside the range.
|
// pass values only slightly outside the range.
|
||||||
|
|
||||||
|
// The easy case - no need to find a resolution anyway:
|
||||||
|
if (!resolve.testAnyFlags(QDateTimePrivate::GapMask))
|
||||||
|
return {};
|
||||||
|
|
||||||
constexpr time_t twoDaysInSeconds = 2 * 24 * 60 * 60;
|
constexpr time_t twoDaysInSeconds = 2 * 24 * 60 * 60;
|
||||||
// Bracket base, one day each side (in case the zone skipped a whole day):
|
// Bracket base, one day each side (in case the zone skipped a whole day):
|
||||||
MkTimeResult early(adjacentDay(base, -1));
|
MkTimeResult early(adjacentDay(base, -1));
|
||||||
@ -291,32 +295,15 @@ MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
|||||||
// OK, looks like a gap.
|
// OK, looks like a gap.
|
||||||
Q_ASSERT(twoDaysInSeconds + early.utcSecs > later.utcSecs);
|
Q_ASSERT(twoDaysInSeconds + early.utcSecs > later.utcSecs);
|
||||||
result.adjusted = true;
|
result.adjusted = true;
|
||||||
// When simply constructing a gap-time, dst is unknown and construction will
|
|
||||||
// leave us with a time outside the gap, so later calls to rediscover its
|
|
||||||
// offset won't hit the gap. So if we've hit a gap and think we know dst,
|
|
||||||
// it's because addDays() or similar has moved us from the side we think
|
|
||||||
// we're on, which means we should over-shoot and get the opposite DST.
|
|
||||||
|
|
||||||
// A gap is usually followed by DST - except for "negative DST", where
|
// Extrapolate backwards from later if this option is set:
|
||||||
// early's tm_isdst is 1 and later's isn't. Default to using 24h after
|
QDateTimePrivate::TransitionOption beforeLater = QDateTimePrivate::GapUseBefore;
|
||||||
// early (which shall fall after the gap).
|
if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
|
||||||
enum { AfterEarly, BeforeLater } choice = AfterEarly;
|
// Reverse DST has DST before a gap and not after:
|
||||||
switch (dst) {
|
if (early.local.tm_isdst == 1 && !later.local.tm_isdst)
|
||||||
case QDateTimePrivate::UnknownDaylightTime:
|
beforeLater = QDateTimePrivate::GapUseAfter;
|
||||||
break;
|
|
||||||
case QDateTimePrivate::StandardTime:
|
|
||||||
// Aiming for DST, so AfterEarly is OK, unless DST is reversed:
|
|
||||||
if (early.local.tm_isdst == 1 && later.local.tm_isdst != 1)
|
|
||||||
choice = BeforeLater;
|
|
||||||
break;
|
|
||||||
case QDateTimePrivate::DaylightTime:
|
|
||||||
// Aiming for standard, so only retain AfterEarly if DST is reversed:
|
|
||||||
if (early.local.tm_isdst != 1 || later.local.tm_isdst == 1)
|
|
||||||
choice = BeforeLater;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (resolve.testFlag(beforeLater)) // Result will be before the gap:
|
||||||
if (choice == BeforeLater) // Result will be before the gap:
|
|
||||||
result.utcSecs = later.utcSecs - secondsBetween(base, later.local);
|
result.utcSecs = later.utcSecs - secondsBetween(base, later.local);
|
||||||
else // Result will be after the gap:
|
else // Result will be after the gap:
|
||||||
result.utcSecs = early.utcSecs + secondsBetween(early.local, base);
|
result.utcSecs = early.utcSecs + secondsBetween(early.local, base);
|
||||||
@ -328,7 +315,7 @@ MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Q_DECL_COLD_FUNCTION
|
Q_DECL_COLD_FUNCTION
|
||||||
bool preferAlternative(QDateTimePrivate::DaylightStatus dst,
|
bool preferAlternative(QDateTimePrivate::TransitionOptions resolve,
|
||||||
// is_dst flags of incumbent and an alternative:
|
// is_dst flags of incumbent and an alternative:
|
||||||
int gotDst, int altDst,
|
int gotDst, int altDst,
|
||||||
// True precisely if alternative selects a later UTC time:
|
// True precisely if alternative selects a later UTC time:
|
||||||
@ -336,35 +323,31 @@ bool preferAlternative(QDateTimePrivate::DaylightStatus dst,
|
|||||||
// True for a gap, false for a fold:
|
// True for a gap, false for a fold:
|
||||||
bool inGap)
|
bool inGap)
|
||||||
{
|
{
|
||||||
if (dst == QDateTimePrivate::UnknownDaylightTime)
|
// If resolve has this option set, prefer the later candidate, else the earlier:
|
||||||
return altIsLater; // Prefer later candidate
|
QDateTimePrivate::TransitionOption preferLater = inGap ? QDateTimePrivate::GapUseAfter
|
||||||
|
: QDateTimePrivate::FoldUseAfter;
|
||||||
// gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
|
if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
|
||||||
// So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
|
// gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
|
||||||
if ((gotDst ^ altDst) != 1) {
|
// So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
|
||||||
// Both or neither think they're DST - pretend one is: around a gap, the
|
if ((altDst ^ gotDst) == 1) {
|
||||||
// later candidate is DST; around a fold, the earlier.
|
// In this case, we can tell whether we have reversed DST: that's a
|
||||||
if (altIsLater == inGap) {
|
// gap with DST before it or a fold with DST after it.
|
||||||
altDst = 1;
|
#if 1
|
||||||
gotDst = 0;
|
const bool isReversed = (altDst == 1) != (altIsLater == inGap);
|
||||||
} else {
|
#else // Pedagogic version of the same thing:
|
||||||
gotDst = 1;
|
bool isReversed;
|
||||||
altDst = 0;
|
if (altIsLater == inGap) // alt is after a gap or before a fold, so summer-time
|
||||||
}
|
isReversed = altDst != 1; // flip if summer-time isn't DST
|
||||||
|
else // alt is before a gap or after a fold, so winter-time
|
||||||
|
isReversed = altDst == 1; // flip if winter-time is DST
|
||||||
|
#endif
|
||||||
|
if (isReversed) {
|
||||||
|
preferLater = inGap ? QDateTimePrivate::GapUseBefore
|
||||||
|
: QDateTimePrivate::FoldUseBefore;
|
||||||
|
}
|
||||||
|
} // Otherwise, we can't tell, so assume not.
|
||||||
}
|
}
|
||||||
// When we create a time in a gap, it comes here with UnknownDST, so has
|
return resolve.testFlag(preferLater) == altIsLater;
|
||||||
// already been handled; so a gep only gets here if we've previously
|
|
||||||
// resolved a non-gap and are now adjusting into the gap. For setTime(),
|
|
||||||
// setDate() or setTimeZone() we've no strong reason to prefer either
|
|
||||||
// resolution, but addDays(), addSecs() and friends all want to overshoot
|
|
||||||
// the gap, to the side beyond where they started; that'll typically be the
|
|
||||||
// side with the *opposite* state to the one specified.
|
|
||||||
|
|
||||||
// If we want standard, switch to the alternative iff what we have is DST
|
|
||||||
if ((dst == QDateTimePrivate::StandardTime) != inGap)
|
|
||||||
return gotDst == 1;
|
|
||||||
// Otherwise we wanted DST, so switch iff alternative is DST
|
|
||||||
return altDst == 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -372,12 +355,12 @@ bool preferAlternative(QDateTimePrivate::DaylightStatus dst,
|
|||||||
|
|
||||||
The local time is specified as a number of seconds since the epoch (so, in
|
The local time is specified as a number of seconds since the epoch (so, in
|
||||||
effect, a time_t, albeit delivered as qint64). If the specified local time
|
effect, a time_t, albeit delivered as qint64). If the specified local time
|
||||||
falls in a transition, dst determines what to do.
|
falls in a transition, resolve determines what to do.
|
||||||
|
|
||||||
If the specified local time is outside what the system time_t APIs will
|
If the specified local time is outside what the system time_t APIs will
|
||||||
handle, this fails.
|
handle, this fails.
|
||||||
*/
|
*/
|
||||||
MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst)
|
MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
const auto localDaySecs = QRoundingDown::qDivMod<SECS_PER_DAY>(local);
|
const auto localDaySecs = QRoundingDown::qDivMod<SECS_PER_DAY>(local);
|
||||||
struct tm base = timeToTm(localDaySecs.quotient, localDaySecs.remainder);
|
struct tm base = timeToTm(localDaySecs.quotient, localDaySecs.remainder);
|
||||||
@ -392,19 +375,23 @@ MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst
|
|||||||
// that we hit a gap, although we have to handle these cases differently:
|
// that we hit a gap, although we have to handle these cases differently:
|
||||||
if (!result.good) {
|
if (!result.good) {
|
||||||
// Rejected. The tricky case: maybe mktime() doesn't resolve gaps.
|
// Rejected. The tricky case: maybe mktime() doesn't resolve gaps.
|
||||||
return resolveRejected(base, result, dst);
|
return resolveRejected(base, result, resolve);
|
||||||
} else if (result.local.tm_isdst < 0) {
|
} else if (result.local.tm_isdst < 0) {
|
||||||
// Apparently success without knowledge of whether this is DST or not.
|
// Apparently success without knowledge of whether this is DST or not.
|
||||||
// Should not happen, but that means our usual understanding of what the
|
// Should not happen, but that means our usual understanding of what the
|
||||||
// system is up to has gone out the window. So just let it be.
|
// system is up to has gone out the window. So just let it be.
|
||||||
} else if (result.adjusted) {
|
} else if (result.adjusted) {
|
||||||
// Shunted out of a gap.
|
// Shunted out of a gap.
|
||||||
|
if (!resolve.testAnyFlags(QDateTimePrivate::GapMask)) {
|
||||||
|
result = {};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to obtain a matching point on the other side of the gap:
|
// Try to obtain a matching point on the other side of the gap:
|
||||||
const MkTimeResult flipped = hopAcrossGap(result, base);
|
const MkTimeResult flipped = hopAcrossGap(result, base);
|
||||||
// Even if that failed, result may be the correct resolution
|
// Even if that failed, result may be the correct resolution
|
||||||
|
|
||||||
if (preferAlternative(dst, result.local.tm_isdst, flipped.local.tm_isdst,
|
if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
|
||||||
flipped.utcSecs > result.utcSecs, true)) {
|
flipped.utcSecs > result.utcSecs, true)) {
|
||||||
// If hopAcrossGap() failed and we do need its answer, give up.
|
// If hopAcrossGap() failed and we do need its answer, give up.
|
||||||
if (!flipped.good || flipped.adjusted)
|
if (!flipped.good || flipped.adjusted)
|
||||||
@ -414,10 +401,11 @@ MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst
|
|||||||
result = flipped;
|
result = flipped;
|
||||||
result.adjusted = true;
|
result.adjusted = true;
|
||||||
}
|
}
|
||||||
} else if (dst != QDateTimePrivate::UnknownDaylightTime
|
} else if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
|
||||||
// We may not need to check whether we're in a transition:
|
// In fold, DST counts as before and standard as after -
|
||||||
// Does DST-ness match what we were asked for ?
|
// we may not need to check whether we're in a transition:
|
||||||
&& result.local.tm_isdst == (dst == QDateTimePrivate::StandardTime ? 0 : 1)) {
|
&& resolve.testFlag(result.local.tm_isdst ? QDateTimePrivate::FoldUseBefore
|
||||||
|
: QDateTimePrivate::FoldUseAfter)) {
|
||||||
// We prefer DST or standard and got what we wanted, so we're good.
|
// We prefer DST or standard and got what we wanted, so we're good.
|
||||||
// As below, but we don't need to check, because we're on the side of
|
// As below, but we don't need to check, because we're on the side of
|
||||||
// the transition that it would select as valid, if we were near one.
|
// the transition that it would select as valid, if we were near one.
|
||||||
@ -432,7 +420,13 @@ MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst
|
|||||||
const MkTimeResult flipped(copy);
|
const MkTimeResult flipped(copy);
|
||||||
if (flipped.good && !flipped.adjusted) {
|
if (flipped.good && !flipped.adjusted) {
|
||||||
// We're in a fall-back
|
// We're in a fall-back
|
||||||
if (preferAlternative(dst, result.local.tm_isdst, flipped.local.tm_isdst,
|
if (!resolve.testAnyFlags(QDateTimePrivate::FoldMask)) {
|
||||||
|
result = {};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work out which repeat to use:
|
||||||
|
if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
|
||||||
flipped.utcSecs > result.utcSecs, false)) {
|
flipped.utcSecs > result.utcSecs, false)) {
|
||||||
result = flipped;
|
result = flipped;
|
||||||
}
|
}
|
||||||
@ -563,9 +557,9 @@ QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis)
|
|||||||
return { localMillis, int(localSeconds - epochSeconds), dst };
|
return { localMillis, int(localSeconds - epochSeconds), dst };
|
||||||
}
|
}
|
||||||
|
|
||||||
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst)
|
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), dst);
|
auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), resolve);
|
||||||
if (!use.good)
|
if (!use.good)
|
||||||
return {};
|
return {};
|
||||||
#ifdef HAVE_TM_ZONE
|
#ifdef HAVE_TM_ZONE
|
||||||
@ -575,11 +569,11 @@ QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus
|
|||||||
return qTzName(use.local.tm_isdst > 0 ? 1 : 0);
|
return qTzName(use.local.tm_isdst > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst)
|
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
|
||||||
{
|
{
|
||||||
// Revised later to match what use.local tells us:
|
// Revised later to match what use.local tells us:
|
||||||
qint64 localSecs = local / MSECS_PER_SEC;
|
qint64 localSecs = local / MSECS_PER_SEC;
|
||||||
auto use = resolveLocalTime(localSecs, dst);
|
auto use = resolveLocalTime(localSecs, resolve);
|
||||||
if (!use.good)
|
if (!use.good)
|
||||||
return {local};
|
return {local};
|
||||||
|
|
||||||
@ -588,8 +582,9 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh
|
|||||||
Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
|
Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
|
||||||
: (millis >= 0 && millis < MSECS_PER_SEC));
|
: (millis >= 0 && millis < MSECS_PER_SEC));
|
||||||
|
|
||||||
// Revise our original hint-dst to what it resolved to:
|
QDateTimePrivate::DaylightStatus dst =
|
||||||
dst = use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
|
use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
|
||||||
|
|
||||||
#ifdef HAVE_TM_GMTOFF
|
#ifdef HAVE_TM_GMTOFF
|
||||||
const int offset = use.local.tm_gmtoff;
|
const int offset = use.local.tm_gmtoff;
|
||||||
localSecs = offset + use.utcSecs;
|
localSecs = offset + use.utcSecs;
|
||||||
|
@ -34,8 +34,8 @@ Q_CORE_EXPORT int getUtcOffset(qint64 atMSecsSinceEpoch);
|
|||||||
|
|
||||||
// Support for QDateTime
|
// Support for QDateTime
|
||||||
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis);
|
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis);
|
||||||
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::TransitionOptions resolve);
|
||||||
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve);
|
||||||
|
|
||||||
struct SystemMillisRange { qint64 min, max; bool minClip, maxClip; };
|
struct SystemMillisRange { qint64 min, max; bool minClip, maxClip; };
|
||||||
SystemMillisRange computeSystemMillisRange();
|
SystemMillisRange computeSystemMillisRange();
|
||||||
|
@ -7,11 +7,13 @@
|
|||||||
#include "qtimezoneprivate_p.h"
|
#include "qtimezoneprivate_p.h"
|
||||||
#include "qtimezoneprivate_data_p.h"
|
#include "qtimezoneprivate_data_p.h"
|
||||||
|
|
||||||
#include <private/qnumeric_p.h>
|
|
||||||
#include <private/qtools_p.h>
|
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
|
||||||
|
#include <private/qcalendarmath_p.h>
|
||||||
|
#include <private/qnumeric_p.h>
|
||||||
|
#include <private/qtools_p.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -170,8 +172,16 @@ QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private only method for use by QDateTime to convert local msecs to epoch msecs
|
// Private only method for use by QDateTime to convert local msecs to epoch msecs
|
||||||
QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const
|
QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
|
||||||
|
qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) const
|
||||||
{
|
{
|
||||||
|
auto dataToState = [](QTimeZonePrivate::Data d) {
|
||||||
|
return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
|
||||||
|
d.offsetFromUtc,
|
||||||
|
d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
|
||||||
|
: QDateTimePrivate::StandardTime);
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We need a UTC time at which to ask for the offset, in order to be able to
|
We need a UTC time at which to ask for the offset, in order to be able to
|
||||||
add that offset to forLocalMSecs, to get the UTC time we need.
|
add that offset to forLocalMSecs, to get the UTC time we need.
|
||||||
@ -194,11 +204,24 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
|
|||||||
? maxMSecs() : millis; // Necessarily >= forLocalMSecs
|
? maxMSecs() : millis; // Necessarily >= forLocalMSecs
|
||||||
// At most one of those was clipped to its boundary value:
|
// At most one of those was clipped to its boundary value:
|
||||||
Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
|
Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
|
||||||
|
|
||||||
|
const Data past = data(recent), future = data(imminent);
|
||||||
|
// > 99% of the time, past and future will agree:
|
||||||
|
if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
|
||||||
|
&& past.standardTimeOffset == future.standardTimeOffset
|
||||||
|
// Those two imply same daylightTimeOffset.
|
||||||
|
&& past.abbreviation == future.abbreviation)) {
|
||||||
|
Data data = future;
|
||||||
|
data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
|
||||||
|
return dataToState(data);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Offsets are Local - UTC, positive to the east of Greenwich, negative to
|
Offsets are Local - UTC, positive to the east of Greenwich, negative to
|
||||||
the west; DST offset always exceeds standard offset, when DST applies.
|
the west; DST offset normally exceeds standard offset, when DST applies.
|
||||||
When we have offsets on either side of a transition, the lower one is
|
When we have offsets on either side of a transition, the lower one is
|
||||||
standard, the higher is DST.
|
standard, the higher is DST, unless we have data telling us it's the other
|
||||||
|
way round.
|
||||||
|
|
||||||
Non-DST transitions (jurisdictions changing time-zone and time-zones
|
Non-DST transitions (jurisdictions changing time-zone and time-zones
|
||||||
changing their standard offset, typically) are described below as if they
|
changing their standard offset, typically) are described below as if they
|
||||||
@ -210,63 +233,26 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
|
|||||||
and take the easy path; with transitions, tran and nextTran get the
|
and take the easy path; with transitions, tran and nextTran get the
|
||||||
correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
|
correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
|
||||||
the right one. In all other cases, the transition changes offset and the
|
the right one. In all other cases, the transition changes offset and the
|
||||||
reasoning that applies to DST applies just the same. Aside from hinting,
|
reasoning that applies to DST applies just the same.
|
||||||
the only thing that looks at DST-ness at all, other than inferred from
|
|
||||||
offset changes, is the case without transition data handling an invalid
|
|
||||||
time in the gap that a transition passed over.
|
|
||||||
|
|
||||||
The handling of hint (see below) is apt to go wrong in non-DST
|
The resolution of transitions, specified by \a resolve, may be lead astray
|
||||||
transitions. There isn't really a great deal we can hope to do about that
|
if (as happens on Windows) the backend has been obliged to guess whether a
|
||||||
without adding yet more unreliable complexity to the heuristics in use for
|
transition is in fact a DST one or a change to standard offset; or to
|
||||||
already obscure corner-cases.
|
guess that the higher-offset side is the DST one (the reverse of this is
|
||||||
*/
|
true for Ireland, using negative DST). There's not much we can do about
|
||||||
|
that, though.
|
||||||
/*
|
|
||||||
The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller
|
|
||||||
thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so
|
|
||||||
should have been handled above; if it slips through, it's wrong but we
|
|
||||||
should probably treat it as standard anyway (never-DST means
|
|
||||||
always-standard, after all). If the hint turns out to be wrong, fall back
|
|
||||||
on trying the other possibility: which makes it harmless to treat -1
|
|
||||||
(meaning unknown) as standard (i.e. try standard first, then try DST). In
|
|
||||||
practice, away from a transition, the only difference hint makes is to
|
|
||||||
which candidate we try first: if the hint is wrong (or unknown and
|
|
||||||
standard fails), we'll try the other candidate and it'll work.
|
|
||||||
|
|
||||||
For the obscure (and invalid) case where forLocalMSecs falls in a
|
|
||||||
spring-forward's missing hour, a common case is that we started with a
|
|
||||||
date/time for which the hint was valid and adjusted it naively; for that
|
|
||||||
case, we should correct the adjustment by shunting across the transition
|
|
||||||
into where hint is wrong. So half-way through the gap, arrived at from
|
|
||||||
the DST side, should be read as an hour earlier, in standard time; but, if
|
|
||||||
arrived at from the standard side, should be read as an hour later, in
|
|
||||||
DST. (This shall be wrong in some cases; for example, when a country
|
|
||||||
changes its transition dates and changing a date/time by more than six
|
|
||||||
months lands it on a transition. However, these cases are even more
|
|
||||||
obscure than those where the heuristic is good.)
|
|
||||||
*/
|
*/
|
||||||
const Data past = data(recent), future = data(imminent);
|
|
||||||
// > 99% of the time, past and future will agree:
|
|
||||||
if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
|
|
||||||
&& past.standardTimeOffset == future.standardTimeOffset
|
|
||||||
// Those two imply same daylightTimeOffset.
|
|
||||||
&& past.abbreviation == future.abbreviation)) {
|
|
||||||
Data data = future;
|
|
||||||
data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasTransitions()) {
|
if (hasTransitions()) {
|
||||||
/*
|
/*
|
||||||
We have transitions.
|
We have transitions.
|
||||||
|
|
||||||
Each transition gives the offsets to use until the next; so we need the
|
Each transition gives the offsets to use until the next; so we need
|
||||||
most recent transition before the time forLocalMSecs describes. If it
|
the most recent transition before the time forLocalMSecs describes. If
|
||||||
describes a time *in* a transition, we'll need both that transition and
|
it describes a time *in* a transition, we'll need both that transition
|
||||||
the one before it. So find one transition that's probably after (and not
|
and the one before it. So find one transition that's probably after
|
||||||
much before, otherwise) and another that's definitely before, then work
|
(and not much before, otherwise) and another that's definitely before,
|
||||||
out which one to use. When both or neither work on forLocalMSecs, use
|
then work out which one to use. When both or neither work on
|
||||||
hint to disambiguate.
|
forLocalMSecs, use resolve to disambiguate.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get a transition definitely before the local MSecs; usually all we need.
|
// Get a transition definitely before the local MSecs; usually all we need.
|
||||||
@ -306,50 +292,75 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
|
|||||||
// If we know of no transition after it, the answer is easy:
|
// If we know of no transition after it, the answer is easy:
|
||||||
const qint64 nextStart = nextTran.atMSecsSinceEpoch;
|
const qint64 nextStart = nextTran.atMSecsSinceEpoch;
|
||||||
if (nextStart == invalidMSecs())
|
if (nextStart == invalidMSecs())
|
||||||
return tran;
|
return dataToState(tran); // Last valid transition.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
... and nextTran is either after or only slightly before. We're
|
... and nextTran is either after or only slightly before. We're
|
||||||
going to interpret one as standard time, the other as DST
|
going to interpret one as standard time, the other as DST
|
||||||
(although the transition might in fact be a change in standard
|
(although the transition might in fact be a change in standard
|
||||||
offset, or a change in DST offset, e.g. to/from double-DST). Our
|
offset, or a change in DST offset, e.g. to/from double-DST).
|
||||||
hint tells us which of those to use (defaulting to standard if no
|
|
||||||
hint): try it first; if that fails, try the other; if both fail,
|
Usually exactly one of those shall be relevant and we'll use it;
|
||||||
life's tricky.
|
but if we're close to nextTran we may be in a transition, to be
|
||||||
|
settled according to resolve's rules.
|
||||||
*/
|
*/
|
||||||
// Work out the UTC value it would make sense to return if using nextTran:
|
// Work out the UTC value it would make sense to return if using nextTran:
|
||||||
nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
|
nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
|
||||||
|
|
||||||
// If both or neither have zero DST, treat the one with lower offset as standard:
|
bool fallBack = false;
|
||||||
const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
|
if (nextStart > nextTran.atMSecsSinceEpoch) {
|
||||||
? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
|
// If both UTC values are before nextTran's offset applies, use tran:
|
||||||
// If that agrees with hint > 0, our first guess is to use nextTran; else tran.
|
if (nextStart > tran.atMSecsSinceEpoch)
|
||||||
const bool nextFirst = nextIsDst == (hint > 0);
|
return dataToState(tran);
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
/*
|
|
||||||
On the first pass, the case we consider is what hint told us to expect
|
|
||||||
(except when hint was -1 and didn't actually tell us what to expect),
|
|
||||||
so it's likely right. We only get a second pass if the first failed,
|
|
||||||
by which time the second case, that we're trying, is likely right.
|
|
||||||
*/
|
|
||||||
if (nextFirst ? i == 0 : i) {
|
|
||||||
if (nextStart <= nextTran.atMSecsSinceEpoch)
|
|
||||||
return nextTran;
|
|
||||||
} else {
|
|
||||||
// If next is invalid, nextFirst is false, to route us here first:
|
|
||||||
if (nextStart > tran.atMSecsSinceEpoch)
|
|
||||||
return tran;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
|
||||||
Neither is valid (e.g. in a spring-forward's gap) and
|
// We're in a spring-forward.
|
||||||
nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch;
|
} else if (nextStart <= tran.atMSecsSinceEpoch) {
|
||||||
swap their atMSecsSinceEpoch to give each a moment on its side of
|
// Both UTC values say we should be using nextTran:
|
||||||
the transition; and pick the reverse of what hint asked for:
|
return dataToState(nextTran);
|
||||||
*/
|
} else {
|
||||||
std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
|
Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
|
||||||
return nextFirst ? tran : nextTran;
|
fallBack = true; // We're in a fall-back.
|
||||||
|
}
|
||||||
|
// (forLocalMSecs - nextStart) / 1000 lies between the two offsets.
|
||||||
|
|
||||||
|
// Apply resolve:
|
||||||
|
// Determine whether FlipForReverseDst affects the outcome:
|
||||||
|
const bool flipped
|
||||||
|
= resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
|
||||||
|
&& (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
|
||||||
|
: tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
|
||||||
|
|
||||||
|
if (fallBack) {
|
||||||
|
if (resolve.testFlag(flipped
|
||||||
|
? QDateTimePrivate::FoldUseBefore
|
||||||
|
: QDateTimePrivate::FoldUseAfter)) {
|
||||||
|
return dataToState(nextTran);
|
||||||
|
}
|
||||||
|
if (resolve.testFlag(flipped
|
||||||
|
? QDateTimePrivate::FoldUseAfter
|
||||||
|
: QDateTimePrivate::FoldUseBefore)) {
|
||||||
|
return dataToState(tran);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Neither is valid (e.g. in a spring-forward's gap) and
|
||||||
|
nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch.
|
||||||
|
So swap their atMSecsSinceEpoch to give each a moment on the
|
||||||
|
side of the transition that it describes, then select the one
|
||||||
|
after or before according to the option set:
|
||||||
|
*/
|
||||||
|
std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
|
||||||
|
if (resolve.testFlag(flipped
|
||||||
|
? QDateTimePrivate::GapUseBefore
|
||||||
|
: QDateTimePrivate::GapUseAfter))
|
||||||
|
return dataToState(nextTran);
|
||||||
|
if (resolve.testFlag(flipped
|
||||||
|
? QDateTimePrivate::GapUseAfter
|
||||||
|
: QDateTimePrivate::GapUseBefore))
|
||||||
|
return dataToState(tran);
|
||||||
|
}
|
||||||
|
// Reject
|
||||||
|
return {forLocalMSecs};
|
||||||
}
|
}
|
||||||
// Before first transition, or system has transitions but not for this zone.
|
// Before first transition, or system has transitions but not for this zone.
|
||||||
// Try falling back to offsetFromUtc (works for before first transition, at least).
|
// Try falling back to offsetFromUtc (works for before first transition, at least).
|
||||||
@ -358,40 +369,54 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
|
|||||||
/* Bracket and refine to discover offset. */
|
/* Bracket and refine to discover offset. */
|
||||||
qint64 utcEpochMSecs;
|
qint64 utcEpochMSecs;
|
||||||
|
|
||||||
|
// We don't have true data on DST-ness, so can't apply FlipForReverseDst.
|
||||||
int early = past.offsetFromUtc;
|
int early = past.offsetFromUtc;
|
||||||
int late = future.offsetFromUtc;
|
int late = future.offsetFromUtc;
|
||||||
if (early == late || late == invalidSeconds()) {
|
if (early == late || late == invalidSeconds()) {
|
||||||
if (early == invalidSeconds()
|
if (early == invalidSeconds()
|
||||||
|| qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
|
|| qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
|
||||||
return invalidData(); // Outside representable range
|
return {forLocalMSecs}; // Outside representable range
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Close to a DST transition: early > late is near a fall-back,
|
|
||||||
// early < late is near a spring-forward.
|
|
||||||
const int offsetInDst = qMax(early, late);
|
|
||||||
const int offsetInStd = qMin(early, late);
|
|
||||||
// Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
|
// Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
|
||||||
const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
|
const qint64 forEarly = forLocalMSecs - early * 1000;
|
||||||
const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
|
const qint64 forLate = forLocalMSecs - late * 1000;
|
||||||
// Best guess at the answer:
|
// If either of those doesn't have the offset we got it from, it's on
|
||||||
const qint64 hinted = hint > 0 ? forDst : forStd;
|
// the wrong side of the transition (and both may be, for a gap):
|
||||||
if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
|
const bool earlyOk = offsetFromUtc(forEarly) == early;
|
||||||
utcEpochMSecs = hinted;
|
const bool lateOk = offsetFromUtc(forLate) == late;
|
||||||
} else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
|
|
||||||
utcEpochMSecs = forDst;
|
if (earlyOk) {
|
||||||
} else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
|
if (lateOk) {
|
||||||
utcEpochMSecs = forStd;
|
Q_ASSERT(early > late);
|
||||||
|
// fall-back's repeated interval
|
||||||
|
if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
|
||||||
|
utcEpochMSecs = forEarly;
|
||||||
|
else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
|
||||||
|
utcEpochMSecs = forLate;
|
||||||
|
else
|
||||||
|
return {forLocalMSecs};
|
||||||
|
} else {
|
||||||
|
// Before and clear of the transition:
|
||||||
|
utcEpochMSecs = forEarly;
|
||||||
|
}
|
||||||
|
} else if (lateOk) {
|
||||||
|
// After and clear of the transition:
|
||||||
|
utcEpochMSecs = forLate;
|
||||||
} else {
|
} else {
|
||||||
// Invalid forLocalMSecs: in spring-forward gap.
|
// forLate <= gap < forEarly
|
||||||
const int dstStep = (offsetInDst - offsetInStd) * 1000;
|
Q_ASSERT(late > early);
|
||||||
// That'll typically be the DST offset at imminent, but changes to
|
const int dstStep = (late - early) * 1000;
|
||||||
// standard time have zero DST offset both before and after.
|
if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
|
||||||
Q_ASSERT(dstStep > 0); // There can't be a gap without it !
|
utcEpochMSecs = forEarly - dstStep;
|
||||||
utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
|
else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
|
||||||
|
utcEpochMSecs = forLate + dstStep;
|
||||||
|
else
|
||||||
|
return {forLocalMSecs};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data(utcEpochMSecs);
|
return dataToState(data(utcEpochMSecs));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QTimeZonePrivate::hasTransitions() const
|
bool QTimeZonePrivate::hasTransitions() const
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "qlist.h"
|
#include "qlist.h"
|
||||||
#include "qtimezone.h"
|
#include "qtimezone.h"
|
||||||
#include "private/qlocale_p.h"
|
#include "private/qlocale_p.h"
|
||||||
|
#include "private/qdatetime_p.h"
|
||||||
|
|
||||||
#if QT_CONFIG(icu)
|
#if QT_CONFIG(icu)
|
||||||
#include <unicode/ucal.h>
|
#include <unicode/ucal.h>
|
||||||
@ -84,7 +85,8 @@ public:
|
|||||||
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const;
|
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const;
|
||||||
|
|
||||||
virtual Data data(qint64 forMSecsSinceEpoch) const;
|
virtual Data data(qint64 forMSecsSinceEpoch) const;
|
||||||
Data dataForLocalTime(qint64 forLocalMSecs, int hint) const;
|
QDateTimePrivate::ZoneState stateAtZoneTime(qint64 forLocalMSecs,
|
||||||
|
QDateTimePrivate::TransitionOptions resolve) const;
|
||||||
|
|
||||||
virtual bool hasTransitions() const;
|
virtual bool hasTransitions() const;
|
||||||
virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const;
|
virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const;
|
||||||
|
@ -1450,16 +1450,11 @@ void QDateTimeEdit::fixup(QString &input) const
|
|||||||
int copy = d->edit->cursorPosition();
|
int copy = d->edit->cursorPosition();
|
||||||
|
|
||||||
QDateTime value = d->validateAndInterpret(input, copy, state, true);
|
QDateTime value = d->validateAndInterpret(input, copy, state, true);
|
||||||
/*
|
// CorrectToPreviousValue correction is handled by QAbstractSpinBox.
|
||||||
String was valid, but the datetime still is not; use the time that
|
// The value might not match the input if the input represents a date-time
|
||||||
has the same distance from epoch.
|
// skipped over by its time representation, such as a spring-forward.
|
||||||
CorrectToPreviousValue correction is handled by QAbstractSpinBox.
|
if (d->correctionMode == QAbstractSpinBox::CorrectToNearestValue)
|
||||||
*/
|
|
||||||
if (!value.isValid() && d->correctionMode == QAbstractSpinBox::CorrectToNearestValue) {
|
|
||||||
value = QDateTime::fromMSecsSinceEpoch(value.toMSecsSinceEpoch(),
|
|
||||||
value.timeRepresentation());
|
|
||||||
input = textFromDateTime(value);
|
input = textFromDateTime(value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1727,11 +1722,7 @@ QDateTime QDateTimeEditPrivate::convertTimeZone(const QDateTime &datetime)
|
|||||||
|
|
||||||
QDateTime QDateTimeEditPrivate::dateTimeValue(QDate date, QTime time) const
|
QDateTime QDateTimeEditPrivate::dateTimeValue(QDate date, QTime time) const
|
||||||
{
|
{
|
||||||
QDateTime when = QDateTime(date, time, timeZone);
|
return QDateTime(date, time, timeZone);
|
||||||
if (when.isValid())
|
|
||||||
return when;
|
|
||||||
// Hit a spring-forward gap
|
|
||||||
return QDateTime::fromMSecsSinceEpoch(when.toMSecsSinceEpoch(), timeZone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QDateTimeEditPrivate::updateTimeZone()
|
void QDateTimeEditPrivate::updateTimeZone()
|
||||||
@ -2135,11 +2126,10 @@ QDateTime QDateTimeEditPrivate::stepBy(int sectionIndex, int steps, bool test) c
|
|||||||
true when date and time are valid, even if the date-time returned
|
true when date and time are valid, even if the date-time returned
|
||||||
isn't), so use the time that has the same distance from epoch.
|
isn't), so use the time that has the same distance from epoch.
|
||||||
*/
|
*/
|
||||||
if (setDigit(v, sectionIndex, val) && !v.isValid()) {
|
if (setDigit(v, sectionIndex, val) && getDigit(v, sectionIndex) != val
|
||||||
auto msecsSinceEpoch = v.toMSecsSinceEpoch();
|
&& sn.type & HourSectionMask && steps < 0) {
|
||||||
// decreasing from e.g 3am to 2am would get us back to 3am, but we want 1am
|
// decreasing from e.g 3am to 2am would get us back to 3am, but we want 1am
|
||||||
if (steps < 0 && sn.type & HourSectionMask)
|
auto msecsSinceEpoch = v.toMSecsSinceEpoch() - 3600 * 1000;
|
||||||
msecsSinceEpoch -= 3600 * 1000;
|
|
||||||
v = QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch, v.timeRepresentation());
|
v = QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch, v.timeRepresentation());
|
||||||
}
|
}
|
||||||
// if this sets year or month it will make
|
// if this sets year or month it will make
|
||||||
|
@ -627,14 +627,18 @@ void tst_QDate::startOfDay_endOfDay()
|
|||||||
QCOMPARE(front.date(), date);
|
QCOMPARE(front.date(), date);
|
||||||
UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start);
|
UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start);
|
||||||
} else UNLESSKLUDGE(IgnoreStart) {
|
} else UNLESSKLUDGE(IgnoreStart) {
|
||||||
|
auto report = qScopeGuard([front]() { qDebug() << "Start of day:" << front; });
|
||||||
QVERIFY(!front.isValid());
|
QVERIFY(!front.isValid());
|
||||||
|
report.dismiss();
|
||||||
}
|
}
|
||||||
if (end.isValid()) {
|
if (end.isValid()) {
|
||||||
QVERIFY(back.isValid());
|
QVERIFY(back.isValid());
|
||||||
QCOMPARE(back.date(), date);
|
QCOMPARE(back.date(), date);
|
||||||
UNLESSKLUDGE(IgnoreEnd) QCOMPARE(back.time(), end);
|
UNLESSKLUDGE(IgnoreEnd) QCOMPARE(back.time(), end);
|
||||||
} else UNLESSKLUDGE(IgnoreEnd) {
|
} else UNLESSKLUDGE(IgnoreEnd) {
|
||||||
|
auto report = qScopeGuard([back]() { qDebug() << "End of day:" << back; });
|
||||||
QVERIFY(!back.isValid());
|
QVERIFY(!back.isValid());
|
||||||
|
report.dismiss();
|
||||||
}
|
}
|
||||||
#undef UNLESSKLUDGE
|
#undef UNLESSKLUDGE
|
||||||
}
|
}
|
||||||
|
@ -2286,15 +2286,14 @@ void tst_QDateTime::springForward()
|
|||||||
QFETCH(int, adjust);
|
QFETCH(int, adjust);
|
||||||
|
|
||||||
QDateTime direct = QDateTime(day.addDays(-step), time, zone).addDays(step);
|
QDateTime direct = QDateTime(day.addDays(-step), time, zone).addDays(step);
|
||||||
if (direct.isValid()) { // mktime() may deem a time in the gap invalid
|
QVERIFY(direct.isValid());
|
||||||
QCOMPARE(direct.date(), day);
|
QCOMPARE(direct.date(), day);
|
||||||
QCOMPARE(direct.time().minute(), time.minute());
|
QCOMPARE(direct.time().minute(), time.minute());
|
||||||
QCOMPARE(direct.time().second(), time.second());
|
QCOMPARE(direct.time().second(), time.second());
|
||||||
const int off = step < 0 ? -1 : 1;
|
const int off = step < 0 ? -1 : 1;
|
||||||
QCOMPARE(direct.time().hour() - time.hour(), off);
|
QCOMPARE(direct.time().hour() - time.hour(), off);
|
||||||
// adjust is the offset on the other side of the gap:
|
// adjust is the offset on the other side of the gap:
|
||||||
QCOMPARE(direct.offsetFromUtc(), (adjust + off * 60) * 60);
|
QCOMPARE(direct.offsetFromUtc(), (adjust + off * 60) * 60);
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat, but getting there via .toTimeZone(). Apply adjust to datetime,
|
// Repeat, but getting there via .toTimeZone(). Apply adjust to datetime,
|
||||||
// not time, as the time wraps round if the adjustment crosses midnight.
|
// not time, as the time wraps round if the adjustment crosses midnight.
|
||||||
@ -2303,12 +2302,8 @@ void tst_QDateTime::springForward()
|
|||||||
QCOMPARE(detour.time(), time);
|
QCOMPARE(detour.time(), time);
|
||||||
detour = detour.addDays(step);
|
detour = detour.addDays(step);
|
||||||
// Insist on consistency:
|
// Insist on consistency:
|
||||||
if (direct.isValid()) {
|
QCOMPARE(detour, direct);
|
||||||
QCOMPARE(detour, direct);
|
QCOMPARE(detour.offsetFromUtc(), direct.offsetFromUtc());
|
||||||
QCOMPARE(detour.offsetFromUtc(), direct.offsetFromUtc());
|
|
||||||
} else {
|
|
||||||
QVERIFY(!detour.isValid());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QDateTime::operator_eqeq_data()
|
void tst_QDateTime::operator_eqeq_data()
|
||||||
@ -3267,11 +3262,10 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data()
|
|||||||
QTimeZone helsinki("Europe/Helsinki");
|
QTimeZone helsinki("Europe/Helsinki");
|
||||||
if (helsinki.isValid()) {
|
if (helsinki.isValid()) {
|
||||||
lacksRows = false;
|
lacksRows = false;
|
||||||
// QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert in
|
// QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert due to
|
||||||
// QTimeZonePrivate::dataForLocalTime() on macOS and iOS.
|
// the first 20m 11s of 1921-05-01 being skipped, so the parser's
|
||||||
// The first 20m 11s of 1921-05-01 were skipped, so the parser's attempt
|
// attempt to construct a local time after scanning yyMM tripped up on
|
||||||
// to construct a local time after scanning yyMM tripped up on the start
|
// the start of the day, when the zone backend lacked transition data.
|
||||||
// of the day, when the zone backend lacked transition data.
|
|
||||||
QTest::newRow("Helsinki-joins-EET")
|
QTest::newRow("Helsinki-joins-EET")
|
||||||
<< QByteArrayLiteral("Europe/Helsinki")
|
<< QByteArrayLiteral("Europe/Helsinki")
|
||||||
<< QString("210506000000Z") << QString("yyMMddHHmmsst")
|
<< QString("210506000000Z") << QString("yyMMddHHmmsst")
|
||||||
@ -3702,12 +3696,23 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
QCOMPARE(before.time(), QTime(1, 59, 59, 999));
|
QCOMPARE(before.time(), QTime(1, 59, 59, 999));
|
||||||
QCOMPARE(before.toMSecsSinceEpoch(), spring2012 - 1);
|
QCOMPARE(before.toMSecsSinceEpoch(), spring2012 - 1);
|
||||||
|
|
||||||
QDateTime missing(QDate(2012, 3, 25), QTime(2, 0));
|
QDateTime entering(QDate(2012, 3, 25), QTime(2, 0),
|
||||||
QVERIFY(!missing.isValid());
|
QDateTime::TransitionResolution::PreferBefore);
|
||||||
QCOMPARE(missing.date(), QDate(2012, 3, 25));
|
QVERIFY(entering.isValid());
|
||||||
QCOMPARE(missing.time(), QTime(3, 0));
|
QVERIFY(!entering.isDaylightTime());
|
||||||
// datetimeparser relies on toMSecsSinceEpoch to still work:
|
QCOMPARE(entering.date(), QDate(2012, 3, 25));
|
||||||
QCOMPARE(missing.toMSecsSinceEpoch(), spring2012);
|
QCOMPARE(entering.time(), QTime(1, 0));
|
||||||
|
// QDateTimeParser relies on toMSecsSinceEpoch() to still work:
|
||||||
|
QCOMPARE(entering.toMSecsSinceEpoch(), spring2012 - msecsOneHour);
|
||||||
|
|
||||||
|
QDateTime leaving(QDate(2012, 3, 25), QTime(2, 0),
|
||||||
|
QDateTime::TransitionResolution::PreferAfter);
|
||||||
|
QVERIFY(leaving.isValid());
|
||||||
|
QVERIFY(leaving.isDaylightTime());
|
||||||
|
QCOMPARE(leaving.date(), QDate(2012, 3, 25));
|
||||||
|
QCOMPARE(leaving.time(), QTime(3, 0));
|
||||||
|
// QDateTimeParser relies on toMSecsSinceEpoch to still work:
|
||||||
|
QCOMPARE(leaving.toMSecsSinceEpoch(), spring2012);
|
||||||
|
|
||||||
QDateTime after(QDate(2012, 3, 25), QTime(3, 0));
|
QDateTime after(QDate(2012, 3, 25), QTime(3, 0));
|
||||||
QVERIFY(after.isValid());
|
QVERIFY(after.isValid());
|
||||||
@ -3735,11 +3740,11 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
QVERIFY(utc.isValid());
|
QVERIFY(utc.isValid());
|
||||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||||
QCOMPARE(utc.time(), QTime(2, 0));
|
QCOMPARE(utc.time(), QTime(2, 0));
|
||||||
utc.setTimeZone(QTimeZone::LocalTime);
|
utc.setTimeZone(QTimeZone::LocalTime); // Resolved to RelativeToBefore.
|
||||||
QVERIFY(!utc.isValid());
|
QVERIFY(utc.isValid());
|
||||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||||
QCOMPARE(utc.time(), QTime(3, 0));
|
QCOMPARE(utc.time(), QTime(3, 0));
|
||||||
utc.setTimeZone(UTC);
|
utc.setTimeZone(UTC); // Preserves the changed time().
|
||||||
QVERIFY(utc.isValid());
|
QVERIFY(utc.isValid());
|
||||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||||
QCOMPARE(utc.time(), QTime(3, 0));
|
QCOMPARE(utc.time(), QTime(3, 0));
|
||||||
@ -3780,19 +3785,17 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
#undef CHECK_SPRING_FORWARD
|
#undef CHECK_SPRING_FORWARD
|
||||||
|
|
||||||
// Test for correct behviour for DaylightTime -> StandardTime transition, fall-back
|
// Test for correct behviour for DaylightTime -> StandardTime transition, fall-back
|
||||||
// TODO (QTBUG-79923): Compare to results of direct QDateTime(date, time, fold)
|
|
||||||
// construction; see Prior/Post commented-out tests.
|
|
||||||
|
|
||||||
QDateTime autumnMidnight = QDate(2012, 10, 28).startOfDay();
|
QDateTime autumnMidnight = QDate(2012, 10, 28).startOfDay();
|
||||||
QVERIFY(autumnMidnight.isValid());
|
QVERIFY(autumnMidnight.isValid());
|
||||||
// QCOMPARE(autumnMidnight, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Prior));
|
|
||||||
QCOMPARE(autumnMidnight.date(), QDate(2012, 10, 28));
|
QCOMPARE(autumnMidnight.date(), QDate(2012, 10, 28));
|
||||||
QCOMPARE(autumnMidnight.time(), QTime(0, 0));
|
QCOMPARE(autumnMidnight.time(), QTime(0, 0));
|
||||||
QCOMPARE(autumnMidnight.toMSecsSinceEpoch(), autumn2012 - 3 * msecsOneHour);
|
QCOMPARE(autumnMidnight.toMSecsSinceEpoch(), autumn2012 - 3 * msecsOneHour);
|
||||||
|
|
||||||
QDateTime startFirst = autumnMidnight.addMSecs(2 * msecsOneHour);
|
QDateTime startFirst = autumnMidnight.addMSecs(2 * msecsOneHour);
|
||||||
QVERIFY(startFirst.isValid());
|
QVERIFY(startFirst.isValid());
|
||||||
// QCOMPARE(startFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Prior));
|
QCOMPARE(startFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 0),
|
||||||
|
QDateTime::TransitionResolution::PreferBefore));
|
||||||
QCOMPARE(startFirst.date(), QDate(2012, 10, 28));
|
QCOMPARE(startFirst.date(), QDate(2012, 10, 28));
|
||||||
QCOMPARE(startFirst.time(), QTime(2, 0));
|
QCOMPARE(startFirst.time(), QTime(2, 0));
|
||||||
QCOMPARE(startFirst.toMSecsSinceEpoch(), autumn2012 - msecsOneHour);
|
QCOMPARE(startFirst.toMSecsSinceEpoch(), autumn2012 - msecsOneHour);
|
||||||
@ -3800,7 +3803,9 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
// 1 msec before transition is 2:59:59.999 FirstOccurrence
|
// 1 msec before transition is 2:59:59.999 FirstOccurrence
|
||||||
QDateTime endFirst = startFirst.addMSecs(msecsOneHour - 1);
|
QDateTime endFirst = startFirst.addMSecs(msecsOneHour - 1);
|
||||||
QVERIFY(endFirst.isValid());
|
QVERIFY(endFirst.isValid());
|
||||||
// QCOMPARE(endFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), Prior));
|
QCOMPARE(endFirst,
|
||||||
|
QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999),
|
||||||
|
QDateTime::TransitionResolution::PreferBefore));
|
||||||
QCOMPARE(endFirst.date(), QDate(2012, 10, 28));
|
QCOMPARE(endFirst.date(), QDate(2012, 10, 28));
|
||||||
QCOMPARE(endFirst.time(), QTime(2, 59, 59, 999));
|
QCOMPARE(endFirst.time(), QTime(2, 59, 59, 999));
|
||||||
QCOMPARE(endFirst.toMSecsSinceEpoch(), autumn2012 - 1);
|
QCOMPARE(endFirst.toMSecsSinceEpoch(), autumn2012 - 1);
|
||||||
@ -3808,7 +3813,8 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
// At the transition, starting the second pass
|
// At the transition, starting the second pass
|
||||||
QDateTime startRepeat = endFirst.addMSecs(1);
|
QDateTime startRepeat = endFirst.addMSecs(1);
|
||||||
QVERIFY(startRepeat.isValid());
|
QVERIFY(startRepeat.isValid());
|
||||||
// QCOMPARE(startRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Post));
|
QCOMPARE(startRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 0),
|
||||||
|
QDateTime::TransitionResolution::PreferAfter));
|
||||||
QCOMPARE(startRepeat.date(), QDate(2012, 10, 28));
|
QCOMPARE(startRepeat.date(), QDate(2012, 10, 28));
|
||||||
QCOMPARE(startRepeat.time(), QTime(2, 0));
|
QCOMPARE(startRepeat.time(), QTime(2, 0));
|
||||||
QCOMPARE(startRepeat.toMSecsSinceEpoch(), autumn2012);
|
QCOMPARE(startRepeat.toMSecsSinceEpoch(), autumn2012);
|
||||||
@ -3816,7 +3822,9 @@ void tst_QDateTime::daylightTransitions() const
|
|||||||
// 59:59.999 after transition is 2:59:59.999 SecondOccurrence
|
// 59:59.999 after transition is 2:59:59.999 SecondOccurrence
|
||||||
QDateTime endRepeat = endFirst.addMSecs(msecsOneHour);
|
QDateTime endRepeat = endFirst.addMSecs(msecsOneHour);
|
||||||
QVERIFY(endRepeat.isValid());
|
QVERIFY(endRepeat.isValid());
|
||||||
// QCOMPARE(endRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), Post));
|
QCOMPARE(endRepeat,
|
||||||
|
QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999),
|
||||||
|
QDateTime::TransitionResolution::PreferAfter));
|
||||||
QCOMPARE(endRepeat.date(), QDate(2012, 10, 28));
|
QCOMPARE(endRepeat.date(), QDate(2012, 10, 28));
|
||||||
QCOMPARE(endRepeat.time(), QTime(2, 59, 59, 999));
|
QCOMPARE(endRepeat.time(), QTime(2, 59, 59, 999));
|
||||||
QCOMPARE(endRepeat.toMSecsSinceEpoch(), autumn2012 + msecsOneHour - 1);
|
QCOMPARE(endRepeat.toMSecsSinceEpoch(), autumn2012 + msecsOneHour - 1);
|
||||||
@ -4209,20 +4217,20 @@ void tst_QDateTime::timeZones() const
|
|||||||
QCOMPARE(atGap.toMSecsSinceEpoch(), gapMSecs);
|
QCOMPARE(atGap.toMSecsSinceEpoch(), gapMSecs);
|
||||||
// - Test transition hole, setting 02:00:00 is invalid
|
// - Test transition hole, setting 02:00:00 is invalid
|
||||||
QDateTime inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 0), cet);
|
QDateTime inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 0), cet);
|
||||||
QVERIFY(!inGap.isValid());
|
QVERIFY(inGap.isValid());
|
||||||
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
||||||
QCOMPARE(inGap.time(), QTime(3, 0));
|
QCOMPARE(inGap.time(), QTime(3, 0));
|
||||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||||
// - Test transition hole, setting 02:59:59.999 is invalid
|
// - Test transition hole, 02:59:59.999 was skipped:
|
||||||
inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 59, 59, 999), cet);
|
inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 59, 59, 999), cet);
|
||||||
QVERIFY(!inGap.isValid());
|
QVERIFY(inGap.isValid());
|
||||||
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
||||||
QCOMPARE(inGap.time(), QTime(3, 59, 59, 999));
|
QCOMPARE(inGap.time(), QTime(3, 59, 59, 999));
|
||||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||||
// Test similar for local time, if it's CET:
|
// Test similar for local time, if it's CET:
|
||||||
if (zoneIsCET) {
|
if (zoneIsCET) {
|
||||||
inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 30));
|
inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 30));
|
||||||
QVERIFY(!inGap.isValid());
|
QVERIFY(inGap.isValid());
|
||||||
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
||||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||||
QCOMPARE(inGap.time(), QTime(3, 30));
|
QCOMPARE(inGap.time(), QTime(3, 30));
|
||||||
@ -4238,7 +4246,7 @@ void tst_QDateTime::timeZones() const
|
|||||||
if (QDateTime(QDate(longYear, 3, 24), QTime(12, 0), cet).msecsTo(
|
if (QDateTime(QDate(longYear, 3, 24), QTime(12, 0), cet).msecsTo(
|
||||||
QDateTime(QDate(longYear, 3, 31), QTime(12, 0), cet)) < millisInWeek) {
|
QDateTime(QDate(longYear, 3, 31), QTime(12, 0), cet)) < millisInWeek) {
|
||||||
inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30), cet);
|
inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30), cet);
|
||||||
QVERIFY(!inGap.isValid());
|
QVERIFY(inGap.isValid());
|
||||||
QCOMPARE(inGap.date(), QDate(longYear, 3, 27));
|
QCOMPARE(inGap.date(), QDate(longYear, 3, 27));
|
||||||
QCOMPARE(inGap.time(), QTime(3, 30));
|
QCOMPARE(inGap.time(), QTime(3, 30));
|
||||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||||
@ -4248,7 +4256,7 @@ void tst_QDateTime::timeZones() const
|
|||||||
if (zoneIsCET && QDateTime(QDate(longYear, 3, 24), QTime(12, 0)).msecsTo(
|
if (zoneIsCET && QDateTime(QDate(longYear, 3, 24), QTime(12, 0)).msecsTo(
|
||||||
QDateTime(QDate(longYear, 3, 31), QTime(12, 0))) < millisInWeek) {
|
QDateTime(QDate(longYear, 3, 31), QTime(12, 0))) < millisInWeek) {
|
||||||
inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30));
|
inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30));
|
||||||
QVERIFY(!inGap.isValid());
|
QVERIFY(inGap.isValid());
|
||||||
QCOMPARE(inGap.date(), QDate(longYear, 3, 27));
|
QCOMPARE(inGap.date(), QDate(longYear, 3, 27));
|
||||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||||
QCOMPARE(inGap.time(), QTime(3, 30));
|
QCOMPARE(inGap.time(), QTime(3, 30));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user