| 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 | |