1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Copyright (C) 2013 John Layt <jlayt@kde.org> |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the QtCore module of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:LGPL$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU Lesser General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
22 | ** packaging of this file. Please review the following information to |
23 | ** ensure the GNU Lesser General Public License version 3 requirements |
24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
25 | ** |
26 | ** GNU General Public License Usage |
27 | ** Alternatively, this file may be used under the terms of the GNU |
28 | ** General Public License version 2.0 or (at your option) the GNU General |
29 | ** Public license version 3 or any later version approved by the KDE Free |
30 | ** Qt Foundation. The licenses are as published by the Free Software |
31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
32 | ** included in the packaging of this file. Please review the following |
33 | ** information to ensure the GNU General Public License requirements will |
34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
35 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
36 | ** |
37 | ** $QT_END_LICENSE$ |
38 | ** |
39 | ****************************************************************************/ |
40 | |
41 | |
42 | #include "qtimezone.h" |
43 | #include "qtimezoneprivate_p.h" |
44 | #include "qtimezoneprivate_data_p.h" |
45 | |
46 | #include <qdatastream.h> |
47 | #include <qdebug.h> |
48 | |
49 | #include <algorithm> |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | /* |
54 | Static utilities for looking up Windows ID tables |
55 | */ |
56 | |
57 | static const int windowsDataTableSize = sizeof(windowsDataTable) / sizeof(QWindowsData) - 1; |
58 | static const int zoneDataTableSize = sizeof(zoneDataTable) / sizeof(QZoneData) - 1; |
59 | static const int utcDataTableSize = sizeof(utcDataTable) / sizeof(QUtcData) - 1; |
60 | |
61 | |
62 | static const QZoneData *zoneData(quint16 index) |
63 | { |
64 | Q_ASSERT(index < zoneDataTableSize); |
65 | return &zoneDataTable[index]; |
66 | } |
67 | |
68 | static const QWindowsData *windowsData(quint16 index) |
69 | { |
70 | Q_ASSERT(index < windowsDataTableSize); |
71 | return &windowsDataTable[index]; |
72 | } |
73 | |
74 | static const QUtcData *utcData(quint16 index) |
75 | { |
76 | Q_ASSERT(index < utcDataTableSize); |
77 | return &utcDataTable[index]; |
78 | } |
79 | |
80 | // Return the Windows ID literal for a given QWindowsData |
81 | static QByteArray windowsId(const QWindowsData *windowsData) |
82 | { |
83 | return (windowsIdData + windowsData->windowsIdIndex); |
84 | } |
85 | |
86 | // Return the IANA ID literal for a given QWindowsData |
87 | static QByteArray ianaId(const QWindowsData *windowsData) |
88 | { |
89 | return (ianaIdData + windowsData->ianaIdIndex); |
90 | } |
91 | |
92 | // Return the IANA ID literal for a given QZoneData |
93 | static QByteArray ianaId(const QZoneData *zoneData) |
94 | { |
95 | return (ianaIdData + zoneData->ianaIdIndex); |
96 | } |
97 | |
98 | static QByteArray utcId(const QUtcData *utcData) |
99 | { |
100 | return (ianaIdData + utcData->ianaIdIndex); |
101 | } |
102 | |
103 | static quint16 toWindowsIdKey(const QByteArray &winId) |
104 | { |
105 | for (quint16 i = 0; i < windowsDataTableSize; ++i) { |
106 | const QWindowsData *data = windowsData(i); |
107 | if (windowsId(data) == winId) |
108 | return data->windowsIdKey; |
109 | } |
110 | return 0; |
111 | } |
112 | |
113 | static QByteArray toWindowsIdLiteral(quint16 windowsIdKey) |
114 | { |
115 | for (quint16 i = 0; i < windowsDataTableSize; ++i) { |
116 | const QWindowsData *data = windowsData(i); |
117 | if (data->windowsIdKey == windowsIdKey) |
118 | return windowsId(data); |
119 | } |
120 | return QByteArray(); |
121 | } |
122 | |
123 | /* |
124 | Base class implementing common utility routines, only intantiate for a null tz. |
125 | */ |
126 | |
127 | QTimeZonePrivate::QTimeZonePrivate() |
128 | { |
129 | } |
130 | |
131 | QTimeZonePrivate::QTimeZonePrivate(const QTimeZonePrivate &other) |
132 | : QSharedData(other), m_id(other.m_id) |
133 | { |
134 | } |
135 | |
136 | QTimeZonePrivate::~QTimeZonePrivate() |
137 | { |
138 | } |
139 | |
140 | QTimeZonePrivate *QTimeZonePrivate::clone() const |
141 | { |
142 | return new QTimeZonePrivate(*this); |
143 | } |
144 | |
145 | bool QTimeZonePrivate::operator==(const QTimeZonePrivate &other) const |
146 | { |
147 | // TODO Too simple, but need to solve problem of comparing different derived classes |
148 | // Should work for all System and ICU classes as names guaranteed unique, but not for Simple. |
149 | // Perhaps once all classes have working transitions can compare full list? |
150 | return (m_id == other.m_id); |
151 | } |
152 | |
153 | bool QTimeZonePrivate::operator!=(const QTimeZonePrivate &other) const |
154 | { |
155 | return !(*this == other); |
156 | } |
157 | |
158 | bool QTimeZonePrivate::isValid() const |
159 | { |
160 | return !m_id.isEmpty(); |
161 | } |
162 | |
163 | QByteArray QTimeZonePrivate::id() const |
164 | { |
165 | return m_id; |
166 | } |
167 | |
168 | QLocale::Country QTimeZonePrivate::country() const |
169 | { |
170 | // Default fall-back mode, use the zoneTable to find Region of known Zones |
171 | for (int i = 0; i < zoneDataTableSize; ++i) { |
172 | const QZoneData *data = zoneData(i); |
173 | if (ianaId(data).split(' ').contains(m_id)) |
174 | return (QLocale::Country)data->country; |
175 | } |
176 | return QLocale::AnyCountry; |
177 | } |
178 | |
179 | QString QTimeZonePrivate::() const |
180 | { |
181 | return QString(); |
182 | } |
183 | |
184 | QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, |
185 | QTimeZone::NameType nameType, |
186 | const QLocale &locale) const |
187 | { |
188 | if (nameType == QTimeZone::OffsetName) |
189 | return isoOffsetFormat(offsetFromUtc(atMSecsSinceEpoch)); |
190 | |
191 | if (isDaylightTime(atMSecsSinceEpoch)) |
192 | return displayName(QTimeZone::DaylightTime, nameType, locale); |
193 | else |
194 | return displayName(QTimeZone::StandardTime, nameType, locale); |
195 | } |
196 | |
197 | QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType, |
198 | QTimeZone::NameType nameType, |
199 | const QLocale &locale) const |
200 | { |
201 | Q_UNUSED(timeType); |
202 | Q_UNUSED(nameType); |
203 | Q_UNUSED(locale); |
204 | return QString(); |
205 | } |
206 | |
207 | QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const |
208 | { |
209 | Q_UNUSED(atMSecsSinceEpoch); |
210 | return QString(); |
211 | } |
212 | |
213 | int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const |
214 | { |
215 | return standardTimeOffset(atMSecsSinceEpoch) + daylightTimeOffset(atMSecsSinceEpoch); |
216 | } |
217 | |
218 | int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const |
219 | { |
220 | Q_UNUSED(atMSecsSinceEpoch); |
221 | return invalidSeconds(); |
222 | } |
223 | |
224 | int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const |
225 | { |
226 | Q_UNUSED(atMSecsSinceEpoch); |
227 | return invalidSeconds(); |
228 | } |
229 | |
230 | bool QTimeZonePrivate::hasDaylightTime() const |
231 | { |
232 | return false; |
233 | } |
234 | |
235 | bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const |
236 | { |
237 | Q_UNUSED(atMSecsSinceEpoch); |
238 | return false; |
239 | } |
240 | |
241 | QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const |
242 | { |
243 | Q_UNUSED(forMSecsSinceEpoch); |
244 | return invalidData(); |
245 | } |
246 | |
247 | // Private only method for use by QDateTime to convert local msecs to epoch msecs |
248 | QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const |
249 | { |
250 | if (!hasDaylightTime()) // No DST means same offset for all local msecs |
251 | return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000); |
252 | |
253 | /* |
254 | We need a UTC time at which to ask for the offset, in order to be able to |
255 | add that offset to forLocalMSecs, to get the UTC time we |
256 | need. Fortunately, no time-zone offset is more than 14 hours; and DST |
257 | transitions happen (much) more than thirty-two hours apart. So sampling |
258 | offset sixteen hours each side gives us information we can be sure |
259 | brackets the correct time and at most one DST transition. |
260 | */ |
261 | const qint64 sixteenHoursInMSecs(16 * 3600 * 1000); |
262 | static_assert(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs |
263 | && sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs); |
264 | const qint64 recent = forLocalMSecs - sixteenHoursInMSecs; |
265 | const qint64 imminent = forLocalMSecs + sixteenHoursInMSecs; |
266 | /* |
267 | Offsets are Local - UTC, positive to the east of Greenwich, negative to |
268 | the west; DST offset always exceeds standard offset, when DST applies. |
269 | When we have offsets on either side of a transition, the lower one is |
270 | standard, the higher is DST. |
271 | |
272 | Non-DST transitions (jurisdictions changing time-zone and time-zones |
273 | changing their standard offset, typically) are described below as if they |
274 | were DST transitions (since these are more usual and familiar); the code |
275 | mostly concerns itself with offsets from UTC, described in terms of the |
276 | common case for changes in that. If there is no actual change in offset |
277 | (e.g. a DST transition cancelled by a standard offset change), this code |
278 | should handle it gracefully; without transitions, it'll see early == late |
279 | and take the easy path; with transitions, tran and nextTran get the |
280 | correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects |
281 | the right one. In all other cases, the transition changes offset and the |
282 | reasoning that applies to DST applies just the same. Aside from hinting, |
283 | the only thing that looks at DST-ness at all, other than inferred from |
284 | offset changes, is the case without transition data handling an invalid |
285 | time in the gap that a transition passed over. |
286 | |
287 | The handling of hint (see below) is apt to go wrong in non-DST |
288 | transitions. There isn't really a great deal we can hope to do about that |
289 | without adding yet more unreliable complexity to the heuristics in use for |
290 | already obscure corner-cases. |
291 | */ |
292 | |
293 | /* |
294 | The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller |
295 | thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so |
296 | should have been handled above; if it slips through, it's wrong but we |
297 | should probably treat it as standard anyway (never-DST means |
298 | always-standard, after all). If the hint turns out to be wrong, fall back |
299 | on trying the other possibility: which makes it harmless to treat -1 |
300 | (meaning unknown) as standard (i.e. try standard first, then try DST). In |
301 | practice, away from a transition, the only difference hint makes is to |
302 | which candidate we try first: if the hint is wrong (or unknown and |
303 | standard fails), we'll try the other candidate and it'll work. |
304 | |
305 | For the obscure (and invalid) case where forLocalMSecs falls in a |
306 | spring-forward's missing hour, a common case is that we started with a |
307 | date/time for which the hint was valid and adjusted it naively; for that |
308 | case, we should correct the adjustment by shunting across the transition |
309 | into where hint is wrong. So half-way through the gap, arrived at from |
310 | the DST side, should be read as an hour earlier, in standard time; but, if |
311 | arrived at from the standard side, should be read as an hour later, in |
312 | DST. (This shall be wrong in some cases; for example, when a country |
313 | changes its transition dates and changing a date/time by more than six |
314 | months lands it on a transition. However, these cases are even more |
315 | obscure than those where the heuristic is good.) |
316 | */ |
317 | |
318 | if (hasTransitions()) { |
319 | /* |
320 | We have transitions. |
321 | |
322 | Each transition gives the offsets to use until the next; so we need the |
323 | most recent transition before the time forLocalMSecs describes. If it |
324 | describes a time *in* a transition, we'll need both that transition and |
325 | the one before it. So find one transition that's probably after (and not |
326 | much before, otherwise) and another that's definitely before, then work |
327 | out which one to use. When both or neither work on forLocalMSecs, use |
328 | hint to disambiguate. |
329 | */ |
330 | |
331 | // Get a transition definitely before the local MSecs; usually all we need. |
332 | // Only around the transition times might we need another. |
333 | Data tran = previousTransition(recent); |
334 | Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable |
335 | forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch); |
336 | Data nextTran = nextTransition(tran.atMSecsSinceEpoch); |
337 | /* |
338 | Now walk those forward until they bracket forLocalMSecs with transitions. |
339 | |
340 | One of the transitions should then be telling us the right offset to use. |
341 | In a transition, we need the transition before it (to describe the run-up |
342 | to the transition) and the transition itself; so we need to stop when |
343 | nextTran is that transition. |
344 | */ |
345 | while (nextTran.atMSecsSinceEpoch != invalidMSecs() |
346 | && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) { |
347 | Data newTran = nextTransition(nextTran.atMSecsSinceEpoch); |
348 | if (newTran.atMSecsSinceEpoch == invalidMSecs() |
349 | || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) { |
350 | // Definitely not a relevant tansition: too far in the future. |
351 | break; |
352 | } |
353 | tran = nextTran; |
354 | nextTran = newTran; |
355 | } |
356 | |
357 | // Check we do *really* have transitions for this zone: |
358 | if (tran.atMSecsSinceEpoch != invalidMSecs()) { |
359 | |
360 | /* |
361 | So now tran is definitely before and nextTran is either after or only |
362 | slightly before. One is standard time; we interpret the other as DST |
363 | (although the transition might in fact by a change in standard offset). Our |
364 | hint tells us which of those to use (defaulting to standard if no hint): try |
365 | it first; if that fails, try the other; if both fail, life's tricky. |
366 | */ |
367 | Q_ASSERT(forLocalMSecs < 0 |
368 | || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch); |
369 | const qint64 nextStart = nextTran.atMSecsSinceEpoch; |
370 | // Work out the UTC values it might make sense to return: |
371 | nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000; |
372 | tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000; |
373 | |
374 | // If both or neither have zero DST, treat the one with lower offset as standard: |
375 | const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset |
376 | ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset; |
377 | // If that agrees with hint > 0, our first guess is to use nextTran; else tran. |
378 | const bool nextFirst = nextIsDst == (hint > 0) && nextStart != invalidMSecs(); |
379 | for (int i = 0; i < 2; i++) { |
380 | /* |
381 | On the first pass, the case we consider is what hint told us to expect |
382 | (except when hint was -1 and didn't actually tell us what to expect), |
383 | so it's likely right. We only get a second pass if the first failed, |
384 | by which time the second case, that we're trying, is likely right. |
385 | */ |
386 | if (nextFirst ? i == 0 : i) { |
387 | Q_ASSERT(nextStart != invalidMSecs()); |
388 | if (nextStart <= nextTran.atMSecsSinceEpoch) |
389 | return nextTran; |
390 | } else { |
391 | // If next is invalid, nextFirst is false, to route us here first: |
392 | if (nextStart == invalidMSecs() || nextStart > tran.atMSecsSinceEpoch) |
393 | return tran; |
394 | } |
395 | } |
396 | |
397 | /* |
398 | Neither is valid (e.g. in a spring-forward's gap) and |
399 | nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so |
400 | 0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch |
401 | = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000 |
402 | */ |
403 | int dstStep = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000; |
404 | Q_ASSERT(dstStep > 0); // How else could we get here ? |
405 | if (nextFirst) { // hint thought we needed nextTran, so use tran |
406 | tran.atMSecsSinceEpoch -= dstStep; |
407 | return tran; |
408 | } |
409 | nextTran.atMSecsSinceEpoch += dstStep; |
410 | return nextTran; |
411 | } |
412 | // System has transitions but not for this zone. |
413 | // Try falling back to offsetFromUtc |
414 | } |
415 | |
416 | /* Bracket and refine to discover offset. */ |
417 | qint64 utcEpochMSecs; |
418 | |
419 | int early = offsetFromUtc(recent); |
420 | int late = offsetFromUtc(imminent); |
421 | if (early == late) { // > 99% of the time |
422 | utcEpochMSecs = forLocalMSecs - early * 1000; |
423 | } else { |
424 | // Close to a DST transition: early > late is near a fall-back, |
425 | // early < late is near a spring-forward. |
426 | const int offsetInDst = qMax(early, late); |
427 | const int offsetInStd = qMin(early, late); |
428 | // Candidate values for utcEpochMSecs (if forLocalMSecs is valid): |
429 | const qint64 forDst = forLocalMSecs - offsetInDst * 1000; |
430 | const qint64 forStd = forLocalMSecs - offsetInStd * 1000; |
431 | // Best guess at the answer: |
432 | const qint64 hinted = hint > 0 ? forDst : forStd; |
433 | if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) { |
434 | utcEpochMSecs = hinted; |
435 | } else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) { |
436 | utcEpochMSecs = forDst; |
437 | } else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) { |
438 | utcEpochMSecs = forStd; |
439 | } else { |
440 | // Invalid forLocalMSecs: in spring-forward gap. |
441 | const int dstStep = daylightTimeOffset(early < late ? imminent : recent) * 1000; |
442 | Q_ASSERT(dstStep); // There can't be a transition without it ! |
443 | utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep; |
444 | } |
445 | } |
446 | |
447 | return data(utcEpochMSecs); |
448 | } |
449 | |
450 | bool QTimeZonePrivate::hasTransitions() const |
451 | { |
452 | return false; |
453 | } |
454 | |
455 | QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const |
456 | { |
457 | Q_UNUSED(afterMSecsSinceEpoch); |
458 | return invalidData(); |
459 | } |
460 | |
461 | QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const |
462 | { |
463 | Q_UNUSED(beforeMSecsSinceEpoch); |
464 | return invalidData(); |
465 | } |
466 | |
467 | QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch, |
468 | qint64 toMSecsSinceEpoch) const |
469 | { |
470 | DataList list; |
471 | if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) { |
472 | // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec |
473 | Data next = nextTransition(fromMSecsSinceEpoch - 1); |
474 | while (next.atMSecsSinceEpoch != invalidMSecs() |
475 | && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) { |
476 | list.append(next); |
477 | next = nextTransition(next.atMSecsSinceEpoch); |
478 | } |
479 | } |
480 | return list; |
481 | } |
482 | |
483 | QByteArray QTimeZonePrivate::systemTimeZoneId() const |
484 | { |
485 | return QByteArray(); |
486 | } |
487 | |
488 | bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const |
489 | { |
490 | // Fall-back implementation, can be made faster in subclasses |
491 | const QList<QByteArray> tzIds = availableTimeZoneIds(); |
492 | return std::binary_search(tzIds.begin(), tzIds.end(), ianaId); |
493 | } |
494 | |
495 | QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const |
496 | { |
497 | return QList<QByteArray>(); |
498 | } |
499 | |
500 | QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const |
501 | { |
502 | // Default fall-back mode, use the zoneTable to find Region of know Zones |
503 | QList<QByteArray> regions; |
504 | |
505 | // First get all Zones in the Zones table belonging to the Region |
506 | for (int i = 0; i < zoneDataTableSize; ++i) { |
507 | if (zoneData(i)->country == country) |
508 | regions += ianaId(zoneData(i)).split(' '); |
509 | } |
510 | |
511 | std::sort(regions.begin(), regions.end()); |
512 | regions.erase(std::unique(regions.begin(), regions.end()), regions.end()); |
513 | |
514 | // Then select just those that are available |
515 | const QList<QByteArray> all = availableTimeZoneIds(); |
516 | QList<QByteArray> result; |
517 | result.reserve(qMin(all.size(), regions.size())); |
518 | std::set_intersection(all.begin(), all.end(), regions.cbegin(), regions.cend(), |
519 | std::back_inserter(result)); |
520 | return result; |
521 | } |
522 | |
523 | QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const |
524 | { |
525 | // Default fall-back mode, use the zoneTable to find Offset of know Zones |
526 | QList<QByteArray> offsets; |
527 | // First get all Zones in the table using the Offset |
528 | for (int i = 0; i < windowsDataTableSize; ++i) { |
529 | const QWindowsData *winData = windowsData(i); |
530 | if (winData->offsetFromUtc == offsetFromUtc) { |
531 | for (int j = 0; j < zoneDataTableSize; ++j) { |
532 | const QZoneData *data = zoneData(j); |
533 | if (data->windowsIdKey == winData->windowsIdKey) |
534 | offsets += ianaId(data).split(' '); |
535 | } |
536 | } |
537 | } |
538 | |
539 | std::sort(offsets.begin(), offsets.end()); |
540 | offsets.erase(std::unique(offsets.begin(), offsets.end()), offsets.end()); |
541 | |
542 | // Then select just those that are available |
543 | const QList<QByteArray> all = availableTimeZoneIds(); |
544 | QList<QByteArray> result; |
545 | result.reserve(qMin(all.size(), offsets.size())); |
546 | std::set_intersection(all.begin(), all.end(), offsets.cbegin(), offsets.cend(), |
547 | std::back_inserter(result)); |
548 | return result; |
549 | } |
550 | |
551 | #ifndef QT_NO_DATASTREAM |
552 | void QTimeZonePrivate::serialize(QDataStream &ds) const |
553 | { |
554 | ds << QString::fromUtf8(m_id); |
555 | } |
556 | #endif // QT_NO_DATASTREAM |
557 | |
558 | // Static Utility Methods |
559 | |
560 | QTimeZonePrivate::Data QTimeZonePrivate::invalidData() |
561 | { |
562 | Data data; |
563 | data.atMSecsSinceEpoch = invalidMSecs(); |
564 | data.offsetFromUtc = invalidSeconds(); |
565 | data.standardTimeOffset = invalidSeconds(); |
566 | data.daylightTimeOffset = invalidSeconds(); |
567 | return data; |
568 | } |
569 | |
570 | QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData() |
571 | { |
572 | QTimeZone::OffsetData offsetData; |
573 | offsetData.atUtc = QDateTime(); |
574 | offsetData.offsetFromUtc = invalidSeconds(); |
575 | offsetData.standardTimeOffset = invalidSeconds(); |
576 | offsetData.daylightTimeOffset = invalidSeconds(); |
577 | return offsetData; |
578 | } |
579 | |
580 | QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Data &data) |
581 | { |
582 | QTimeZone::OffsetData offsetData = invalidOffsetData(); |
583 | if (data.atMSecsSinceEpoch != invalidMSecs()) { |
584 | offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, Qt::UTC); |
585 | offsetData.offsetFromUtc = data.offsetFromUtc; |
586 | offsetData.standardTimeOffset = data.standardTimeOffset; |
587 | offsetData.daylightTimeOffset = data.daylightTimeOffset; |
588 | offsetData.abbreviation = data.abbreviation; |
589 | } |
590 | return offsetData; |
591 | } |
592 | |
593 | // Is the format of the ID valid ? |
594 | bool QTimeZonePrivate::isValidId(const QByteArray &ianaId) |
595 | { |
596 | /* |
597 | Main rules for defining TZ/IANA names as per ftp://ftp.iana.org/tz/code/Theory |
598 | 1. Use only valid POSIX file name components |
599 | 2. Within a file name component, use only ASCII letters, `.', `-' and `_'. |
600 | 3. Do not use digits (except in a [+-]\d+ suffix, when used). |
601 | 4. A file name component must not exceed 14 characters or start with `-' |
602 | However, the rules are really guidelines - a later one says |
603 | - Do not change established names if they only marginally violate the |
604 | above rules. |
605 | We may, therefore, need to be a bit slack in our check here, if we hit |
606 | legitimate exceptions in real time-zone databases. |
607 | |
608 | In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid |
609 | so we need to accept digits, ':', and '+'; aliases typically have the form |
610 | of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX |
611 | suffix starts with an offset (as in GMT+7) and may continue with another |
612 | name (as in EST5EDT, giving the DST name of the zone); a further offset is |
613 | allowed (for DST). The ("hard to describe and [...] error-prone in |
614 | practice") POSIX form even allows a suffix giving the dates (and |
615 | optionally times) of the annual DST transitions. Hopefully, no TZ aliases |
616 | go that far, but we at least need to accept an offset and (single |
617 | fragment) DST-name. |
618 | |
619 | But for the legacy complications, the following would be preferable if |
620 | QRegExp would work on QByteArrays directly: |
621 | const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}" |
622 | "(?:/[a-z+._][a-z+._-]{,13})*" |
623 | // Optional suffix: |
624 | "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset |
625 | // one name fragment (DST): |
626 | "(?:[a-z+._][a-z+._-]{,13})?)"), |
627 | Qt::CaseInsensitive); |
628 | return rx.exactMatch(ianaId); |
629 | */ |
630 | |
631 | // Somewhat slack hand-rolled version: |
632 | const int MinSectionLength = 1; |
633 | #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) |
634 | // Android has its own naming of zones. |
635 | // "Canada/East-Saskatchewan" has a 17-character second component. |
636 | const int MaxSectionLength = 17; |
637 | #else |
638 | const int MaxSectionLength = 14; |
639 | #endif |
640 | int sectionLength = 0; |
641 | for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) { |
642 | const char ch = *it; |
643 | if (ch == '/') { |
644 | if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength) |
645 | return false; // violates (4) |
646 | sectionLength = -1; |
647 | } else if (ch == '-') { |
648 | if (sectionLength == 0) |
649 | return false; // violates (4) |
650 | } else if (!(ch >= 'a' && ch <= 'z') |
651 | && !(ch >= 'A' && ch <= 'Z') |
652 | && !(ch == '_') |
653 | && !(ch == '.') |
654 | // Should ideally check these only happen as an offset: |
655 | && !(ch >= '0' && ch <= '9') |
656 | && !(ch == '+') |
657 | && !(ch == ':')) { |
658 | return false; // violates (2) |
659 | } |
660 | } |
661 | if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength) |
662 | return false; // violates (4) |
663 | return true; |
664 | } |
665 | |
666 | QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc) |
667 | { |
668 | const int mins = offsetFromUtc / 60; |
669 | return QString::fromUtf8("UTC%1%2:%3" ).arg(mins >= 0 ? QLatin1Char('+') : QLatin1Char('-')) |
670 | .arg(qAbs(mins) / 60, 2, 10, QLatin1Char('0')) |
671 | .arg(qAbs(mins) % 60, 2, 10, QLatin1Char('0')); |
672 | } |
673 | |
674 | QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id) |
675 | { |
676 | for (int i = 0; i < zoneDataTableSize; ++i) { |
677 | const QZoneData *data = zoneData(i); |
678 | if (ianaId(data).split(' ').contains(id)) |
679 | return toWindowsIdLiteral(data->windowsIdKey); |
680 | } |
681 | return QByteArray(); |
682 | } |
683 | |
684 | QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId) |
685 | { |
686 | const quint16 windowsIdKey = toWindowsIdKey(windowsId); |
687 | for (int i = 0; i < windowsDataTableSize; ++i) { |
688 | const QWindowsData *data = windowsData(i); |
689 | if (data->windowsIdKey == windowsIdKey) |
690 | return ianaId(data); |
691 | } |
692 | return QByteArray(); |
693 | } |
694 | |
695 | QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId, |
696 | QLocale::Country country) |
697 | { |
698 | const QList<QByteArray> list = windowsIdToIanaIds(windowsId, country); |
699 | if (list.count() > 0) |
700 | return list.first(); |
701 | else |
702 | return QByteArray(); |
703 | } |
704 | |
705 | QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId) |
706 | { |
707 | const quint16 windowsIdKey = toWindowsIdKey(windowsId); |
708 | QList<QByteArray> list; |
709 | |
710 | for (int i = 0; i < zoneDataTableSize; ++i) { |
711 | const QZoneData *data = zoneData(i); |
712 | if (data->windowsIdKey == windowsIdKey) |
713 | list << ianaId(data).split(' '); |
714 | } |
715 | |
716 | // Return the full list in alpha order |
717 | std::sort(list.begin(), list.end()); |
718 | return list; |
719 | } |
720 | |
721 | QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId, |
722 | QLocale::Country country) |
723 | { |
724 | const quint16 windowsIdKey = toWindowsIdKey(windowsId); |
725 | for (int i = 0; i < zoneDataTableSize; ++i) { |
726 | const QZoneData *data = zoneData(i); |
727 | // Return the region matches in preference order |
728 | if (data->windowsIdKey == windowsIdKey && data->country == (quint16) country) |
729 | return ianaId(data).split(' '); |
730 | } |
731 | |
732 | return QList<QByteArray>(); |
733 | } |
734 | |
735 | // Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly |
736 | template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone() |
737 | { |
738 | return d->clone(); |
739 | } |
740 | |
741 | /* |
742 | UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used, |
743 | or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc. |
744 | */ |
745 | |
746 | // Create default UTC time zone |
747 | QUtcTimeZonePrivate::QUtcTimeZonePrivate() |
748 | { |
749 | const QString name = utcQString(); |
750 | init(utcQByteArray(), 0, name, name, QLocale::AnyCountry, name); |
751 | } |
752 | |
753 | // Create a named UTC time zone |
754 | QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id) |
755 | { |
756 | // Look for the name in the UTC list, if found set the values |
757 | for (int i = 0; i < utcDataTableSize; ++i) { |
758 | const QUtcData *data = utcData(i); |
759 | const QByteArray uid = utcId(data); |
760 | if (uid == id) { |
761 | QString name = QString::fromUtf8(id); |
762 | init(id, data->offsetFromUtc, name, name, QLocale::AnyCountry, name); |
763 | break; |
764 | } |
765 | } |
766 | } |
767 | |
768 | qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id) |
769 | { |
770 | // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds. |
771 | // Assumption: id has already been tried as a CLDR UTC offset ID (notably |
772 | // including plain "UTC" itself) and a system offset ID; it's neither. |
773 | if (!id.startsWith("UTC" ) || id.size() < 5) |
774 | return invalidSeconds(); // Doesn't match |
775 | const char signChar = id.at(3); |
776 | if (signChar != '-' && signChar != '+') |
777 | return invalidSeconds(); // No sign |
778 | const int sign = signChar == '-' ? -1 : 1; |
779 | |
780 | const auto offsets = id.mid(4).split(':'); |
781 | if (offsets.isEmpty() || offsets.size() > 3) |
782 | return invalidSeconds(); // No numbers, or too many. |
783 | |
784 | qint32 seconds = 0; |
785 | int prior = 0; // Number of fields parsed thus far |
786 | for (const auto &offset : offsets) { |
787 | bool ok = false; |
788 | unsigned short field = offset.toUShort(&ok); |
789 | // Bound hour above at 24, minutes and seconds at 60: |
790 | if (!ok || field >= (prior ? 60 : 24)) |
791 | return invalidSeconds(); |
792 | seconds = seconds * 60 + field; |
793 | ++prior; |
794 | } |
795 | while (prior++ < 3) |
796 | seconds *= 60; |
797 | |
798 | return seconds * sign; |
799 | } |
800 | |
801 | // Create offset from UTC |
802 | QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds) |
803 | { |
804 | QString utcId; |
805 | |
806 | if (offsetSeconds == 0) |
807 | utcId = utcQString(); |
808 | else |
809 | utcId = isoOffsetFormat(offsetSeconds); |
810 | |
811 | init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyCountry, utcId); |
812 | } |
813 | |
814 | QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds, |
815 | const QString &name, const QString &abbreviation, |
816 | QLocale::Country country, const QString &) |
817 | { |
818 | init(zoneId, offsetSeconds, name, abbreviation, country, comment); |
819 | } |
820 | |
821 | QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other) |
822 | : QTimeZonePrivate(other), m_name(other.m_name), |
823 | m_abbreviation(other.m_abbreviation), |
824 | m_comment(other.m_comment), |
825 | m_country(other.m_country), |
826 | m_offsetFromUtc(other.m_offsetFromUtc) |
827 | { |
828 | } |
829 | |
830 | QUtcTimeZonePrivate::~QUtcTimeZonePrivate() |
831 | { |
832 | } |
833 | |
834 | QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const |
835 | { |
836 | return new QUtcTimeZonePrivate(*this); |
837 | } |
838 | |
839 | QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const |
840 | { |
841 | Data d; |
842 | d.abbreviation = m_abbreviation; |
843 | d.atMSecsSinceEpoch = forMSecsSinceEpoch; |
844 | d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc; |
845 | d.daylightTimeOffset = 0; |
846 | return d; |
847 | } |
848 | |
849 | void QUtcTimeZonePrivate::init(const QByteArray &zoneId) |
850 | { |
851 | m_id = zoneId; |
852 | } |
853 | |
854 | void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name, |
855 | const QString &abbreviation, QLocale::Country country, |
856 | const QString &) |
857 | { |
858 | m_id = zoneId; |
859 | m_offsetFromUtc = offsetSeconds; |
860 | m_name = name; |
861 | m_abbreviation = abbreviation; |
862 | m_country = country; |
863 | m_comment = comment; |
864 | } |
865 | |
866 | QLocale::Country QUtcTimeZonePrivate::country() const |
867 | { |
868 | return m_country; |
869 | } |
870 | |
871 | QString QUtcTimeZonePrivate::() const |
872 | { |
873 | return m_comment; |
874 | } |
875 | |
876 | QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType, |
877 | QTimeZone::NameType nameType, |
878 | const QLocale &locale) const |
879 | { |
880 | Q_UNUSED(timeType); |
881 | Q_UNUSED(locale); |
882 | if (nameType == QTimeZone::ShortName) |
883 | return m_abbreviation; |
884 | else if (nameType == QTimeZone::OffsetName) |
885 | return isoOffsetFormat(m_offsetFromUtc); |
886 | return m_name; |
887 | } |
888 | |
889 | QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const |
890 | { |
891 | Q_UNUSED(atMSecsSinceEpoch); |
892 | return m_abbreviation; |
893 | } |
894 | |
895 | qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const |
896 | { |
897 | Q_UNUSED(atMSecsSinceEpoch); |
898 | return m_offsetFromUtc; |
899 | } |
900 | |
901 | qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const |
902 | { |
903 | Q_UNUSED(atMSecsSinceEpoch); |
904 | return 0; |
905 | } |
906 | |
907 | QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const |
908 | { |
909 | return utcQByteArray(); |
910 | } |
911 | |
912 | bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const |
913 | { |
914 | // Only the zone IDs supplied by CLDR and recognized by constructor. |
915 | for (int i = 0; i < utcDataTableSize; ++i) { |
916 | const QUtcData *data = utcData(i); |
917 | if (utcId(data) == ianaId) |
918 | return true; |
919 | } |
920 | // But see offsetFromUtcString(), which lets us accept some "unavailable" IDs. |
921 | return false; |
922 | } |
923 | |
924 | QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const |
925 | { |
926 | // Only the zone IDs supplied by CLDR and recognized by constructor. |
927 | QList<QByteArray> result; |
928 | result.reserve(utcDataTableSize); |
929 | for (int i = 0; i < utcDataTableSize; ++i) |
930 | result << utcId(utcData(i)); |
931 | // Not guaranteed to be sorted, so sort: |
932 | std::sort(result.begin(), result.end()); |
933 | // ### assuming no duplicates |
934 | return result; |
935 | } |
936 | |
937 | QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const |
938 | { |
939 | // If AnyCountry then is request for all non-region offset codes |
940 | if (country == QLocale::AnyCountry) |
941 | return availableTimeZoneIds(); |
942 | return QList<QByteArray>(); |
943 | } |
944 | |
945 | QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const |
946 | { |
947 | // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00 |
948 | // and UTC-00:00 all have the same offset.) |
949 | QList<QByteArray> result; |
950 | for (int i = 0; i < utcDataTableSize; ++i) { |
951 | const QUtcData *data = utcData(i); |
952 | if (data->offsetFromUtc == offsetSeconds) |
953 | result << utcId(data); |
954 | } |
955 | // Not guaranteed to be sorted, so sort: |
956 | std::sort(result.begin(), result.end()); |
957 | // ### assuming no duplicates |
958 | return result; |
959 | } |
960 | |
961 | #ifndef QT_NO_DATASTREAM |
962 | void QUtcTimeZonePrivate::serialize(QDataStream &ds) const |
963 | { |
964 | ds << QStringLiteral("OffsetFromUtc" ) << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name |
965 | << m_abbreviation << (qint32) m_country << m_comment; |
966 | } |
967 | #endif // QT_NO_DATASTREAM |
968 | |
969 | QT_END_NAMESPACE |
970 | |