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)
|
||||
|
||||
#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)
|
||||
|
||||
#include "qjniobject.h"
|
||||
|
@ -853,7 +853,10 @@ static bool inDateTimeRange(qint64 jd, DaySide side)
|
||||
static QDateTime toEarliest(QDate day, const QTimeZone &zone)
|
||||
{
|
||||
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:
|
||||
QDateTime when = moment(QTime(2, 0));
|
||||
if (!when.isValid()) {
|
||||
@ -871,7 +874,8 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone)
|
||||
// Binary chop to the right minute
|
||||
while (high > low + 1) {
|
||||
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) {
|
||||
high = mid;
|
||||
when = probe;
|
||||
@ -933,24 +937,26 @@ QDateTime QDate::startOfDay(const QTimeZone &zone) const
|
||||
if (!inDateTimeRange(jd, DaySide::Start) || !zone.isValid())
|
||||
return QDateTime();
|
||||
|
||||
QDateTime when(*this, QTime(0, 0), zone);
|
||||
if (Q_LIKELY(when.isValid()))
|
||||
return when;
|
||||
|
||||
QDateTime when(*this, QTime(0, 0), zone,
|
||||
QDateTime::TransitionResolution::RelativeToBefore);
|
||||
if (Q_UNLIKELY(!when.isValid() || when.date() != *this)) {
|
||||
#if QT_CONFIG(timezone)
|
||||
// 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()) {
|
||||
QTimeZone::OffsetData tran
|
||||
// There's unlikely to be another transition before noon tomorrow.
|
||||
// However, the whole of today may have been skipped !
|
||||
= zone.previousTransition(QDateTime(addDays(1), QTime(12, 0), zone));
|
||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||
if (at.isValid() && at.date() == *this)
|
||||
return at;
|
||||
}
|
||||
// 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()) {
|
||||
QTimeZone::OffsetData tran
|
||||
// There's unlikely to be another transition before noon tomorrow.
|
||||
// However, the whole of today may have been skipped !
|
||||
= zone.previousTransition(QDateTime(addDays(1), QTime(12, 0), zone));
|
||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||
if (at.isValid() && at.date() == *this)
|
||||
return at;
|
||||
}
|
||||
#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)
|
||||
{
|
||||
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:
|
||||
QDateTime when = moment(QTime(21, 59, 59, 999));
|
||||
if (!when.isValid()) {
|
||||
@ -1020,7 +1029,8 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone)
|
||||
// Binary chop to the right minute
|
||||
while (high > low + 1) {
|
||||
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) {
|
||||
low = mid;
|
||||
when = probe;
|
||||
@ -1083,24 +1093,25 @@ QDateTime QDate::endOfDay(const QTimeZone &zone) const
|
||||
if (!inDateTimeRange(jd, DaySide::End) || !zone.isValid())
|
||||
return QDateTime();
|
||||
|
||||
QDateTime when(*this, QTime(23, 59, 59, 999), zone);
|
||||
if (Q_LIKELY(when.isValid()))
|
||||
return when;
|
||||
|
||||
QDateTime when(*this, QTime(23, 59, 59, 999), zone,
|
||||
QDateTime::TransitionResolution::RelativeToAfter);
|
||||
if (Q_UNLIKELY(!when.isValid() || when.date() != *this)) {
|
||||
#if QT_CONFIG(timezone)
|
||||
// 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()) {
|
||||
QTimeZone::OffsetData tran
|
||||
// It's unlikely there's been another transition since yesterday noon.
|
||||
// However, the whole of today may have been skipped !
|
||||
= zone.nextTransition(QDateTime(addDays(-1), QTime(12, 0), zone));
|
||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||
if (at.isValid() && at.date() == *this)
|
||||
return at;
|
||||
}
|
||||
// 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()) {
|
||||
QTimeZone::OffsetData tran
|
||||
// It's unlikely there's been another transition since yesterday noon.
|
||||
// However, the whole of today may have been skipped !
|
||||
= zone.nextTransition(QDateTime(addDays(-1), QTime(12, 0), zone));
|
||||
const QDateTime &at = tran.atUtc.toTimeZone(zone);
|
||||
if (at.isValid() && at.date() == *this)
|
||||
return at;
|
||||
}
|
||||
#endif
|
||||
|
||||
return toLatest(*this, zone);
|
||||
when = toLatest(*this, zone);
|
||||
}
|
||||
return when;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -2728,11 +2739,99 @@ static auto millisToWithinRange(qint64 millis)
|
||||
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)
|
||||
{
|
||||
const QDateTimePrivate::TransitionOptions resolve = toTransitionOptions(dst);
|
||||
QString abbreviation;
|
||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||
abbreviation = QLocalTime::localTimeAbbbreviationAt(millis, dst);
|
||||
abbreviation = QLocalTime::localTimeAbbbreviationAt(millis, resolve);
|
||||
if (!abbreviation.isEmpty())
|
||||
return abbreviation;
|
||||
}
|
||||
@ -2742,7 +2841,7 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
||||
// Use the system zone:
|
||||
const auto sys = QTimeZone::systemTimeZone();
|
||||
if (sys.isValid()) {
|
||||
ZoneState state = zoneStateAtMillis(sys, millis, dst);
|
||||
ZoneState state = zoneStateAtMillis(sys, millis, resolve);
|
||||
if (state.valid)
|
||||
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:
|
||||
auto fake = millisToWithinRange(millis);
|
||||
if (Q_LIKELY(fake.good))
|
||||
return QLocalTime::localTimeAbbbreviationAt(fake.shifted, dst);
|
||||
return QLocalTime::localTimeAbbbreviationAt(fake.shifted, resolve);
|
||||
|
||||
// Overflow, apparently.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 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
|
||||
// case it does fall in the range and gets useful information:
|
||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||
auto result = QLocalTime::mapLocalTime(millis, dst);
|
||||
auto result = QLocalTime::mapLocalTime(millis, resolve);
|
||||
if (result.valid)
|
||||
return result;
|
||||
}
|
||||
@ -2774,14 +2874,14 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
|
||||
// Use the system zone:
|
||||
const auto sys = QTimeZone::systemTimeZone();
|
||||
if (sys.isValid())
|
||||
return zoneStateAtMillis(sys, millis, dst);
|
||||
return zoneStateAtMillis(sys, millis, resolve);
|
||||
#endif // timezone
|
||||
|
||||
// Kludge
|
||||
// Use a time in the system range with the same day-of-week pattern to its year:
|
||||
auto fake = millisToWithinRange(millis);
|
||||
if (Q_LIKELY(fake.good)) {
|
||||
auto result = QLocalTime::mapLocalTime(fake.shifted, dst);
|
||||
auto result = QLocalTime::mapLocalTime(fake.shifted, resolve);
|
||||
if (result.valid) {
|
||||
qint64 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)
|
||||
// For a TimeZone and a time expressed in zone msecs encoding, possibly with a
|
||||
// hint to DST-ness, compute the actual DST-ness and offset, adjusting the time
|
||||
// if needed to escape a spring-forward.
|
||||
QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(const QTimeZone &zone,
|
||||
qint64 millis, DaylightStatus dst)
|
||||
// For a TimeZone and a time expressed in zone msecs encoding, compute the
|
||||
// actual DST-ness and offset, adjusting the time if needed to escape a
|
||||
// spring-forward.
|
||||
QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(
|
||||
const QTimeZone &zone, qint64 millis, QDateTimePrivate::TransitionOptions resolve)
|
||||
{
|
||||
Q_ASSERT(zone.isValid());
|
||||
Q_ASSERT(zone.timeSpec() == Qt::TimeZone);
|
||||
// Get the effective data from QTimeZone
|
||||
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);
|
||||
return zone.d->stateAtZoneTime(millis, resolve);
|
||||
}
|
||||
#endif // timezone
|
||||
|
||||
static inline QDateTimePrivate::ZoneState stateAtMillis(const QTimeZone &zone, qint64 millis,
|
||||
QDateTimePrivate::DaylightStatus dst)
|
||||
QDateTimePrivate::TransitionOptions resolve)
|
||||
{
|
||||
if (zone.timeSpec() == Qt::LocalTime)
|
||||
return QDateTimePrivate::localStateAtMillis(millis, dst);
|
||||
return QDateTimePrivate::localStateAtMillis(millis, resolve);
|
||||
#if QT_CONFIG(timezone)
|
||||
if (zone.timeSpec() == Qt::TimeZone && zone.isValid())
|
||||
return QDateTimePrivate::zoneStateAtMillis(zone, millis, dst);
|
||||
return QDateTimePrivate::zoneStateAtMillis(zone, millis, resolve);
|
||||
#endif
|
||||
return {millis};
|
||||
}
|
||||
@ -2938,7 +3031,8 @@ static inline bool usesSameOffset(const QDateTimeData &a, const QDateTimeData &b
|
||||
}
|
||||
|
||||
// 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);
|
||||
auto status = getStatus(d);
|
||||
@ -2958,27 +3052,19 @@ static void refreshZonedDateTime(QDateTimeData &d, const QTimeZone &zone)
|
||||
if (!status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime)) {
|
||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||
} else {
|
||||
// We have a valid date and time and a Qt::LocalTime or Qt::TimeZone that needs calculating
|
||||
// LocalTime and TimeZone might fall into a "missing" DST transition hour
|
||||
// Calling toEpochMSecs will adjust the returned date/time if it does
|
||||
// We have a valid date and time and a Qt::LocalTime or Qt::TimeZone
|
||||
// that might fall into a "missing" DST transition hour.
|
||||
qint64 msecs = getMSecs(d);
|
||||
QDateTimePrivate::ZoneState state = stateAtMillis(zone, msecs,
|
||||
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;
|
||||
QDateTimePrivate::ZoneState state = stateAtMillis(zone, msecs, resolve);
|
||||
Q_ASSERT(!state.valid || (state.offset >= -SECS_PER_DAY && state.offset <= SECS_PER_DAY));
|
||||
if (Q_LIKELY(state.valid && msecs == state.when)) {
|
||||
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
||||
} else { // msecs changed: gap, or failed to convert (e.g. overflow)
|
||||
if (state.dst == QDateTimePrivate::UnknownDaylightTime) { // Overflow
|
||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||
if (state.valid) { // gap
|
||||
/* Make sure our offset and msecs do produce the selected UTC
|
||||
secs, if queried. When d isn't short, we record offset, so
|
||||
need msecs to match; when d is short, consistency demands we
|
||||
also update msecs, which will at least mean we don't hit the
|
||||
gap again, if we ever recompute offset. */
|
||||
} else if (state.valid) {
|
||||
status = mergeDaylightStatus(status, state.dst);
|
||||
offsetFromUtc = state.offset;
|
||||
status.setFlag(QDateTimePrivate::ValidDateTime, true);
|
||||
if (Q_UNLIKELY(msecs != state.when)) {
|
||||
// Update msecs to the resolution:
|
||||
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
||||
if (msecsCanBeSmall(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))
|
||||
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:
|
||||
static void checkValidDateTime(QDateTimeData &d)
|
||||
static void checkValidDateTime(QDateTimeData &d, QDateTime::TransitionResolution resolve)
|
||||
{
|
||||
auto spec = extractSpec(getStatus(d));
|
||||
switch (spec) {
|
||||
@ -3030,12 +3118,13 @@ static void checkValidDateTime(QDateTimeData &d)
|
||||
case Qt::LocalTime:
|
||||
// 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.
|
||||
refreshZonedDateTime(d, d.timeZone());
|
||||
refreshZonedDateTime(d, d.timeZone(), toTransitionOptions(resolve));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void reviseTimeZone(QDateTimeData &d, QTimeZone zone)
|
||||
static void reviseTimeZone(QDateTimeData &d, QTimeZone zone,
|
||||
QDateTime::TransitionResolution resolve)
|
||||
{
|
||||
Qt::TimeSpec spec = zone.timeSpec();
|
||||
auto status = mergeSpec(getStatus(d), spec);
|
||||
@ -3074,7 +3163,7 @@ static void reviseTimeZone(QDateTimeData &d, QTimeZone zone)
|
||||
if (QTimeZone::isUtcOrFixedOffset(spec))
|
||||
refreshSimpleDateTime(d);
|
||||
else
|
||||
refreshZonedDateTime(d, zone);
|
||||
refreshZonedDateTime(d, zone, toTransitionOptions(resolve));
|
||||
}
|
||||
|
||||
static void setDateTime(QDateTimeData &d, QDate date, QTime time)
|
||||
@ -3319,14 +3408,15 @@ inline QDateTimePrivate *QDateTime::Data::operator->()
|
||||
*****************************************************************************/
|
||||
|
||||
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);
|
||||
setDateTime(result, toDate, toTime);
|
||||
if (zone.isUtcOrFixedOffset())
|
||||
refreshSimpleDateTime(result);
|
||||
else
|
||||
refreshZonedDateTime(result, zone);
|
||||
refreshZonedDateTime(result, zone, toTransitionOptions(resolve));
|
||||
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
|
||||
standard notation as "UTC+01:00"), with no daylight-saving
|
||||
complications. When using either local time or a specified time zone,
|
||||
time-zone transitions (see \l {Daylight-Saving Time (DST)}{below}) are taken
|
||||
into account. A QDateTime's timeSpec() will tell you which of the four types
|
||||
of time representation is in use; its timeRepresentation() provides a full
|
||||
representation of that time representation, as a QTimeZone.
|
||||
time-zone transitions (see \l {Timezone transitions}{below}) are taken into
|
||||
account. A QDateTime's timeSpec() will tell you which of the four types of
|
||||
time representation is in use; its timeRepresentation() provides a full
|
||||
description of that time representation, as a QTimeZone.
|
||||
|
||||
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
|
||||
currentDateTime() or fromMSecsSinceEpoch(). The date and time can be changed
|
||||
with setDate() and setTime(). A datetime can also be set using the
|
||||
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
|
||||
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
|
||||
that instead.
|
||||
|
||||
\note QDateTime does not account for leap seconds.
|
||||
|
||||
\section1 Remarks
|
||||
|
||||
\note QDateTime does not account for leap seconds.
|
||||
|
||||
\note All conversion to and from string formats is done using the C locale.
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
Unix.
|
||||
|
||||
\section2 Daylight-Saving Time (DST)
|
||||
\section2 Timezone transitions
|
||||
|
||||
QDateTime takes into account transitions between Standard Time and
|
||||
Daylight-Saving Time. For example, if the transition is at 2am and the clock
|
||||
goes forward to 3am, then there is a "missing" hour from 02:00:00 to
|
||||
02:59:59.999 which QDateTime considers to be invalid. Any date arithmetic
|
||||
performed will take this missing hour into account and return a valid
|
||||
result. For example, adding one second to 01:59:59 will get 03:00:00.
|
||||
QDateTime takes into account timezone transitions, both the transitions
|
||||
between Standard Time and Daylight-Saving Time (DST) and the transitions
|
||||
that arise when a zone changes its standard offset. For example, if the
|
||||
transition is at 2am and the clock goes forward to 3am, then there is a
|
||||
"missing" hour from 02:00:00 to 02:59:59.999. Such a transition is known as
|
||||
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
|
||||
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
|
||||
determine local time's offset from UTC. For datetimes not handled by these
|
||||
system APIs, QTimeZone::systemTimeZone() is used. In either case, the offset
|
||||
information used depends on the system and may be incomplete or, for past
|
||||
times, historically inaccurate. In any case, for future dates, the local
|
||||
time zone's offsets and DST rules may change before that date comes around.
|
||||
system APIs (potentially including some within the \c time_t range),
|
||||
QTimeZone::systemTimeZone() is used, if available, or a best effort is made
|
||||
to estimate. In any case, the offset information used depends on the system
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
*/
|
||||
@ -3496,6 +3637,148 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
||||
\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.
|
||||
|
||||
@ -3534,7 +3817,8 @@ QDateTime::QDateTime() noexcept
|
||||
skipped over the given date and time, the result is invalid.
|
||||
*/
|
||||
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
|
||||
@ -3546,24 +3830,36 @@ QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSecond
|
||||
representation described by \a timeZone.
|
||||
|
||||
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)
|
||||
: d(QDateTimePrivate::create(date, time, timeZone))
|
||||
QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone, TransitionResolution resolve)
|
||||
: d(QDateTimePrivate::create(date, time, timeZone, resolve))
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.5
|
||||
\overload
|
||||
|
||||
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)
|
||||
: d(QDateTimePrivate::create(date, time, QTimeZone::LocalTime))
|
||||
QDateTime::QDateTime(QDate date, QTime time, TransitionResolution resolve)
|
||||
: d(QDateTimePrivate::create(date, time, QTimeZone::LocalTime, resolve))
|
||||
{
|
||||
}
|
||||
|
||||
@ -3754,8 +4050,8 @@ int QDateTime::offsetFromUtc() const
|
||||
auto spec = extractSpec(status);
|
||||
if (spec == Qt::LocalTime) {
|
||||
// We didn't cache the value, so we need to calculate it:
|
||||
auto dst = extractDaylightStatus(status);
|
||||
return QDateTimePrivate::localStateAtMillis(getMSecs(d), dst).offset;
|
||||
const auto resolve = toTransitionOptions(extractDaylightStatus(status));
|
||||
return QDateTimePrivate::localStateAtMillis(getMSecs(d), resolve).offset;
|
||||
}
|
||||
|
||||
Q_ASSERT(spec == Qt::UTC);
|
||||
@ -3840,8 +4136,10 @@ bool QDateTime::isDaylightTime() const
|
||||
#endif // timezone
|
||||
case Qt::LocalTime: {
|
||||
auto dst = extractDaylightStatus(getStatus(d));
|
||||
if (dst == QDateTimePrivate::UnknownDaylightTime)
|
||||
dst = QDateTimePrivate::localStateAtMillis(getMSecs(d), dst).dst;
|
||||
if (dst == QDateTimePrivate::UnknownDaylightTime) {
|
||||
dst = QDateTimePrivate::localStateAtMillis(
|
||||
getMSecs(d), toTransitionOptions(TransitionResolution::LegacyBehavior)).dst;
|
||||
}
|
||||
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
|
||||
is set to midnight. If \a date is invalid, this QDateTime becomes invalid.
|
||||
Sets the date part of this datetime to \a date.
|
||||
|
||||
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()
|
||||
*/
|
||||
|
||||
void QDateTime::setDate(QDate date)
|
||||
void QDateTime::setDate(QDate date, TransitionResolution resolve)
|
||||
{
|
||||
setDateTime(d, date, time());
|
||||
checkValidDateTime(d);
|
||||
checkValidDateTime(d, resolve);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -3871,13 +4177,19 @@ void QDateTime::setDate(QDate date)
|
||||
dt.setTime(QTime());
|
||||
\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()
|
||||
*/
|
||||
|
||||
void QDateTime::setTime(QTime time)
|
||||
void QDateTime::setTime(QTime time, TransitionResolution resolve)
|
||||
{
|
||||
setDateTime(d, date(), time);
|
||||
checkValidDateTime(d);
|
||||
checkValidDateTime(d, resolve);
|
||||
}
|
||||
|
||||
#if QT_DEPRECATED_SINCE(6, 9)
|
||||
@ -3901,7 +4213,8 @@ void QDateTime::setTime(QTime time)
|
||||
|
||||
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)
|
||||
{
|
||||
reviseTimeZone(d, QTimeZone::fromSecondsAheadOfUtc(offsetSeconds));
|
||||
reviseTimeZone(d, QTimeZone::fromSecondsAheadOfUtc(offsetSeconds),
|
||||
TransitionResolution::Reject);
|
||||
}
|
||||
#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
|
||||
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
|
||||
*/
|
||||
|
||||
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:
|
||||
if (status.testFlag(QDateTimePrivate::ShortData)) {
|
||||
// Short form has nowhere to cache the offset, so recompute.
|
||||
auto dst = extractDaylightStatus(status);
|
||||
auto state = QDateTimePrivate::localStateAtMillis(getMSecs(d), dst);
|
||||
const auto resolve = toTransitionOptions(extractDaylightStatus(getStatus(d)));
|
||||
const auto state = QDateTimePrivate::localStateAtMillis(getMSecs(d), resolve);
|
||||
return state.when - state.offset * MSECS_PER_SEC;
|
||||
}
|
||||
// Use the offset saved by refreshZonedDateTime() on creation.
|
||||
@ -4248,19 +4567,11 @@ QString QDateTime::toString(QStringView format, QCalendar cal) const
|
||||
}
|
||||
#endif // datestring
|
||||
|
||||
static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime time)
|
||||
static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime time, bool forward)
|
||||
{
|
||||
/*
|
||||
If we have just adjusted to a day with a DST transition, our given time
|
||||
may lie in the transition hour (either missing or duplicated). For any
|
||||
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).
|
||||
*/
|
||||
const QDateTimePrivate::TransitionOptions resolve = toTransitionOptions(
|
||||
forward ? QDateTime::TransitionResolution::RelativeToBefore
|
||||
: QDateTime::TransitionResolution::RelativeToAfter);
|
||||
auto status = getStatus(d);
|
||||
Q_ASSERT(status.testFlags(QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime
|
||||
| QDateTimePrivate::ValidDateTime));
|
||||
@ -4270,13 +4581,13 @@ static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime t
|
||||
refreshSimpleDateTime(d);
|
||||
return;
|
||||
}
|
||||
auto dst = extractDaylightStatus(status);
|
||||
qint64 local = timeToMSecs(date, time);
|
||||
const QDateTimePrivate::ZoneState state = stateAtMillis(d.timeZone(), local, dst);
|
||||
if (state.valid)
|
||||
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
||||
else
|
||||
const QDateTimePrivate::ZoneState state = stateAtMillis(d.timeZone(), local, resolve);
|
||||
Q_ASSERT(state.valid || state.dst == QDateTimePrivate::UnknownDaylightTime);
|
||||
if (state.dst == QDateTimePrivate::UnknownDaylightTime)
|
||||
status.setFlag(QDateTimePrivate::ValidDateTime, false);
|
||||
else
|
||||
status = mergeDaylightStatus(status | QDateTimePrivate::ValidDateTime, state.dst);
|
||||
|
||||
if (status & QDateTimePrivate::ShortData) {
|
||||
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
|
||||
< 0}) or after 3am (otherwise).
|
||||
|
||||
\sa daysTo(), addMonths(), addYears(), addSecs()
|
||||
\sa daysTo(), addMonths(), addYears(), addSecs(), {Timezone transitions}
|
||||
*/
|
||||
|
||||
QDateTime QDateTime::addDays(qint64 ndays) const
|
||||
@ -4313,7 +4624,7 @@ QDateTime QDateTime::addDays(qint64 ndays) const
|
||||
|
||||
QDateTime dt(*this);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -4329,7 +4640,7 @@ QDateTime QDateTime::addDays(qint64 ndays) const
|
||||
aiming between 2am and 3am will be adjusted to fall before 2am (if
|
||||
\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
|
||||
@ -4339,7 +4650,7 @@ QDateTime QDateTime::addMonths(int nmonths) const
|
||||
|
||||
QDateTime dt(*this);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
< 0}) or after 3am (otherwise).
|
||||
|
||||
\sa daysTo(), addDays(), addMonths(), addSecs()
|
||||
\sa daysTo(), addDays(), addMonths(), addSecs(), {Timezone transitions}
|
||||
*/
|
||||
|
||||
QDateTime QDateTime::addYears(int nyears) const
|
||||
@ -4365,7 +4676,7 @@ QDateTime QDateTime::addYears(int nyears) const
|
||||
|
||||
QDateTime dt(*this);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -5109,7 +5420,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs
|
||||
QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
|
||||
{
|
||||
QDateTime dt;
|
||||
reviseTimeZone(dt.d, timeZone);
|
||||
reviseTimeZone(dt.d, timeZone, TransitionResolution::Reject);
|
||||
if (timeZone.isValid())
|
||||
dt.setMSecsSinceEpoch(msecs);
|
||||
return dt;
|
||||
@ -5141,7 +5452,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs)
|
||||
QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
|
||||
{
|
||||
QDateTime dt;
|
||||
reviseTimeZone(dt.d, timeZone);
|
||||
reviseTimeZone(dt.d, timeZone, TransitionResolution::Reject);
|
||||
if (timeZone.isValid())
|
||||
dt.setSecsSinceEpoch(secs);
|
||||
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
|
||||
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,
|
||||
whose toMSecsSinceEpoch() represents a near-by datetime that is
|
||||
valid. Passing that to fromMSecsSinceEpoch() will produce a valid datetime
|
||||
that isn't faithfully represented by the string parsed.
|
||||
gap skipped by a time-zone transition), an valid QDateTime is returned, that
|
||||
represents a near-by datetime that is valid.
|
||||
|
||||
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
|
||||
@ -5609,6 +5918,7 @@ QDataStream &operator>>(QDataStream &in, QDateTime &dateTime)
|
||||
in >> zone;
|
||||
break;
|
||||
}
|
||||
// Note: no way to resolve transition ambiguity, when relevant; use default.
|
||||
dateTime = QDateTime(dt, tm, zone);
|
||||
|
||||
} else if (in.version() == QDataStream::Qt_5_0) {
|
||||
|
@ -311,12 +311,31 @@ class Q_CORE_EXPORT QDateTime
|
||||
|
||||
public:
|
||||
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)
|
||||
QT_DEPRECATED_VERSION_X_6_9("Pass QTimeZone instead")
|
||||
QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSeconds = 0);
|
||||
#endif
|
||||
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||
QDateTime(QDate date, QTime time, const QTimeZone &timeZone);
|
||||
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(QDateTime &&other) noexcept;
|
||||
~QDateTime();
|
||||
@ -343,15 +362,24 @@ public:
|
||||
qint64 toMSecsSinceEpoch() const;
|
||||
qint64 toSecsSinceEpoch() const;
|
||||
|
||||
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||
void setDate(QDate date);
|
||||
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)
|
||||
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
||||
void setTimeSpec(Qt::TimeSpec spec);
|
||||
QT_DEPRECATED_VERSION_X_6_9("Use setTimeZone() instead")
|
||||
void setOffsetFromUtc(int offsetSeconds);
|
||||
#endif
|
||||
#if QT_CORE_REMOVED_SINCE(6, 7)
|
||||
void setTimeZone(const QTimeZone &toZone);
|
||||
#endif
|
||||
void setTimeZone(const QTimeZone &toZone,
|
||||
TransitionResolution resolve = TransitionResolution::LegacyBehavior);
|
||||
void setMSecsSinceEpoch(qint64 msecs);
|
||||
void setSecsSinceEpoch(qint64 secs);
|
||||
|
||||
|
@ -73,6 +73,22 @@ public:
|
||||
};
|
||||
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 {
|
||||
TimeSpecShift = 4,
|
||||
};
|
||||
@ -89,14 +105,16 @@ public:
|
||||
: 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)
|
||||
static ZoneState zoneStateAtMillis(const QTimeZone &zone, qint64 millis, DaylightStatus dst);
|
||||
static ZoneState zoneStateAtMillis(const QTimeZone &zone, qint64 millis,
|
||||
TransitionOptions resolve);
|
||||
#endif // timezone
|
||||
|
||||
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
|
||||
|
||||
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::TransitionOptions)
|
||||
|
||||
namespace QtPrivate {
|
||||
namespace DateTimeConstants {
|
||||
|
@ -798,11 +798,6 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
||||
&& m_text.at(offset) == u'-');
|
||||
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 { 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);
|
||||
break; }
|
||||
case TimeZoneSection:
|
||||
result = findTimeZone(sectionTextRef, defaultValue,
|
||||
result = findTimeZone(sectionTextRef, currentValue,
|
||||
absoluteMax(sectionIndex),
|
||||
absoluteMin(sectionIndex), sn.count);
|
||||
break;
|
||||
@ -850,7 +845,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
||||
int num = 0, used = 0;
|
||||
if (sn.type == MonthSection) {
|
||||
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;
|
||||
num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
|
||||
} else {
|
||||
@ -955,7 +950,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
||||
}
|
||||
|
||||
} else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
|
||||
if (skipToNextSection(sectionIndex, defaultValue, digitsStr)) {
|
||||
if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
|
||||
const int missingZeroes = sectionmaxsize - digitsStr.size();
|
||||
result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
|
||||
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 QDateTime when = QDateTime(date, time, timeZone);
|
||||
|
||||
// If hour wasn't specified, check the default we're using exists on the
|
||||
// given date (which might be a spring-forward, skipping an hour).
|
||||
if (!(isSet & HourSectionMask) && !when.isValid()) {
|
||||
switch (parserType) {
|
||||
case QMetaType::QDateTime: {
|
||||
qint64 msecs = when.toMSecsSinceEpoch();
|
||||
// Fortunately, that gets a useful answer, even though when is invalid ...
|
||||
const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
|
||||
const QTime tick = replace.time();
|
||||
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);
|
||||
if (when.time() != time || when.date() != date) {
|
||||
// In a spring-forward, if we hit the skipped hour, we may have been
|
||||
// shunted out of it.
|
||||
|
||||
// If hour wasn't specified, so we're using our default, changing it may
|
||||
// fix that.
|
||||
if (!(isSet & HourSectionMask)) {
|
||||
switch (parserType) {
|
||||
case QMetaType::QDateTime: {
|
||||
qint64 msecs = when.toMSecsSinceEpoch();
|
||||
// Fortunately, that gets a useful answer, even though when is invalid ...
|
||||
const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
|
||||
const QTime tick = replace.time();
|
||||
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;
|
||||
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());
|
||||
} else if (state > Intermediate) {
|
||||
state = Intermediate;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1607,12 +1611,8 @@ QDateTimeParser::parse(const QString &input, int position,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
We might have ended up with an invalid datetime: the non-existent hour
|
||||
during dst changes, for instance.
|
||||
*/
|
||||
if (!scan.value.isValid() && scan.state == Acceptable)
|
||||
scan.state = Intermediate;
|
||||
// An invalid time should only arise if we set the state to less than acceptable:
|
||||
Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
|
||||
|
||||
return scan;
|
||||
}
|
||||
@ -2233,7 +2233,7 @@ bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime) const
|
||||
const StateNode tmp = parse(t, -1, defaultLocalTime, false);
|
||||
if (datetime)
|
||||
*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
|
||||
|
@ -274,13 +274,17 @@ MkTimeResult hopAcrossGap(const MkTimeResult &outside, const struct tm &base)
|
||||
|
||||
Q_DECL_COLD_FUNCTION
|
||||
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
|
||||
// functions, or from a gap (on a platform where mktime() rejects them).
|
||||
// QDateTime filters on times well outside the supported range, but may
|
||||
// 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;
|
||||
// Bracket base, one day each side (in case the zone skipped a whole day):
|
||||
MkTimeResult early(adjacentDay(base, -1));
|
||||
@ -291,32 +295,15 @@ MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
||||
// OK, looks like a gap.
|
||||
Q_ASSERT(twoDaysInSeconds + early.utcSecs > later.utcSecs);
|
||||
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
|
||||
// early's tm_isdst is 1 and later's isn't. Default to using 24h after
|
||||
// early (which shall fall after the gap).
|
||||
enum { AfterEarly, BeforeLater } choice = AfterEarly;
|
||||
switch (dst) {
|
||||
case QDateTimePrivate::UnknownDaylightTime:
|
||||
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;
|
||||
// Extrapolate backwards from later if this option is set:
|
||||
QDateTimePrivate::TransitionOption beforeLater = QDateTimePrivate::GapUseBefore;
|
||||
if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
|
||||
// Reverse DST has DST before a gap and not after:
|
||||
if (early.local.tm_isdst == 1 && !later.local.tm_isdst)
|
||||
beforeLater = QDateTimePrivate::GapUseAfter;
|
||||
}
|
||||
|
||||
if (choice == BeforeLater) // Result will be before the gap:
|
||||
if (resolve.testFlag(beforeLater)) // Result will be before the gap:
|
||||
result.utcSecs = later.utcSecs - secondsBetween(base, later.local);
|
||||
else // Result will be after the gap:
|
||||
result.utcSecs = early.utcSecs + secondsBetween(early.local, base);
|
||||
@ -328,7 +315,7 @@ MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
|
||||
}
|
||||
|
||||
Q_DECL_COLD_FUNCTION
|
||||
bool preferAlternative(QDateTimePrivate::DaylightStatus dst,
|
||||
bool preferAlternative(QDateTimePrivate::TransitionOptions resolve,
|
||||
// is_dst flags of incumbent and an alternative:
|
||||
int gotDst, int altDst,
|
||||
// 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:
|
||||
bool inGap)
|
||||
{
|
||||
if (dst == QDateTimePrivate::UnknownDaylightTime)
|
||||
return altIsLater; // Prefer later candidate
|
||||
|
||||
// gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
|
||||
// So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
|
||||
if ((gotDst ^ altDst) != 1) {
|
||||
// Both or neither think they're DST - pretend one is: around a gap, the
|
||||
// later candidate is DST; around a fold, the earlier.
|
||||
if (altIsLater == inGap) {
|
||||
altDst = 1;
|
||||
gotDst = 0;
|
||||
} else {
|
||||
gotDst = 1;
|
||||
altDst = 0;
|
||||
}
|
||||
// If resolve has this option set, prefer the later candidate, else the earlier:
|
||||
QDateTimePrivate::TransitionOption preferLater = inGap ? QDateTimePrivate::GapUseAfter
|
||||
: QDateTimePrivate::FoldUseAfter;
|
||||
if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
|
||||
// gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
|
||||
// So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
|
||||
if ((altDst ^ gotDst) == 1) {
|
||||
// In this case, we can tell whether we have reversed DST: that's a
|
||||
// gap with DST before it or a fold with DST after it.
|
||||
#if 1
|
||||
const bool isReversed = (altDst == 1) != (altIsLater == inGap);
|
||||
#else // Pedagogic version of the same thing:
|
||||
bool isReversed;
|
||||
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
|
||||
// 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;
|
||||
return resolve.testFlag(preferLater) == altIsLater;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -372,12 +355,12 @@ bool preferAlternative(QDateTimePrivate::DaylightStatus dst,
|
||||
|
||||
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
|
||||
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
|
||||
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);
|
||||
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:
|
||||
if (!result.good) {
|
||||
// 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) {
|
||||
// Apparently success without knowledge of whether this is DST or not.
|
||||
// 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.
|
||||
} else if (result.adjusted) {
|
||||
// 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:
|
||||
const MkTimeResult flipped = hopAcrossGap(result, base);
|
||||
// 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)) {
|
||||
// If hopAcrossGap() failed and we do need its answer, give up.
|
||||
if (!flipped.good || flipped.adjusted)
|
||||
@ -414,10 +401,11 @@ MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst
|
||||
result = flipped;
|
||||
result.adjusted = true;
|
||||
}
|
||||
} else if (dst != QDateTimePrivate::UnknownDaylightTime
|
||||
// We may not need to check whether we're in a transition:
|
||||
// Does DST-ness match what we were asked for ?
|
||||
&& result.local.tm_isdst == (dst == QDateTimePrivate::StandardTime ? 0 : 1)) {
|
||||
} else if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
|
||||
// In fold, DST counts as before and standard as after -
|
||||
// we may not need to check whether we're in a transition:
|
||||
&& resolve.testFlag(result.local.tm_isdst ? QDateTimePrivate::FoldUseBefore
|
||||
: QDateTimePrivate::FoldUseAfter)) {
|
||||
// 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
|
||||
// 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);
|
||||
if (flipped.good && !flipped.adjusted) {
|
||||
// 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)) {
|
||||
result = flipped;
|
||||
}
|
||||
@ -563,9 +557,9 @@ QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis)
|
||||
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)
|
||||
return {};
|
||||
#ifdef HAVE_TM_ZONE
|
||||
@ -575,11 +569,11 @@ QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus
|
||||
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:
|
||||
qint64 localSecs = local / MSECS_PER_SEC;
|
||||
auto use = resolveLocalTime(localSecs, dst);
|
||||
auto use = resolveLocalTime(localSecs, resolve);
|
||||
if (!use.good)
|
||||
return {local};
|
||||
|
||||
@ -588,8 +582,9 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh
|
||||
Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
|
||||
: (millis >= 0 && millis < MSECS_PER_SEC));
|
||||
|
||||
// Revise our original hint-dst to what it resolved to:
|
||||
dst = use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
|
||||
QDateTimePrivate::DaylightStatus dst =
|
||||
use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
|
||||
|
||||
#ifdef HAVE_TM_GMTOFF
|
||||
const int offset = use.local.tm_gmtoff;
|
||||
localSecs = offset + use.utcSecs;
|
||||
|
@ -34,8 +34,8 @@ Q_CORE_EXPORT int getUtcOffset(qint64 atMSecsSinceEpoch);
|
||||
|
||||
// Support for QDateTime
|
||||
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis);
|
||||
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
||||
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
||||
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::TransitionOptions resolve);
|
||||
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve);
|
||||
|
||||
struct SystemMillisRange { qint64 min, max; bool minClip, maxClip; };
|
||||
SystemMillisRange computeSystemMillisRange();
|
||||
|
@ -7,11 +7,13 @@
|
||||
#include "qtimezoneprivate_p.h"
|
||||
#include "qtimezoneprivate_data_p.h"
|
||||
|
||||
#include <private/qnumeric_p.h>
|
||||
#include <private/qtools_p.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qdebug.h>
|
||||
|
||||
#include <private/qcalendarmath_p.h>
|
||||
#include <private/qnumeric_p.h>
|
||||
#include <private/qtools_p.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
// At most one of those was clipped to its boundary value:
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
|
||||
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,
|
||||
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.
|
||||
reasoning that applies to DST applies just the same.
|
||||
|
||||
The handling of hint (see below) is apt to go wrong in non-DST
|
||||
transitions. There isn't really a great deal we can hope to do about that
|
||||
without adding yet more unreliable complexity to the heuristics in use for
|
||||
already obscure corner-cases.
|
||||
*/
|
||||
|
||||
/*
|
||||
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.)
|
||||
The resolution of transitions, specified by \a resolve, may be lead astray
|
||||
if (as happens on Windows) the backend has been obliged to guess whether a
|
||||
transition is in fact a DST one or a change to standard offset; or to
|
||||
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.
|
||||
*/
|
||||
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()) {
|
||||
/*
|
||||
We have transitions.
|
||||
|
||||
Each transition gives the offsets to use until the next; so we need the
|
||||
most recent transition before the time forLocalMSecs describes. If it
|
||||
describes a time *in* a transition, we'll need both that transition and
|
||||
the one before it. So find one transition that's probably after (and not
|
||||
much before, otherwise) and another that's definitely before, then work
|
||||
out which one to use. When both or neither work on forLocalMSecs, use
|
||||
hint to disambiguate.
|
||||
Each transition gives the offsets to use until the next; so we need
|
||||
the most recent transition before the time forLocalMSecs describes. If
|
||||
it describes a time *in* a transition, we'll need both that transition
|
||||
and the one before it. So find one transition that's probably after
|
||||
(and not much before, otherwise) and another that's definitely before,
|
||||
then work out which one to use. When both or neither work on
|
||||
forLocalMSecs, use resolve to disambiguate.
|
||||
*/
|
||||
|
||||
// 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:
|
||||
const qint64 nextStart = nextTran.atMSecsSinceEpoch;
|
||||
if (nextStart == invalidMSecs())
|
||||
return tran;
|
||||
return dataToState(tran); // Last valid transition.
|
||||
|
||||
/*
|
||||
... and nextTran is either after or only slightly before. We're
|
||||
going to interpret one as standard time, the other as DST
|
||||
(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
|
||||
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,
|
||||
life's tricky.
|
||||
offset, or a change in DST offset, e.g. to/from double-DST).
|
||||
|
||||
Usually exactly one of those shall be relevant and we'll use it;
|
||||
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:
|
||||
nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
|
||||
|
||||
// If both or neither have zero DST, treat the one with lower offset as standard:
|
||||
const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
|
||||
? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
|
||||
// If that agrees with hint > 0, our first guess is to use nextTran; else tran.
|
||||
const bool nextFirst = nextIsDst == (hint > 0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
bool fallBack = false;
|
||||
if (nextStart > nextTran.atMSecsSinceEpoch) {
|
||||
// If both UTC values are before nextTran's offset applies, use tran:
|
||||
if (nextStart > tran.atMSecsSinceEpoch)
|
||||
return dataToState(tran);
|
||||
|
||||
/*
|
||||
Neither is valid (e.g. in a spring-forward's gap) and
|
||||
nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch;
|
||||
swap their atMSecsSinceEpoch to give each a moment on its side of
|
||||
the transition; and pick the reverse of what hint asked for:
|
||||
*/
|
||||
std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
|
||||
return nextFirst ? tran : nextTran;
|
||||
Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
|
||||
// We're in a spring-forward.
|
||||
} else if (nextStart <= tran.atMSecsSinceEpoch) {
|
||||
// Both UTC values say we should be using nextTran:
|
||||
return dataToState(nextTran);
|
||||
} else {
|
||||
Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
|
||||
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.
|
||||
// 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. */
|
||||
qint64 utcEpochMSecs;
|
||||
|
||||
// We don't have true data on DST-ness, so can't apply FlipForReverseDst.
|
||||
int early = past.offsetFromUtc;
|
||||
int late = future.offsetFromUtc;
|
||||
if (early == late || late == invalidSeconds()) {
|
||||
if (early == invalidSeconds()
|
||||
|| qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
|
||||
return invalidData(); // Outside representable range
|
||||
return {forLocalMSecs}; // Outside representable range
|
||||
}
|
||||
} 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):
|
||||
const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
|
||||
const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
|
||||
// Best guess at the answer:
|
||||
const qint64 hinted = hint > 0 ? forDst : forStd;
|
||||
if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
|
||||
utcEpochMSecs = hinted;
|
||||
} else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
|
||||
utcEpochMSecs = forDst;
|
||||
} else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
|
||||
utcEpochMSecs = forStd;
|
||||
const qint64 forEarly = forLocalMSecs - early * 1000;
|
||||
const qint64 forLate = forLocalMSecs - late * 1000;
|
||||
// If either of those doesn't have the offset we got it from, it's on
|
||||
// the wrong side of the transition (and both may be, for a gap):
|
||||
const bool earlyOk = offsetFromUtc(forEarly) == early;
|
||||
const bool lateOk = offsetFromUtc(forLate) == late;
|
||||
|
||||
if (earlyOk) {
|
||||
if (lateOk) {
|
||||
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 {
|
||||
// Invalid forLocalMSecs: in spring-forward gap.
|
||||
const int dstStep = (offsetInDst - offsetInStd) * 1000;
|
||||
// That'll typically be the DST offset at imminent, but changes to
|
||||
// standard time have zero DST offset both before and after.
|
||||
Q_ASSERT(dstStep > 0); // There can't be a gap without it !
|
||||
utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
|
||||
// forLate <= gap < forEarly
|
||||
Q_ASSERT(late > early);
|
||||
const int dstStep = (late - early) * 1000;
|
||||
if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
|
||||
utcEpochMSecs = forEarly - dstStep;
|
||||
else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
|
||||
utcEpochMSecs = forLate + dstStep;
|
||||
else
|
||||
return {forLocalMSecs};
|
||||
}
|
||||
}
|
||||
|
||||
return data(utcEpochMSecs);
|
||||
return dataToState(data(utcEpochMSecs));
|
||||
}
|
||||
|
||||
bool QTimeZonePrivate::hasTransitions() const
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "qlist.h"
|
||||
#include "qtimezone.h"
|
||||
#include "private/qlocale_p.h"
|
||||
#include "private/qdatetime_p.h"
|
||||
|
||||
#if QT_CONFIG(icu)
|
||||
#include <unicode/ucal.h>
|
||||
@ -84,7 +85,8 @@ public:
|
||||
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) 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 Data nextTransition(qint64 afterMSecsSinceEpoch) const;
|
||||
|
@ -1450,16 +1450,11 @@ void QDateTimeEdit::fixup(QString &input) const
|
||||
int copy = d->edit->cursorPosition();
|
||||
|
||||
QDateTime value = d->validateAndInterpret(input, copy, state, true);
|
||||
/*
|
||||
String was valid, but the datetime still is not; use the time that
|
||||
has the same distance from epoch.
|
||||
CorrectToPreviousValue correction is handled by QAbstractSpinBox.
|
||||
*/
|
||||
if (!value.isValid() && d->correctionMode == QAbstractSpinBox::CorrectToNearestValue) {
|
||||
value = QDateTime::fromMSecsSinceEpoch(value.toMSecsSinceEpoch(),
|
||||
value.timeRepresentation());
|
||||
// CorrectToPreviousValue correction is handled by QAbstractSpinBox.
|
||||
// The value might not match the input if the input represents a date-time
|
||||
// skipped over by its time representation, such as a spring-forward.
|
||||
if (d->correctionMode == QAbstractSpinBox::CorrectToNearestValue)
|
||||
input = textFromDateTime(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1727,11 +1722,7 @@ QDateTime QDateTimeEditPrivate::convertTimeZone(const QDateTime &datetime)
|
||||
|
||||
QDateTime QDateTimeEditPrivate::dateTimeValue(QDate date, QTime time) const
|
||||
{
|
||||
QDateTime when = QDateTime(date, time, timeZone);
|
||||
if (when.isValid())
|
||||
return when;
|
||||
// Hit a spring-forward gap
|
||||
return QDateTime::fromMSecsSinceEpoch(when.toMSecsSinceEpoch(), timeZone);
|
||||
return QDateTime(date, time, timeZone);
|
||||
}
|
||||
|
||||
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
|
||||
isn't), so use the time that has the same distance from epoch.
|
||||
*/
|
||||
if (setDigit(v, sectionIndex, val) && !v.isValid()) {
|
||||
auto msecsSinceEpoch = v.toMSecsSinceEpoch();
|
||||
if (setDigit(v, sectionIndex, val) && getDigit(v, sectionIndex) != val
|
||||
&& sn.type & HourSectionMask && steps < 0) {
|
||||
// decreasing from e.g 3am to 2am would get us back to 3am, but we want 1am
|
||||
if (steps < 0 && sn.type & HourSectionMask)
|
||||
msecsSinceEpoch -= 3600 * 1000;
|
||||
auto msecsSinceEpoch = v.toMSecsSinceEpoch() - 3600 * 1000;
|
||||
v = QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch, v.timeRepresentation());
|
||||
}
|
||||
// if this sets year or month it will make
|
||||
|
@ -627,14 +627,18 @@ void tst_QDate::startOfDay_endOfDay()
|
||||
QCOMPARE(front.date(), date);
|
||||
UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start);
|
||||
} else UNLESSKLUDGE(IgnoreStart) {
|
||||
auto report = qScopeGuard([front]() { qDebug() << "Start of day:" << front; });
|
||||
QVERIFY(!front.isValid());
|
||||
report.dismiss();
|
||||
}
|
||||
if (end.isValid()) {
|
||||
QVERIFY(back.isValid());
|
||||
QCOMPARE(back.date(), date);
|
||||
UNLESSKLUDGE(IgnoreEnd) QCOMPARE(back.time(), end);
|
||||
} else UNLESSKLUDGE(IgnoreEnd) {
|
||||
auto report = qScopeGuard([back]() { qDebug() << "End of day:" << back; });
|
||||
QVERIFY(!back.isValid());
|
||||
report.dismiss();
|
||||
}
|
||||
#undef UNLESSKLUDGE
|
||||
}
|
||||
|
@ -2286,15 +2286,14 @@ void tst_QDateTime::springForward()
|
||||
QFETCH(int, adjust);
|
||||
|
||||
QDateTime direct = QDateTime(day.addDays(-step), time, zone).addDays(step);
|
||||
if (direct.isValid()) { // mktime() may deem a time in the gap invalid
|
||||
QCOMPARE(direct.date(), day);
|
||||
QCOMPARE(direct.time().minute(), time.minute());
|
||||
QCOMPARE(direct.time().second(), time.second());
|
||||
const int off = step < 0 ? -1 : 1;
|
||||
QCOMPARE(direct.time().hour() - time.hour(), off);
|
||||
// adjust is the offset on the other side of the gap:
|
||||
QCOMPARE(direct.offsetFromUtc(), (adjust + off * 60) * 60);
|
||||
}
|
||||
QVERIFY(direct.isValid());
|
||||
QCOMPARE(direct.date(), day);
|
||||
QCOMPARE(direct.time().minute(), time.minute());
|
||||
QCOMPARE(direct.time().second(), time.second());
|
||||
const int off = step < 0 ? -1 : 1;
|
||||
QCOMPARE(direct.time().hour() - time.hour(), off);
|
||||
// adjust is the offset on the other side of the gap:
|
||||
QCOMPARE(direct.offsetFromUtc(), (adjust + off * 60) * 60);
|
||||
|
||||
// Repeat, but getting there via .toTimeZone(). Apply adjust to datetime,
|
||||
// not time, as the time wraps round if the adjustment crosses midnight.
|
||||
@ -2303,12 +2302,8 @@ void tst_QDateTime::springForward()
|
||||
QCOMPARE(detour.time(), time);
|
||||
detour = detour.addDays(step);
|
||||
// Insist on consistency:
|
||||
if (direct.isValid()) {
|
||||
QCOMPARE(detour, direct);
|
||||
QCOMPARE(detour.offsetFromUtc(), direct.offsetFromUtc());
|
||||
} else {
|
||||
QVERIFY(!detour.isValid());
|
||||
}
|
||||
QCOMPARE(detour, direct);
|
||||
QCOMPARE(detour.offsetFromUtc(), direct.offsetFromUtc());
|
||||
}
|
||||
|
||||
void tst_QDateTime::operator_eqeq_data()
|
||||
@ -3267,11 +3262,10 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data()
|
||||
QTimeZone helsinki("Europe/Helsinki");
|
||||
if (helsinki.isValid()) {
|
||||
lacksRows = false;
|
||||
// QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert in
|
||||
// QTimeZonePrivate::dataForLocalTime() on macOS and iOS.
|
||||
// The first 20m 11s of 1921-05-01 were skipped, so the parser's attempt
|
||||
// to construct a local time after scanning yyMM tripped up on the start
|
||||
// of the day, when the zone backend lacked transition data.
|
||||
// QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert due to
|
||||
// the first 20m 11s of 1921-05-01 being skipped, so the parser's
|
||||
// attempt to construct a local time after scanning yyMM tripped up on
|
||||
// the start of the day, when the zone backend lacked transition data.
|
||||
QTest::newRow("Helsinki-joins-EET")
|
||||
<< QByteArrayLiteral("Europe/Helsinki")
|
||||
<< QString("210506000000Z") << QString("yyMMddHHmmsst")
|
||||
@ -3702,12 +3696,23 @@ void tst_QDateTime::daylightTransitions() const
|
||||
QCOMPARE(before.time(), QTime(1, 59, 59, 999));
|
||||
QCOMPARE(before.toMSecsSinceEpoch(), spring2012 - 1);
|
||||
|
||||
QDateTime missing(QDate(2012, 3, 25), QTime(2, 0));
|
||||
QVERIFY(!missing.isValid());
|
||||
QCOMPARE(missing.date(), QDate(2012, 3, 25));
|
||||
QCOMPARE(missing.time(), QTime(3, 0));
|
||||
// datetimeparser relies on toMSecsSinceEpoch to still work:
|
||||
QCOMPARE(missing.toMSecsSinceEpoch(), spring2012);
|
||||
QDateTime entering(QDate(2012, 3, 25), QTime(2, 0),
|
||||
QDateTime::TransitionResolution::PreferBefore);
|
||||
QVERIFY(entering.isValid());
|
||||
QVERIFY(!entering.isDaylightTime());
|
||||
QCOMPARE(entering.date(), QDate(2012, 3, 25));
|
||||
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));
|
||||
QVERIFY(after.isValid());
|
||||
@ -3735,11 +3740,11 @@ void tst_QDateTime::daylightTransitions() const
|
||||
QVERIFY(utc.isValid());
|
||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||
QCOMPARE(utc.time(), QTime(2, 0));
|
||||
utc.setTimeZone(QTimeZone::LocalTime);
|
||||
QVERIFY(!utc.isValid());
|
||||
utc.setTimeZone(QTimeZone::LocalTime); // Resolved to RelativeToBefore.
|
||||
QVERIFY(utc.isValid());
|
||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||
QCOMPARE(utc.time(), QTime(3, 0));
|
||||
utc.setTimeZone(UTC);
|
||||
utc.setTimeZone(UTC); // Preserves the changed time().
|
||||
QVERIFY(utc.isValid());
|
||||
QCOMPARE(utc.date(), QDate(2012, 3, 25));
|
||||
QCOMPARE(utc.time(), QTime(3, 0));
|
||||
@ -3780,19 +3785,17 @@ void tst_QDateTime::daylightTransitions() const
|
||||
#undef CHECK_SPRING_FORWARD
|
||||
|
||||
// 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();
|
||||
QVERIFY(autumnMidnight.isValid());
|
||||
// QCOMPARE(autumnMidnight, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Prior));
|
||||
QCOMPARE(autumnMidnight.date(), QDate(2012, 10, 28));
|
||||
QCOMPARE(autumnMidnight.time(), QTime(0, 0));
|
||||
QCOMPARE(autumnMidnight.toMSecsSinceEpoch(), autumn2012 - 3 * msecsOneHour);
|
||||
|
||||
QDateTime startFirst = autumnMidnight.addMSecs(2 * msecsOneHour);
|
||||
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.time(), QTime(2, 0));
|
||||
QCOMPARE(startFirst.toMSecsSinceEpoch(), autumn2012 - msecsOneHour);
|
||||
@ -3800,7 +3803,9 @@ void tst_QDateTime::daylightTransitions() const
|
||||
// 1 msec before transition is 2:59:59.999 FirstOccurrence
|
||||
QDateTime endFirst = startFirst.addMSecs(msecsOneHour - 1);
|
||||
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.time(), QTime(2, 59, 59, 999));
|
||||
QCOMPARE(endFirst.toMSecsSinceEpoch(), autumn2012 - 1);
|
||||
@ -3808,7 +3813,8 @@ void tst_QDateTime::daylightTransitions() const
|
||||
// At the transition, starting the second pass
|
||||
QDateTime startRepeat = endFirst.addMSecs(1);
|
||||
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.time(), QTime(2, 0));
|
||||
QCOMPARE(startRepeat.toMSecsSinceEpoch(), autumn2012);
|
||||
@ -3816,7 +3822,9 @@ void tst_QDateTime::daylightTransitions() const
|
||||
// 59:59.999 after transition is 2:59:59.999 SecondOccurrence
|
||||
QDateTime endRepeat = endFirst.addMSecs(msecsOneHour);
|
||||
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.time(), QTime(2, 59, 59, 999));
|
||||
QCOMPARE(endRepeat.toMSecsSinceEpoch(), autumn2012 + msecsOneHour - 1);
|
||||
@ -4209,20 +4217,20 @@ void tst_QDateTime::timeZones() const
|
||||
QCOMPARE(atGap.toMSecsSinceEpoch(), gapMSecs);
|
||||
// - Test transition hole, setting 02:00:00 is invalid
|
||||
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.time(), QTime(3, 0));
|
||||
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);
|
||||
QVERIFY(!inGap.isValid());
|
||||
QVERIFY(inGap.isValid());
|
||||
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
||||
QCOMPARE(inGap.time(), QTime(3, 59, 59, 999));
|
||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||
// Test similar for local time, if it's CET:
|
||||
if (zoneIsCET) {
|
||||
inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 30));
|
||||
QVERIFY(!inGap.isValid());
|
||||
QVERIFY(inGap.isValid());
|
||||
QCOMPARE(inGap.date(), QDate(2013, 3, 31));
|
||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||
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(
|
||||
QDateTime(QDate(longYear, 3, 31), QTime(12, 0), cet)) < millisInWeek) {
|
||||
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.time(), QTime(3, 30));
|
||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||
@ -4248,7 +4256,7 @@ void tst_QDateTime::timeZones() const
|
||||
if (zoneIsCET && QDateTime(QDate(longYear, 3, 24), QTime(12, 0)).msecsTo(
|
||||
QDateTime(QDate(longYear, 3, 31), QTime(12, 0))) < millisInWeek) {
|
||||
inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30));
|
||||
QVERIFY(!inGap.isValid());
|
||||
QVERIFY(inGap.isValid());
|
||||
QCOMPARE(inGap.date(), QDate(longYear, 3, 27));
|
||||
QCOMPARE(inGap.offsetFromUtc(), 7200);
|
||||
QCOMPARE(inGap.time(), QTime(3, 30));
|
||||
|
Loading…
Reference in New Issue
Block a user