summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2016-12-06 13:44:00 +0100
committerEdward Welbourne <edward.welbourne@qt.io>2018-01-18 14:27:42 +0000
commitf9b8ea4b0eaea94a6d724a0955b13fd8a97d96e3 (patch)
treee7a1f45e977df41bada7d6f336e0f71c79c2f0cb
parent016ab238b66d84a58b665782d25daaffdf1708bd (diff)
Fix QMacTimeZonePrivate::previousTransition() for early after epoch
The native APIs don't support previous transition, only next after a stipulated date. The prior code started its search at the epoch; if used for a time before the first transition after the epoch, this found no transitions so returned invalid data, when the last transition before the epoch would have been suitable. It also wound through all transitions since the epoch, on its way to the selected time, which was potentially laborious. Instead, start a year before the stipulated time; this should get a transition if the zone uses DST. If it doesn't, start with the first known transition and binary-chop our way to one within a year of the last before the stipulated time; then wind forward one transition at a time, as before. The chopping is actually faster than binary: each time we find a transition after the interval mid-point but early enough, we move the early end of our interval to the transition, which is later than the old interval's middle. Using halving, starting with a vast interval, should thus only incur modest cost, while ensuring we give up early when no transition data is available at all or the zone's first transition ever was after the stipulated time. Task-number: QTBUG-65443 Change-Id: I96c14540fc2600837e6a22e480fb8dc36cb37220 (cherry picked from commit 7c0b706488b0edcc2fd6db7870db700ff0548f21) (cherry picked from commit a090076e93487f8e461d9b866b9da1c0c21cb59b) Reviewed-by: Andy Shaw <andy.shaw@qt.io> Reviewed-by: Jason Dolan <jason.t.dolan@gmail.com>
-rw-r--r--src/corelib/tools/qtimezoneprivate_mac.mm88
1 files changed, 70 insertions, 18 deletions
diff --git a/src/corelib/tools/qtimezoneprivate_mac.mm b/src/corelib/tools/qtimezoneprivate_mac.mm
index 4e9a432fbf..fa0dd87cfc 100644
--- a/src/corelib/tools/qtimezoneprivate_mac.mm
+++ b/src/corelib/tools/qtimezoneprivate_mac.mm
@@ -226,27 +226,79 @@ QTimeZonePrivate::Data QMacTimeZonePrivate::nextTransition(qint64 afterMSecsSinc
QTimeZonePrivate::Data QMacTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
{
- // No direct Mac API, so get all transitions since epoch and return the last one
- QList<int> secsList;
- if (beforeMSecsSinceEpoch > 0) {
- const int endSecs = beforeMSecsSinceEpoch / 1000.0;
- NSTimeInterval prevSecs = 0;
- NSTimeInterval nextSecs = 0;
- NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:nextSecs];
- // If invalid may return a nil date or an Epoch date
+ // The native API only lets us search forward, so we need to find an early-enough start:
+ const NSTimeInterval lowerBound = std::numeric_limits<NSTimeInterval>::min();
+ const qint64 endSecs = beforeMSecsSinceEpoch / 1000;
+ const int year = 366 * 24 * 3600; // a (long) year, in seconds
+ NSTimeInterval prevSecs = endSecs; // sentinel for later check
+ NSTimeInterval nextSecs = prevSecs - year;
+ NSTimeInterval tranSecs = lowerBound; // time at a transition; may be > endSecs
+
+ NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:nextSecs];
+ nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
+ if (nextDate != nil
+ && (tranSecs = [nextDate timeIntervalSince1970]) < endSecs) {
+ // There's a transition within the last year before endSecs:
+ nextSecs = tranSecs;
+ } else {
+ // Need to start our search earlier:
+ nextDate = [NSDate dateWithTimeIntervalSince1970:lowerBound];
nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
- nextSecs = [nextDate timeIntervalSince1970];
- while (nextDate != nil && nextSecs > prevSecs && nextSecs < endSecs) {
- secsList.append(nextSecs);
- prevSecs = nextSecs;
- nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
+ if (nextDate != nil) {
+ NSTimeInterval lateSecs = nextSecs;
nextSecs = [nextDate timeIntervalSince1970];
- }
+ Q_ASSERT(nextSecs <= endSecs - year || nextSecs == tranSecs);
+ /*
+ We're looking at the first ever transition for our zone, at
+ nextSecs (and our zone *does* have at least one transition). If
+ it's later than endSecs - year, then we must have found it on the
+ initial check and therefore set tranSecs to the same transition
+ time (which, we can infer here, is >= endSecs). In this case, we
+ won't enter the binary-chop loop, below.
+
+ In the loop, nextSecs < lateSecs < endSecs: we have a transition
+ at nextSecs and there is no transition between lateSecs and
+ endSecs. The loop narrows the interval between nextSecs and
+ lateSecs by looking for a transition after their mid-point; if it
+ finds one < endSecs, nextSecs moves to this transition; otherwise,
+ lateSecs moves to the mid-point. This soon enough narrows the gap
+ to within a year, after which walking forward one transition at a
+ time (the "Wind through" loop, below) is good enough.
+ */
+
+ // Binary chop to within a year of last transition before endSecs:
+ while (nextSecs + year < lateSecs) {
+ // Careful about overflow, not fussy about rounding errors:
+ NSTimeInterval middle = nextSecs / 2 + lateSecs / 2;
+ NSDate *split = [NSDate dateWithTimeIntervalSince1970:middle];
+ split = [m_nstz nextDaylightSavingTimeTransitionAfterDate:split];
+ if (split != nil
+ && (tranSecs = [split timeIntervalSince1970]) < endSecs) {
+ nextDate = split;
+ nextSecs = tranSecs;
+ } else {
+ lateSecs = middle;
+ }
+ }
+ Q_ASSERT(nextDate != nil);
+ // ... and nextSecs < endSecs unless first transition ever was >= endSecs.
+ } // else: we have no data - prevSecs is still endSecs, nextDate is still nil
}
- if (secsList.size() >= 1)
- return data(qint64(secsList.constLast()) * 1000);
- else
- return invalidData();
+ // Either nextDate is nil or nextSecs is at its transition.
+
+ // Wind through remaining transitions (spanning at most a year), one at a time:
+ while (nextDate != nil && nextSecs < endSecs) {
+ prevSecs = nextSecs;
+ nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
+ nextSecs = [nextDate timeIntervalSince1970];
+ if (nextSecs <= prevSecs) // presumably no later data available
+ break;
+ }
+ if (prevSecs < endSecs) // i.e. we did make it into that while loop
+ return data(qint64(prevSecs * 1e3));
+
+ // No transition data; or first transition later than requested time.
+ return invalidData();
}
QByteArray QMacTimeZonePrivate::systemTimeZoneId() const