QDate: enable {start,end}OfDay() to make second-adjustments

Previously it only got the answer correct to the minute. That's good
enough for most transitions, but those involving local solar mean time
(LMT) may have second deviations. So check the second before or after,
that'll usually be the other side of the transition; if it isn't, do a
further binary chop on seconds to hit the correct value.

Most zones' canonical locations are only specified to the size of a
city, so second precision is all anyone cares about; a few hundred
metres difference in location would change that. The one exception is
Europe/Amsterdam, which had its own Royal Observatory and time
standard, hence had an offset known to greater precision; but the IANA
DB duly approximates that, too, so we won't have data with millisecond
precision even in that case, so don't try to refine beyond second.

Change-Id: I20fb355f8113c32387ed8a84fbf5a41004273978
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Konrad Kujawa <konrad.kujawa@qt.io>
This commit is contained in:
Edward Welbourne 2023-03-31 11:40:42 +02:00 committed by Thiago Macieira
parent b906796af6
commit 76075aa3a9

View File

@ -864,8 +864,8 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone)
int low = 0;
// Binary chop to the right minute
while (high > low + 1) {
int mid = (high + low) / 2;
QDateTime probe = moment(QTime(mid / 60, mid % 60));
const int mid = (high + low) / 2;
const QDateTime probe = moment(QTime(mid / 60, mid % 60));
if (probe.isValid() && probe.date() == day) {
high = mid;
when = probe;
@ -873,6 +873,24 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone)
low = mid;
}
}
// Transitions out of local solar mean time, and the few international
// date-line crossings before that (Alaska, Philippines), may have happened
// between minute boundaries. Don't try to fix milliseconds.
if (QDateTime p = moment(when.time().addSecs(-1)); Q_UNLIKELY(p.isValid() && p.date() == day)) {
high *= 60;
low *= 60;
while (high > low + 1) {
const int mid = (high + low) / 2;
const int min = mid / 60;
const QDateTime probe = moment(QTime(min / 60, min % 60, mid % 60));
if (probe.isValid() && probe.date() == day) {
high = mid;
when = probe;
} else {
low = mid;
}
}
}
return when.isValid() ? when : QDateTime();
}
@ -910,7 +928,7 @@ QDateTime QDate::startOfDay(const QTimeZone &zone) const
return QDateTime();
QDateTime when(*this, QTime(0, 0), zone);
if (when.isValid())
if (Q_LIKELY(when.isValid()))
return when;
#if QT_CONFIG(timezone)
@ -995,8 +1013,8 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone)
int low = when.time().msecsSinceStartOfDay() / 60000;
// Binary chop to the right minute
while (high > low + 1) {
int mid = (high + low) / 2;
QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999));
const int mid = (high + low) / 2;
const QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999));
if (probe.isValid() && probe.date() == day) {
low = mid;
when = probe;
@ -1004,6 +1022,24 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone)
high = mid;
}
}
// Transitions out of local solar mean time, and the few international
// date-line crossings before that (Alaska, Philippines), may have happened
// between minute boundaries. Don't try to fix milliseconds.
if (QDateTime p = moment(when.time().addSecs(1)); Q_UNLIKELY(p.isValid() && p.date() == day)) {
high *= 60;
low *= 60;
while (high > low + 1) {
const int mid = (high + low) / 2;
const int min = mid / 60;
const QDateTime probe = moment(QTime(min / 60, min % 60, mid % 60, 999));
if (probe.isValid() && probe.date() == day) {
low = mid;
when = probe;
} else {
high = mid;
}
}
}
return when.isValid() ? when : QDateTime();
}
@ -1042,7 +1078,7 @@ QDateTime QDate::endOfDay(const QTimeZone &zone) const
return QDateTime();
QDateTime when(*this, QTime(23, 59, 59, 999), zone);
if (when.isValid())
if (Q_LIKELY(when.isValid()))
return when;
#if QT_CONFIG(timezone)