Coverage Report

Created: 2024-09-08 06:48

/src/qtbase/src/corelib/time/qtimezoneprivate_tz.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (C) 2022 The Qt Company Ltd.
2
// Copyright (C) 2019 Crimson AS <info@crimson.no>
3
// Copyright (C) 2013 John Layt <jlayt@kde.org>
4
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6
#include "qtimezone.h"
7
#include "qtimezoneprivate_p.h"
8
#include "private/qlocale_tools_p.h"
9
#include "private/qlocking_p.h"
10
11
#include <QtCore/QDataStream>
12
#include <QtCore/QDateTime>
13
#include <QtCore/QDirListing>
14
#include <QtCore/QDir>
15
#include <QtCore/QFile>
16
#include <QtCore/QCache>
17
#include <QtCore/QMap>
18
#include <QtCore/QMutex>
19
20
#include <qdebug.h>
21
#include <qplatformdefs.h>
22
23
#include <algorithm>
24
#include <memory>
25
26
#include <errno.h>
27
#include <limits.h>
28
#ifndef Q_OS_INTEGRITY
29
#include <sys/param.h> // to use MAXSYMLINKS constant
30
#endif
31
#include <unistd.h>    // to use _SC_SYMLOOP_MAX constant
32
33
QT_BEGIN_NAMESPACE
34
35
using namespace Qt::StringLiterals;
36
37
/*
38
    Private
39
40
    tz file implementation
41
*/
42
43
struct QTzTimeZone {
44
    QLocale::Territory territory = QLocale::AnyTerritory;
45
    QByteArray comment;
46
};
47
48
// Define as a type as Q_GLOBAL_STATIC doesn't like it
49
typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash;
50
51
static bool isTzFile(const QString &name);
52
53
// Open a named file under the zone info directory:
54
static bool openZoneInfo(const QString &name, QFile *file)
55
0
{
56
    // At least on Linux / glibc (see man 3 tzset), $TZDIR overrides the system
57
    // default location for zone info:
58
0
    const QString tzdir = qEnvironmentVariable("TZDIR");
59
0
    if (!tzdir.isEmpty()) {
60
0
        file->setFileName(QDir(tzdir).filePath(name));
61
0
        if (file->open(QIODevice::ReadOnly))
62
0
            return true;
63
0
    }
64
    // Try modern system path first:
65
0
    constexpr auto zoneShare = "/usr/share/zoneinfo/"_L1;
66
0
    if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) {
67
0
        file->setFileName(zoneShare + name);
68
0
        if (file->open(QIODevice::ReadOnly))
69
0
            return true;
70
0
    }
71
    // Fall back to legacy system path:
72
0
    constexpr auto zoneLib = "/usr/lib/zoneinfo/"_L1;
73
0
    if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) {
74
0
        file->setFileName(zoneShare + name);
75
0
        if (file->open(QIODevice::ReadOnly))
76
0
            return true;
77
0
    }
78
0
    return false;
79
0
}
80
81
// Parse zone.tab table for territory information, read directories to ensure we
82
// find all installed zones (many are omitted from zone.tab; even more from
83
// zone1970.tab).
84
static QTzTimeZoneHash loadTzTimeZones()
85
0
{
86
0
    QFile tzif;
87
0
    if (!openZoneInfo("zone.tab"_L1, &tzif))
88
0
        return QTzTimeZoneHash();
89
90
0
    QTzTimeZoneHash zonesHash;
91
0
    while (!tzif.atEnd()) {
92
0
        const QByteArray line = tzif.readLine().trimmed();
93
0
        if (line.isEmpty() || line.at(0) == '#') // Ignore empty or comment
94
0
            continue;
95
        // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments
96
0
        QByteArrayView text(line);
97
0
        int cut = text.indexOf('\t');
98
0
        if (Q_LIKELY(cut > 0)) {
99
0
            QTzTimeZone zone;
100
            // TODO: QLocale & friends could do this look-up without UTF8-conversion:
101
0
            zone.territory = QLocalePrivate::codeToTerritory(QString::fromUtf8(text.first(cut)));
102
0
            text = text.sliced(cut + 1);
103
0
            cut = text.indexOf('\t');
104
0
            if (Q_LIKELY(cut >= 0)) { // Skip over Coordinates, read ID and comment
105
0
                text = text.sliced(cut + 1);
106
0
                cut = text.indexOf('\t'); // < 0 if line has no comment
107
0
                if (Q_LIKELY(cut)) {
108
0
                    const QByteArray id = (cut > 0 ? text.first(cut) : text).toByteArray();
109
0
                    if (cut > 0)
110
0
                        zone.comment = text.sliced(cut + 1).toByteArray();
111
0
                    zonesHash.insert(id, zone);
112
0
                }
113
0
            }
114
0
        }
115
0
    }
116
117
0
    QString path = tzif.fileName();
118
0
    const qsizetype cut = path.lastIndexOf(u'/');
119
0
    Q_ASSERT(cut > 0);
120
0
    path.truncate(cut + 1);
121
0
    const qsizetype prefixLen = path.size();
122
0
    for (const auto &info : QDirListing(path, QDirListing::IteratorFlag::Recursive)) {
123
0
        if (!(info.isFile() || info.isSymLink()))
124
0
            continue;
125
0
        const QString infoAbsolutePath = info.absoluteFilePath();
126
0
        const QString name = infoAbsolutePath.sliced(prefixLen);
127
        // Two sub-directories containing (more or less) copies of the zoneinfo tree.
128
0
        if (info.isDir() ? name == "posix"_L1 || name == "right"_L1
129
0
            : name.startsWith("posix/"_L1) || name.startsWith("right/"_L1)) {
130
0
            continue;
131
0
        }
132
        // We could filter out *.* and leapseconds instead of doing the
133
        // isTzFile() check; in practice current (2023) zoneinfo/ contains only
134
        // actual zone files and matches to that filter.
135
0
        const QByteArray id = QFile::encodeName(name);
136
0
        if (!zonesHash.contains(id) && isTzFile(infoAbsolutePath))
137
0
            zonesHash.insert(id, QTzTimeZone());
138
0
    }
139
0
    return zonesHash;
140
0
}
141
142
// Hash of available system tz files as loaded by loadTzTimeZones()
143
Q_GLOBAL_STATIC(const QTzTimeZoneHash, tzZones, loadTzTimeZones());
144
145
/*
146
    The following is copied and modified from tzfile.h which is in the public domain.
147
    Copied as no compatibility guarantee and is never system installed.
148
    See https://github.com/eggert/tz/blob/master/tzfile.h
149
*/
150
151
0
#define TZ_MAGIC      "TZif"
152
0
#define TZ_MAX_TIMES  1200
153
0
#define TZ_MAX_TYPES   256  // Limited by what (unsigned char)'s can hold
154
0
#define TZ_MAX_CHARS    50  // Maximum number of abbreviation characters
155
0
#define TZ_MAX_LEAPS    50  // Maximum number of leap second corrections
156
157
struct QTzHeader {
158
    char       tzh_magic[4];        // TZ_MAGIC
159
    char       tzh_version;         // '\0' or '2' as of 2005
160
    char       tzh_reserved[15];    // reserved--must be zero
161
    quint32    tzh_ttisgmtcnt;      // number of trans. time flags
162
    quint32    tzh_ttisstdcnt;      // number of trans. time flags
163
    quint32    tzh_leapcnt;         // number of leap seconds
164
    quint32    tzh_timecnt;         // number of transition times
165
    quint32    tzh_typecnt;         // number of local time types
166
    quint32    tzh_charcnt;         // number of abbr. chars
167
};
168
169
struct QTzTransition {
170
    qint64 tz_time;     // Transition time
171
    quint8 tz_typeind;  // Type Index
172
};
173
Q_DECLARE_TYPEINFO(QTzTransition, Q_PRIMITIVE_TYPE);
174
175
struct QTzType {
176
    int tz_gmtoff;  // UTC offset in seconds
177
    bool   tz_isdst;   // Is DST
178
    quint8 tz_abbrind; // abbreviation list index
179
};
180
Q_DECLARE_TYPEINFO(QTzType, Q_PRIMITIVE_TYPE);
181
182
static bool isTzFile(const QString &name)
183
0
{
184
0
    QFile file(name);
185
0
    return file.open(QFile::ReadOnly) && file.read(strlen(TZ_MAGIC)) == TZ_MAGIC;
186
0
}
187
188
// TZ File parsing
189
190
static QTzHeader parseTzHeader(QDataStream &ds, bool *ok)
191
0
{
192
0
    QTzHeader hdr;
193
0
    quint8 ch;
194
0
    *ok = false;
195
196
    // Parse Magic, 4 bytes
197
0
    ds.readRawData(hdr.tzh_magic, 4);
198
199
0
    if (memcmp(hdr.tzh_magic, TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok)
200
0
        return hdr;
201
202
    // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2', since 2013 a '3'
203
0
    ds >> ch;
204
0
    hdr.tzh_version = ch;
205
0
    if (ds.status() != QDataStream::Ok
206
0
        || (hdr.tzh_version != '2' && hdr.tzh_version != '\0' && hdr.tzh_version != '3')) {
207
0
        return hdr;
208
0
    }
209
210
    // Parse reserved space, 15 bytes
211
0
    ds.readRawData(hdr.tzh_reserved, 15);
212
0
    if (ds.status() != QDataStream::Ok)
213
0
        return hdr;
214
215
    // Parse rest of header, 6 x 4-byte transition counts
216
0
    ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt
217
0
       >> hdr.tzh_typecnt >> hdr.tzh_charcnt;
218
219
    // Check defined maximums
220
0
    if (ds.status() != QDataStream::Ok
221
0
        || hdr.tzh_timecnt > TZ_MAX_TIMES
222
0
        || hdr.tzh_typecnt > TZ_MAX_TYPES
223
0
        || hdr.tzh_charcnt > TZ_MAX_CHARS
224
0
        || hdr.tzh_leapcnt > TZ_MAX_LEAPS
225
0
        || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt
226
0
        || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) {
227
0
        return hdr;
228
0
    }
229
230
0
    *ok = true;
231
0
    return hdr;
232
0
}
233
234
static QList<QTzTransition> parseTzTransitions(QDataStream &ds, int tzh_timecnt, bool longTran)
235
0
{
236
0
    QList<QTzTransition> transitions(tzh_timecnt);
237
238
0
    if (longTran) {
239
        // Parse tzh_timecnt x 8-byte transition times
240
0
        for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
241
0
            ds >> transitions[i].tz_time;
242
0
            if (ds.status() != QDataStream::Ok)
243
0
                transitions.resize(i);
244
0
        }
245
0
    } else {
246
        // Parse tzh_timecnt x 4-byte transition times
247
0
        qint32 val;
248
0
        for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
249
0
            ds >> val;
250
0
            transitions[i].tz_time = val;
251
0
            if (ds.status() != QDataStream::Ok)
252
0
                transitions.resize(i);
253
0
        }
254
0
    }
255
256
    // Parse tzh_timecnt x 1-byte transition type index
257
0
    for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
258
0
        quint8 typeind;
259
0
        ds >> typeind;
260
0
        if (ds.status() == QDataStream::Ok)
261
0
            transitions[i].tz_typeind = typeind;
262
0
    }
263
264
0
    return transitions;
265
0
}
266
267
static QList<QTzType> parseTzTypes(QDataStream &ds, int tzh_typecnt)
268
0
{
269
0
    QList<QTzType> types(tzh_typecnt);
270
271
    // Parse tzh_typecnt x transition types
272
0
    for (int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
273
0
        QTzType &type = types[i];
274
        // Parse UTC Offset, 4 bytes
275
0
        ds >> type.tz_gmtoff;
276
        // Parse Is DST flag, 1 byte
277
0
        if (ds.status() == QDataStream::Ok)
278
0
            ds >> type.tz_isdst;
279
        // Parse Abbreviation Array Index, 1 byte
280
0
        if (ds.status() == QDataStream::Ok)
281
0
            ds >> type.tz_abbrind;
282
0
        if (ds.status() != QDataStream::Ok)
283
0
            types.resize(i);
284
0
    }
285
286
0
    return types;
287
0
}
288
289
static QMap<int, QByteArray> parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QList<QTzType> &types)
290
0
{
291
    // Parse the abbreviation list which is tzh_charcnt long with '\0' separated strings. The
292
    // QTzType.tz_abbrind index points to the first char of the abbreviation in the array, not the
293
    // occurrence in the list. It can also point to a partial string so we need to use the actual typeList
294
    // index values when parsing.  By using a map with tz_abbrind as ordered key we get both index
295
    // methods in one data structure and can convert the types afterwards.
296
0
    QMap<int, QByteArray> map;
297
0
    quint8 ch;
298
0
    QByteArray input;
299
    // First parse the full abbrev string
300
0
    for (int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
301
0
        ds >> ch;
302
0
        if (ds.status() == QDataStream::Ok)
303
0
            input.append(char(ch));
304
0
        else
305
0
            return map;
306
0
    }
307
    // Then extract all the substrings pointed to by types
308
0
    for (const QTzType &type : types) {
309
0
        QByteArray abbrev;
310
0
        for (int i = type.tz_abbrind; input.at(i) != '\0'; ++i)
311
0
            abbrev.append(input.at(i));
312
        // Have reached end of an abbreviation, so add to map
313
0
        map[type.tz_abbrind] = abbrev;
314
0
    }
315
0
    return map;
316
0
}
317
318
static void parseTzLeapSeconds(QDataStream &ds, int tzh_leapcnt, bool longTran)
319
0
{
320
    // Parse tzh_leapcnt x pairs of leap seconds
321
    // We don't use leap seconds, so only read and don't store
322
0
    qint32 val;
323
0
    if (longTran) {
324
        // v2 file format, each entry is 12 bytes long
325
0
        qint64 time;
326
0
        for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
327
            // Parse Leap Occurrence Time, 8 bytes
328
0
            ds >> time;
329
            // Parse Leap Seconds To Apply, 4 bytes
330
0
            if (ds.status() == QDataStream::Ok)
331
0
                ds >> val;
332
0
        }
333
0
    } else {
334
        // v0 file format, each entry is 8 bytes long
335
0
        for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
336
            // Parse Leap Occurrence Time, 4 bytes
337
0
            ds >> val;
338
            // Parse Leap Seconds To Apply, 4 bytes
339
0
            if (ds.status() == QDataStream::Ok)
340
0
                ds >> val;
341
0
        }
342
0
    }
343
0
}
344
345
static QList<QTzType> parseTzIndicators(QDataStream &ds, const QList<QTzType> &types, int tzh_ttisstdcnt,
346
                                        int tzh_ttisgmtcnt)
347
0
{
348
0
    QList<QTzType> result = types;
349
0
    bool temp;
350
    /*
351
      Scan and discard indicators.
352
353
      These indicators are only of use (by the date program) when "handling
354
      POSIX-style time zone environment variables".  The flags here say whether
355
      the *specification* of the zone gave the time in UTC, local standard time
356
      or local wall time; but whatever was specified has been digested for us,
357
      already, by the zone-info compiler (zic), so that the tz_time values read
358
      from the file (by parseTzTransitions) are all in UTC.
359
     */
360
361
    // Scan tzh_ttisstdcnt x 1-byte standard/wall indicators
362
0
    for (int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
363
0
        ds >> temp;
364
365
    // Scan tzh_ttisgmtcnt x 1-byte UTC/local indicators
366
0
    for (int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
367
0
        ds >> temp;
368
369
0
    return result;
370
0
}
371
372
static QByteArray parseTzPosixRule(QDataStream &ds)
373
0
{
374
    // Parse POSIX rule, variable length '\n' enclosed
375
0
    QByteArray rule;
376
377
0
    quint8 ch;
378
0
    ds >> ch;
379
0
    if (ch != '\n' || ds.status() != QDataStream::Ok)
380
0
        return rule;
381
0
    ds >> ch;
382
0
    while (ch != '\n' && ds.status() == QDataStream::Ok) {
383
0
        rule.append((char)ch);
384
0
        ds >> ch;
385
0
    }
386
387
0
    return rule;
388
0
}
389
390
static QDate calculateDowDate(int year, int month, int dayOfWeek, int week)
391
0
{
392
0
    if (dayOfWeek == 0) // Sunday; we represent it as 7, POSIX uses 0
393
0
        dayOfWeek = 7;
394
0
    else if (dayOfWeek & ~7 || month < 1 || month > 12 || week < 1 || week > 5)
395
0
        return QDate();
396
397
0
    QDate date(year, month, 1);
398
0
    int startDow = date.dayOfWeek();
399
0
    if (startDow <= dayOfWeek)
400
0
        date = date.addDays(dayOfWeek - startDow - 7);
401
0
    else
402
0
        date = date.addDays(dayOfWeek - startDow);
403
0
    date = date.addDays(week * 7);
404
0
    while (date.month() != month)
405
0
        date = date.addDays(-7);
406
0
    return date;
407
0
}
408
409
static QDate calculatePosixDate(const QByteArray &dateRule, int year)
410
0
{
411
0
    Q_ASSERT(!dateRule.isEmpty());
412
0
    bool ok;
413
    // Can start with M, J, or a digit
414
0
    if (dateRule.at(0) == 'M') {
415
        // nth week in month format "Mmonth.week.dow"
416
0
        QList<QByteArray> dateParts = dateRule.split('.');
417
0
        if (dateParts.size() > 2) {
418
0
            Q_ASSERT(!dateParts.at(0).isEmpty()); // the 'M' is its [0].
419
0
            int month = QByteArrayView{ dateParts.at(0) }.sliced(1).toInt(&ok);
420
0
            int week = ok ? dateParts.at(1).toInt(&ok) : 0;
421
0
            int dow = ok ? dateParts.at(2).toInt(&ok) : 0;
422
0
            if (ok)
423
0
                return calculateDowDate(year, month, dow, week);
424
0
        }
425
0
    } else if (dateRule.at(0) == 'J') {
426
        // Day of Year 1...365, ignores Feb 29.
427
        // So March always starts on day 60.
428
0
        int doy = QByteArrayView{ dateRule }.sliced(1).toInt(&ok);
429
0
        if (ok && doy > 0 && doy < 366) {
430
            // Subtract 1 because we're adding days *after* the first of
431
            // January, unless it's after February in a leap year, when the leap
432
            // day cancels that out:
433
0
            if (!QDate::isLeapYear(year) || doy < 60)
434
0
                --doy;
435
0
            return QDate(year, 1, 1).addDays(doy);
436
0
        }
437
0
    } else {
438
        // Day of Year 0...365, includes Feb 29
439
0
        int doy = dateRule.toInt(&ok);
440
0
        if (ok && doy >= 0 && doy < 366)
441
0
            return QDate(year, 1, 1).addDays(doy);
442
0
    }
443
0
    return QDate();
444
0
}
445
446
// returns the time in seconds, INT_MIN if we failed to parse
447
static int parsePosixTime(const char *begin, const char *end)
448
0
{
449
    // Format "hh[:mm[:ss]]"
450
0
    int hour, min = 0, sec = 0;
451
452
0
    const int maxHour = 137; // POSIX's extended range.
453
0
    auto r = qstrntoll(begin, end - begin, 10);
454
0
    hour = r.result;
455
0
    if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
456
0
        return INT_MIN;
457
0
    begin += r.used;
458
0
    if (begin < end && *begin == ':') {
459
        // minutes
460
0
        ++begin;
461
0
        r = qstrntoll(begin, end - begin, 10);
462
0
        min = r.result;
463
0
        if (!r.ok() || min < 0 || min > 59 || r.used > 2)
464
0
            return INT_MIN;
465
466
0
        begin += r.used;
467
0
        if (begin < end && *begin == ':') {
468
            // seconds
469
0
            ++begin;
470
0
            r = qstrntoll(begin, end - begin, 10);
471
0
            sec = r.result;
472
0
            if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
473
0
                return INT_MIN;
474
0
            begin += r.used;
475
0
        }
476
0
    }
477
478
    // we must have consumed everything
479
0
    if (begin != end)
480
0
        return INT_MIN;
481
482
0
    return (hour * 60 + min) * 60 + sec;
483
0
}
484
485
static int parsePosixTransitionTime(const QByteArray &timeRule)
486
0
{
487
0
    return parsePosixTime(timeRule.constBegin(), timeRule.constEnd());
488
0
}
489
490
static int parsePosixOffset(const char *begin, const char *end)
491
0
{
492
    // Format "[+|-]hh[:mm[:ss]]"
493
    // note that the sign is inverted because POSIX counts in hours West of GMT
494
0
    bool negate = true;
495
0
    if (*begin == '+') {
496
0
        ++begin;
497
0
    } else if (*begin == '-') {
498
0
        negate = false;
499
0
        ++begin;
500
0
    }
501
502
0
    int value = parsePosixTime(begin, end);
503
0
    if (value == INT_MIN)
504
0
        return value;
505
0
    return negate ? -value : value;
506
0
}
507
508
static inline bool asciiIsLetter(char ch)
509
0
{
510
0
    ch |= 0x20; // lowercases if it is a letter, otherwise just corrupts ch
511
0
    return ch >= 'a' && ch <= 'z';
512
0
}
513
514
namespace {
515
516
struct PosixZone
517
{
518
    enum {
519
        InvalidOffset = INT_MIN,
520
    };
521
522
    QString name;
523
    int offset = InvalidOffset;
524
0
    bool hasValidOffset() const noexcept { return offset != InvalidOffset; }
525
    QTimeZonePrivate::Data dataAt(qint64 when)
526
0
    {
527
0
        Q_ASSERT(hasValidOffset());
528
0
        return QTimeZonePrivate::Data(name, when, offset, offset);
529
0
    }
530
    QTimeZonePrivate::Data dataAtOffset(qint64 when, int standard)
531
0
    {
532
0
        Q_ASSERT(hasValidOffset());
533
0
        return QTimeZonePrivate::Data(name, when, offset, standard);
534
0
    }
535
536
    static PosixZone parse(const char *&pos, const char *end);
537
};
538
539
} // unnamed namespace
540
541
// Returns the zone name, the offset (in seconds) and advances \a begin to
542
// where the parsing ended. Returns a zone of INT_MIN in case an offset
543
// couldn't be read.
544
PosixZone PosixZone::parse(const char *&pos, const char *end)
545
0
{
546
0
    static const char offsetChars[] = "0123456789:";
547
548
0
    const char *nameBegin = pos;
549
0
    const char *nameEnd;
550
0
    Q_ASSERT(pos < end);
551
552
0
    if (*pos == '<') {
553
0
        ++nameBegin;    // skip the '<'
554
0
        nameEnd = nameBegin;
555
0
        while (nameEnd < end && *nameEnd != '>') {
556
            // POSIX says only alphanumeric, but we allow anything
557
0
            ++nameEnd;
558
0
        }
559
0
        pos = nameEnd + 1;      // skip the '>'
560
0
    } else {
561
0
        nameEnd = nameBegin;
562
0
        while (nameEnd < end && asciiIsLetter(*nameEnd))
563
0
            ++nameEnd;
564
0
        pos = nameEnd;
565
0
    }
566
0
    if (nameEnd - nameBegin < 3)
567
0
        return {};  // name must be at least 3 characters long
568
569
    // zone offset, form [+-]hh:mm:ss
570
0
    const char *zoneBegin = pos;
571
0
    const char *zoneEnd = pos;
572
0
    if (zoneEnd < end && (zoneEnd[0] == '+' || zoneEnd[0] == '-'))
573
0
        ++zoneEnd;
574
0
    while (zoneEnd < end) {
575
0
        if (strchr(offsetChars, char(*zoneEnd)) == nullptr)
576
0
            break;
577
0
        ++zoneEnd;
578
0
    }
579
580
0
    QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
581
0
    const int offset = zoneEnd > zoneBegin ? parsePosixOffset(zoneBegin, zoneEnd) : InvalidOffset;
582
0
    pos = zoneEnd;
583
    // UTC+hh:mm:ss or GMT+hh:mm:ss should be read as offsets from UTC, not as a
584
    // POSIX rule naming a zone as UTC or GMT and specifying a non-zero offset.
585
0
    if (offset != 0 && (name =="UTC"_L1 || name == "GMT"_L1))
586
0
        return {};
587
0
    return {std::move(name), offset};
588
0
}
589
590
/* Parse and check a POSIX rule.
591
592
   By default a simple zone abbreviation with no offset information is accepted.
593
   Set \a requireOffset to \c true to require that there be offset data present.
594
*/
595
static auto validatePosixRule(const QByteArray &posixRule, bool requireOffset = false)
596
0
{
597
    // Format is described here:
598
    // http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
599
    // See also calculatePosixTransition()'s reference.
600
0
    const auto parts = posixRule.split(',');
601
0
    const struct { bool isValid, hasDst; } fail{false, false}, good{true, parts.size() > 1};
602
0
    const QByteArray &zoneinfo = parts.at(0);
603
0
    if (zoneinfo.isEmpty())
604
0
        return fail;
605
606
0
    const char *begin = zoneinfo.begin();
607
0
    {
608
        // Updates begin to point after the name and offset it parses:
609
0
        const auto posix = PosixZone::parse(begin, zoneinfo.end());
610
0
        if (posix.name.isEmpty())
611
0
            return fail;
612
0
        if (requireOffset && !posix.hasValidOffset())
613
0
            return fail;
614
0
    }
615
616
0
    if (good.hasDst) {
617
0
        if (begin >= zoneinfo.end())
618
0
            return fail;
619
        // Expect a second name (and optional offset) after the first:
620
0
        if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
621
0
            return fail;
622
0
    }
623
0
    if (begin < zoneinfo.end())
624
0
        return fail;
625
626
0
    if (good.hasDst) {
627
0
        if (parts.size() != 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
628
0
            return fail;
629
0
        for (int i = 1; i < 3; ++i) {
630
0
            const auto tran = parts.at(i).split('/');
631
0
            if (!calculatePosixDate(tran.at(0), 1972).isValid())
632
0
                return fail;
633
0
            if (tran.size() > 1) {
634
0
                const auto time = tran.at(1);
635
0
                if (parsePosixTime(time.begin(), time.end()) == INT_MIN)
636
0
                    return fail;
637
0
            }
638
0
        }
639
0
    }
640
0
    return good;
641
0
}
642
643
static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray &posixRule,
644
                                                               int startYear, int endYear,
645
                                                               qint64 lastTranMSecs)
646
0
{
647
0
    QList<QTimeZonePrivate::Data> result;
648
649
    // POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00"
650
    // i.e. "std offset dst [offset],start[/time],end[/time]"
651
    // See the section about TZ at
652
    // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
653
    // and the link in validatePosixRule(), above.
654
0
    QList<QByteArray> parts = posixRule.split(',');
655
656
0
    PosixZone stdZone, dstZone;
657
0
    {
658
0
        const QByteArray &zoneinfo = parts.at(0);
659
0
        const char *begin = zoneinfo.constBegin();
660
661
0
        stdZone = PosixZone::parse(begin, zoneinfo.constEnd());
662
0
        if (!stdZone.hasValidOffset()) {
663
0
            stdZone.offset = 0;     // reset to UTC if we failed to parse
664
0
        } else if (begin < zoneinfo.constEnd()) {
665
0
            dstZone = PosixZone::parse(begin, zoneinfo.constEnd());
666
0
            if (!dstZone.hasValidOffset()) {
667
                // if the dst offset isn't provided, it is 1 hour ahead of the standard offset
668
0
                dstZone.offset = stdZone.offset + (60 * 60);
669
0
            }
670
0
        }
671
0
    }
672
673
    // If only the name part, or no DST specified, then no transitions
674
0
    if (parts.size() == 1 || !dstZone.hasValidOffset()) {
675
0
        result.emplaceBack(
676
0
                stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name,
677
0
                lastTranMSecs, stdZone.offset, stdZone.offset);
678
0
        return result;
679
0
    }
680
0
    if (parts.size() < 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
681
0
        return result; // Malformed.
682
683
    // Get the std to dst transition details
684
0
    const int twoOClock = 7200; // Default transition time, when none specified
685
0
    const auto dstParts = parts.at(1).split('/');
686
0
    const QByteArray dstDateRule = dstParts.at(0);
687
0
    const int dstTime = dstParts.size() < 2 ? twoOClock : parsePosixTransitionTime(dstParts.at(1));
688
689
    // Get the dst to std transition details
690
0
    const auto stdParts = parts.at(2).split('/');
691
0
    const QByteArray stdDateRule = stdParts.at(0);
692
0
    const int stdTime = stdParts.size() < 2 ? twoOClock : parsePosixTransitionTime(stdParts.at(1));
693
694
0
    if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
695
0
        return result; // Malformed.
696
697
    // Limit year to the range QDateTime can represent:
698
0
    const int minYear = int(QDateTime::YearRange::First);
699
0
    const int maxYear = int(QDateTime::YearRange::Last);
700
0
    startYear = qBound(minYear, startYear, maxYear);
701
0
    endYear = qBound(minYear, endYear, maxYear);
702
0
    Q_ASSERT(startYear <= endYear);
703
704
0
    for (int year = startYear; year <= endYear; ++year) {
705
        // Note: std and dst, despite being QDateTime(,, UTC), have the
706
        // date() and time() of the *zone*'s description of the transition
707
        // moments; the atMSecsSinceEpoch values computed from them are
708
        // correctly offse to be UTC-based.
709
710
        // Transition to daylight-saving time:
711
0
        QDateTime dst(calculatePosixDate(dstDateRule, year)
712
0
                      .startOfDay(QTimeZone::UTC).addSecs(dstTime));
713
0
        auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000,
714
0
                                           stdZone.offset);
715
        // Transition to standard time:
716
0
        QDateTime std(calculatePosixDate(stdDateRule, year)
717
0
                      .startOfDay(QTimeZone::UTC).addSecs(stdTime));
718
0
        auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000);
719
720
0
        if (year == startYear) {
721
            // Handle the special case of fixed state, which may be represented
722
            // by fake transitions at start and end of each year:
723
0
            if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) {
724
0
                if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
725
0
                    && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
726
                    // Permanent DST:
727
0
                    saving.atMSecsSinceEpoch = lastTranMSecs;
728
0
                    result.emplaceBack(std::move(saving));
729
0
                    return result;
730
0
                }
731
0
            } else {
732
0
                if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
733
0
                    && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
734
                    // Permanent Standard time, perversely described:
735
0
                    standard.atMSecsSinceEpoch = lastTranMSecs;
736
0
                    result.emplaceBack(std::move(standard));
737
0
                    return result;
738
0
                }
739
0
            }
740
0
        }
741
742
0
        const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
743
0
        const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
744
0
        if (useStd && useDst) {
745
0
            if (dst < std) {
746
0
                result.emplaceBack(std::move(saving));
747
0
                result.emplaceBack(std::move(standard));
748
0
            } else {
749
0
                result.emplaceBack(std::move(standard));
750
0
                result.emplaceBack(std::move(saving));
751
0
            }
752
0
        } else if (useStd) {
753
0
            result.emplaceBack(std::move(standard));
754
0
        } else if (useDst) {
755
0
            result.emplaceBack(std::move(saving));
756
0
        }
757
0
    }
758
0
    return result;
759
0
}
760
761
// Create the system default time zone
762
QTzTimeZonePrivate::QTzTimeZonePrivate()
763
    : QTzTimeZonePrivate(staticSystemTimeZoneId())
764
0
{
765
0
}
766
767
QTzTimeZonePrivate::~QTzTimeZonePrivate()
768
0
{
769
0
}
770
771
QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const
772
0
{
773
0
    return new QTzTimeZonePrivate(*this);
774
0
}
775
776
class QTzTimeZoneCache
777
{
778
public:
779
    QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId);
780
781
private:
782
    static QTzTimeZoneCacheEntry findEntry(const QByteArray &ianaId);
783
    QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
784
    QMutex m_mutex;
785
};
786
787
QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId)
788
0
{
789
0
    QTzTimeZoneCacheEntry ret;
790
0
    QFile tzif;
791
0
    if (ianaId.isEmpty()) {
792
        // Open system tz
793
0
        tzif.setFileName(QStringLiteral("/etc/localtime"));
794
0
        if (!tzif.open(QIODevice::ReadOnly))
795
0
            return ret;
796
0
    } else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
797
        // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ
798
0
        auto check = validatePosixRule(ianaId);
799
0
        if (check.isValid) {
800
0
            ret.m_hasDst = check.hasDst;
801
0
            ret.m_posixRule = ianaId;
802
0
        }
803
0
        return ret;
804
0
    }
805
806
0
    QDataStream ds(&tzif);
807
808
    // Parse the old version block of data
809
0
    bool ok = false;
810
0
    QByteArray posixRule;
811
0
    QTzHeader hdr = parseTzHeader(ds, &ok);
812
0
    if (!ok || ds.status() != QDataStream::Ok)
813
0
        return ret;
814
0
    QList<QTzTransition> tranList = parseTzTransitions(ds, hdr.tzh_timecnt, false);
815
0
    if (ds.status() != QDataStream::Ok)
816
0
        return ret;
817
0
    QList<QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
818
0
    if (ds.status() != QDataStream::Ok)
819
0
        return ret;
820
0
    QMap<int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
821
0
    if (ds.status() != QDataStream::Ok)
822
0
        return ret;
823
0
    parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false);
824
0
    if (ds.status() != QDataStream::Ok)
825
0
        return ret;
826
0
    typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
827
0
    if (ds.status() != QDataStream::Ok)
828
0
        return ret;
829
830
    // If version 2 then parse the second block of data
831
0
    if (hdr.tzh_version == '2' || hdr.tzh_version == '3') {
832
0
        ok = false;
833
0
        QTzHeader hdr2 = parseTzHeader(ds, &ok);
834
0
        if (!ok || ds.status() != QDataStream::Ok)
835
0
            return ret;
836
0
        tranList = parseTzTransitions(ds, hdr2.tzh_timecnt, true);
837
0
        if (ds.status() != QDataStream::Ok)
838
0
            return ret;
839
0
        typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
840
0
        if (ds.status() != QDataStream::Ok)
841
0
            return ret;
842
0
        abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
843
0
        if (ds.status() != QDataStream::Ok)
844
0
            return ret;
845
0
        parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true);
846
0
        if (ds.status() != QDataStream::Ok)
847
0
            return ret;
848
0
        typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
849
0
        if (ds.status() != QDataStream::Ok)
850
0
            return ret;
851
0
        posixRule = parseTzPosixRule(ds);
852
0
        if (ds.status() != QDataStream::Ok)
853
0
            return ret;
854
0
    }
855
    // Translate the TZ file's raw data into our internal form:
856
857
0
    if (!posixRule.isEmpty()) {
858
0
        auto check = validatePosixRule(posixRule);
859
0
        if (!check.isValid) // We got a POSIX rule, but it was malformed:
860
0
            return ret;
861
0
        ret.m_posixRule = posixRule;
862
0
        ret.m_hasDst = check.hasDst;
863
0
    }
864
865
    // Translate the array-index-based tz_abbrind into list index
866
0
    const int size = abbrevMap.size();
867
0
    ret.m_abbreviations.clear();
868
0
    ret.m_abbreviations.reserve(size);
869
0
    QList<int> abbrindList;
870
0
    abbrindList.reserve(size);
871
0
    for (auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
872
0
        ret.m_abbreviations.append(it.value());
873
0
        abbrindList.append(it.key());
874
0
    }
875
    // Map tz_abbrind from map's keys (as initially read) to abbrindList's
876
    // indices (used hereafter):
877
0
    for (int i = 0; i < typeList.size(); ++i)
878
0
        typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
879
880
    // TODO: is typeList[0] always the "before zones" data ? It seems to be ...
881
0
    if (typeList.size())
882
0
        ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
883
884
    // Offsets are stored as total offset, want to know separate UTC and DST offsets
885
    // so find the first non-dst transition to use as base UTC Offset
886
0
    int utcOffset = ret.m_preZoneRule.stdOffset;
887
0
    for (const QTzTransition &tran : std::as_const(tranList)) {
888
0
        if (!typeList.at(tran.tz_typeind).tz_isdst) {
889
0
            utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
890
0
            break;
891
0
        }
892
0
    }
893
894
    // Now for each transition time calculate and store our rule:
895
0
    const int tranCount = tranList.size();
896
0
    ret.m_tranTimes.reserve(tranCount);
897
    // The DST offset when in effect: usually stable, usually an hour:
898
0
    int lastDstOff = 3600;
899
0
    for (int i = 0; i < tranCount; i++) {
900
0
        const QTzTransition &tz_tran = tranList.at(i);
901
0
        QTzTransitionTime tran;
902
0
        QTzTransitionRule rule;
903
0
        const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
904
905
        // Calculate the associated Rule
906
0
        if (!tz_type.tz_isdst) {
907
0
            utcOffset = tz_type.tz_gmtoff;
908
0
        } else if (Q_UNLIKELY(tz_type.tz_gmtoff != utcOffset + lastDstOff)) {
909
            /*
910
              This might be a genuine change in DST offset, but could also be
911
              DST starting at the same time as the standard offset changed.  See
912
              if DST's end gives a more plausible utcOffset (i.e. one closer to
913
              the last we saw, or a simple whole hour):
914
            */
915
            // Standard offset inferred from net offset and expected DST offset:
916
0
            const int inferStd = tz_type.tz_gmtoff - lastDstOff; // != utcOffset
917
0
            for (int j = i + 1; j < tranCount; j++) {
918
0
                const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
919
0
                if (!new_type.tz_isdst) {
920
0
                    const int newUtc = new_type.tz_gmtoff;
921
0
                    if (newUtc == utcOffset) {
922
                        // DST-end can't help us, avoid lots of messy checks.
923
                    // else: See if the end matches the familiar DST offset:
924
0
                    } else if (newUtc == inferStd) {
925
0
                        utcOffset = newUtc;
926
                    // else: let either end shift us to one hour as DST offset:
927
0
                    } else if (tz_type.tz_gmtoff - 3600 == utcOffset) {
928
                        // Start does it
929
0
                    } else if (tz_type.tz_gmtoff - 3600 == newUtc) {
930
0
                        utcOffset = newUtc; // End does it
931
                    // else: prefer whichever end gives DST offset closer to
932
                    // last, but consider any offset > 0 "closer" than any <= 0:
933
0
                    } else if (newUtc < tz_type.tz_gmtoff
934
0
                               ? (utcOffset >= tz_type.tz_gmtoff
935
0
                                  || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
936
0
                               : (utcOffset >= tz_type.tz_gmtoff
937
0
                                  && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
938
0
                        utcOffset = newUtc;
939
0
                    }
940
0
                    break;
941
0
                }
942
0
            }
943
0
            lastDstOff = tz_type.tz_gmtoff - utcOffset;
944
0
        }
945
0
        rule.stdOffset = utcOffset;
946
0
        rule.dstOffset = tz_type.tz_gmtoff - utcOffset;
947
0
        rule.abbreviationIndex = tz_type.tz_abbrind;
948
949
        // If the rule already exist then use that, otherwise add it
950
0
        int ruleIndex = ret.m_tranRules.indexOf(rule);
951
0
        if (ruleIndex == -1) {
952
0
            if (rule.dstOffset != 0)
953
0
                ret.m_hasDst = true;
954
0
            tran.ruleIndex = ret.m_tranRules.size();
955
0
            ret.m_tranRules.append(rule);
956
0
        } else {
957
0
            tran.ruleIndex = ruleIndex;
958
0
        }
959
960
0
        tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
961
0
        ret.m_tranTimes.append(tran);
962
0
    }
963
964
0
    return ret;
965
0
}
966
967
QTzTimeZoneCacheEntry QTzTimeZoneCache::fetchEntry(const QByteArray &ianaId)
968
0
{
969
0
    QMutexLocker locker(&m_mutex);
970
971
    // search the cache...
972
0
    QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
973
0
    if (obj)
974
0
        return *obj;
975
976
    // ... or build a new entry from scratch
977
978
0
    locker.unlock(); // don't parse files under mutex lock
979
980
0
    QTzTimeZoneCacheEntry ret = findEntry(ianaId);
981
0
    auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
982
983
0
    locker.relock();
984
0
    m_cache.insert(ianaId, ptr.release()); // may overwrite if another thread was faster
985
0
    locker.unlock();
986
987
0
    return ret;
988
0
}
989
990
// Create a named time zone
991
QTzTimeZonePrivate::QTzTimeZonePrivate(const QByteArray &ianaId)
992
0
{
993
0
    if (!isTimeZoneIdAvailable(ianaId)) // Avoid pointlessly creating cache entries
994
0
        return;
995
0
    static QTzTimeZoneCache tzCache;
996
0
    auto entry = tzCache.fetchEntry(ianaId);
997
0
    if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
998
0
        return; // Invalid after all !
999
1000
0
    cached_data = std::move(entry);
1001
0
    m_id = ianaId;
1002
    // Avoid empty ID, if we have an abbreviation to use instead
1003
0
    if (m_id.isEmpty()) {
1004
        // This can only happen for the system zone, when we've read the
1005
        // contents of /etc/localtime because it wasn't a symlink.
1006
        // TODO: use CLDR generic abbreviation for the zone.
1007
0
        m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1008
0
    }
1009
0
}
1010
1011
QLocale::Territory QTzTimeZonePrivate::territory() const
1012
0
{
1013
0
    return tzZones->value(m_id).territory;
1014
0
}
1015
1016
QString QTzTimeZonePrivate::comment() const
1017
0
{
1018
0
    return QString::fromUtf8(tzZones->value(m_id).comment);
1019
0
}
1020
1021
QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1022
                                        QTimeZone::NameType nameType,
1023
                                        const QLocale &locale) const
1024
0
{
1025
    // TZ only provides C-locale abbreviations and offset:
1026
0
    if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1027
0
        Data tran = data(timeType);
1028
0
        if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1029
0
            if (nameType == QTimeZone::ShortName)
1030
0
                return tran.abbreviation;
1031
            // Save base class repeating the data(timeType) query:
1032
0
            if (locale.language() == QLocale::C)
1033
0
                return isoOffsetFormat(tran.offsetFromUtc);
1034
0
        }
1035
0
    }
1036
    // Otherwise, fall back to base class (and qtimezonelocale.cpp):
1037
0
    return QTimeZonePrivate::displayName(timeType, nameType, locale);
1038
0
}
1039
1040
QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
1041
0
{
1042
0
    return data(atMSecsSinceEpoch).abbreviation;
1043
0
}
1044
1045
int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
1046
0
{
1047
0
    const Data tran = data(atMSecsSinceEpoch);
1048
0
    return tran.offsetFromUtc; // == tran.standardTimeOffset + tran.daylightTimeOffset
1049
0
}
1050
1051
int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
1052
0
{
1053
0
    return data(atMSecsSinceEpoch).standardTimeOffset;
1054
0
}
1055
1056
int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
1057
0
{
1058
0
    return data(atMSecsSinceEpoch).daylightTimeOffset;
1059
0
}
1060
1061
bool QTzTimeZonePrivate::hasDaylightTime() const
1062
0
{
1063
0
    return cached_data.m_hasDst;
1064
0
}
1065
1066
bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
1067
0
{
1068
0
    return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1069
0
}
1070
1071
QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran) const
1072
0
{
1073
0
    return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1074
0
}
1075
1076
QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1077
                                                        qint64 msecsSinceEpoch) const
1078
0
{
1079
0
    return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1080
0
                msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1081
0
}
1082
1083
QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const
1084
0
{
1085
0
    const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1086
    // The Data::atMSecsSinceEpoch of the single entry if zone is constant:
1087
0
    qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1088
0
    return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1089
0
}
1090
1091
QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
1092
0
{
1093
    // If the required time is after the last transition (or there were none)
1094
    // and we have a POSIX rule, then use it:
1095
0
    if (!cached_data.m_posixRule.isEmpty()
1096
0
        && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1097
0
        QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1098
0
        auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1099
0
                                       [forMSecsSinceEpoch] (const Data &at) {
1100
0
                                           return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1101
0
                                       });
1102
        // Use most recent, if any in the past; or the first if we have no other rules:
1103
0
        if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1104
0
            Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1105
0
            data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1106
0
            return data;
1107
0
        }
1108
0
    }
1109
0
    if (tranCache().isEmpty()) // Only possible if !isValid()
1110
0
        return {};
1111
1112
    // Otherwise, use the rule for the most recent or first transition:
1113
0
    auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1114
0
                                     [forMSecsSinceEpoch] (QTzTransitionTime at) {
1115
0
                                         return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1116
0
                                     });
1117
0
    if (last == tranCache().cbegin())
1118
0
        return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1119
1120
0
    --last;
1121
0
    return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1122
0
}
1123
1124
// Overridden because the final iteration over transitions only needs to look
1125
// forward and backwards one transition within the POSIX rule (when there is
1126
// one, as is common) to settle the whole period it covers, so we can then skip
1127
// all other transitions of the POSIX rule and iterate tranCache() backwards
1128
// from its most recent transition.
1129
QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType) const
1130
0
{
1131
    // True if tran is valid and has the DST-ness to match timeType:
1132
0
    const auto validMatch = [timeType](const Data &tran) {
1133
0
        return tran.atMSecsSinceEpoch != invalidMSecs()
1134
0
            && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1135
0
    };
1136
1137
    // Get current tran, use if suitable:
1138
0
    const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1139
0
    Data tran = data(currentMSecs);
1140
0
    if (validMatch(tran))
1141
0
        return tran;
1142
1143
    // Otherwise, next tran probably flips DST-ness:
1144
0
    tran = nextTransition(currentMSecs);
1145
0
    if (validMatch(tran))
1146
0
        return tran;
1147
1148
    // Failing that, prev (or present, if current MSecs is eactly a transition
1149
    // moment) tran defines what data() got us and the one before that probably
1150
    // flips DST-ness:
1151
0
    tran = previousTransition(currentMSecs + 1);
1152
0
    if (tran.atMSecsSinceEpoch != invalidMSecs())
1153
0
        tran = previousTransition(tran.atMSecsSinceEpoch);
1154
0
    if (validMatch(tran))
1155
0
        return tran;
1156
1157
    // Otherwise, we can look backwards through transitions for a match; if we
1158
    // have a POSIX rule, it clearly doesn't do DST (or we'd have hit it by
1159
    // now), so we only need to look in the tranCache() up to now.
1160
0
    const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1161
0
        return at.atMSecsSinceEpoch <= currentMSecs;
1162
0
    };
1163
0
    auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1164
    // That's the end or first future transition; we don't want to look at it,
1165
    // but at all those before it.
1166
0
    while (it != tranCache().cbegin()) {
1167
0
        --it;
1168
0
        tran = dataForTzTransition(*it);
1169
0
        if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1170
0
            return tran;
1171
0
    }
1172
1173
0
    return {};
1174
0
}
1175
1176
bool QTzTimeZonePrivate::isDataLocale(const QLocale &locale) const
1177
0
{
1178
    // TZ data uses English / C locale names:
1179
0
    return locale.language() == QLocale::C || locale.language() == QLocale::English;
1180
0
}
1181
1182
bool QTzTimeZonePrivate::hasTransitions() const
1183
0
{
1184
0
    return true;
1185
0
}
1186
1187
QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
1188
0
{
1189
    // If the required time is after the last transition (or there were none)
1190
    // and we have a POSIX rule, then use it:
1191
0
    if (!cached_data.m_posixRule.isEmpty()
1192
0
        && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1193
0
        QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1194
0
        auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1195
0
                                       [afterMSecsSinceEpoch] (const Data &at) {
1196
0
                                           return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1197
0
                                       });
1198
1199
0
        return it == posixTrans.cend() ? Data{} : *it;
1200
0
    }
1201
1202
    // Otherwise, if we can find a valid tran, use its rule:
1203
0
    auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1204
0
                                     [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1205
0
                                         return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1206
0
                                     });
1207
0
    return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1208
0
}
1209
1210
QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
1211
0
{
1212
    // If the required time is after the last transition (or there were none)
1213
    // and we have a POSIX rule, then use it:
1214
0
    if (!cached_data.m_posixRule.isEmpty()
1215
0
        && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1216
0
        QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1217
0
        auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1218
0
                                       [beforeMSecsSinceEpoch] (const Data &at) {
1219
0
                                           return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1220
0
                                       });
1221
0
        if (it > posixTrans.cbegin())
1222
0
            return *--it;
1223
        // It fell between the last transition (if any) and the first of the POSIX rule:
1224
0
        return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1225
0
    }
1226
1227
    // Otherwise if we can find a valid tran then use its rule
1228
0
    auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1229
0
                                     [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1230
0
                                         return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1231
0
                                     });
1232
0
    return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1233
0
}
1234
1235
bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
1236
0
{
1237
    // Allow a POSIX rule as long as it has offset data. (This needs to reject a
1238
    // plain abbreviation, without offset, since claiming to support such zones
1239
    // would prevent the custom QTimeZone constructor from accepting such a
1240
    // name, as it doesn't want a custom zone to over-ride a "real" one.)
1241
0
    return tzZones->contains(ianaId) || validatePosixRule(ianaId, true).isValid;
1242
0
}
1243
1244
QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const
1245
0
{
1246
0
    QList<QByteArray> result = tzZones->keys();
1247
0
    std::sort(result.begin(), result.end());
1248
0
    return result;
1249
0
}
1250
1251
QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory) const
1252
0
{
1253
    // TODO AnyTerritory
1254
0
    QList<QByteArray> result;
1255
0
    for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1256
0
        if (it.value().territory == territory)
1257
0
            result << it.key();
1258
0
    }
1259
0
    std::sort(result.begin(), result.end());
1260
0
    return result;
1261
0
}
1262
1263
// Getting the system zone's ID:
1264
1265
namespace {
1266
class ZoneNameReader
1267
{
1268
public:
1269
    QByteArray name()
1270
0
    {
1271
        /* Assumptions:
1272
           a) Systems don't change which of localtime and TZ they use without a
1273
              reboot.
1274
           b) When they change, they use atomic renames, hence a new device and
1275
              inode for the new file.
1276
           c) If we change which *name* is used for a zone, while referencing
1277
              the same final zoneinfo file, we don't care about the change of
1278
              name (e.g. if Europe/Oslo and Europe/Berlin are both symlinks to
1279
              the same CET file, continuing to use the old name, after
1280
              /etc/localtime changes which of the two it points to, is
1281
              harmless).
1282
1283
           The alternative would be to use a file-system watcher, but they are a
1284
           scarce resource.
1285
         */
1286
0
        const StatIdent local = identify("/etc/localtime");
1287
0
        const StatIdent tz = identify("/etc/TZ");
1288
0
        const StatIdent timezone = identify("/etc/timezone");
1289
0
        if (!m_name.isEmpty() && m_last.isValid()
1290
0
            && (m_last == local || m_last == tz || m_last == timezone)) {
1291
0
            return m_name;
1292
0
        }
1293
1294
0
        m_name = etcLocalTime();
1295
0
        if (!m_name.isEmpty()) {
1296
0
            m_last = local;
1297
0
            return m_name;
1298
0
        }
1299
1300
        // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ:
1301
0
        m_name = etcContent(QStringLiteral("/etc/TZ"));
1302
0
        if (!m_name.isEmpty()) {
1303
0
            m_last = tz;
1304
0
            return m_name;
1305
0
        }
1306
1307
        // Gentoo still (2020, QTBUG-87326) uses this:
1308
0
        m_name = etcContent(QStringLiteral("/etc/timezone"));
1309
0
        m_last = m_name.isEmpty() ? StatIdent() : timezone;
1310
0
        return m_name;
1311
0
    }
1312
1313
private:
1314
    QByteArray m_name;
1315
    struct StatIdent
1316
    {
1317
        static constexpr unsigned long bad = ~0ul;
1318
        unsigned long m_dev, m_ino;
1319
0
        constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1320
0
        StatIdent(const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1321
0
        bool isValid() { return m_dev != bad || m_ino != bad; }
1322
        friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1323
0
        { return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1324
    };
1325
    StatIdent m_last;
1326
1327
    static StatIdent identify(const char *path)
1328
0
    {
1329
0
        QT_STATBUF data;
1330
0
        return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1331
0
    }
1332
1333
    static QByteArray etcLocalTime()
1334
0
    {
1335
        // On most distros /etc/localtime is a symlink to a real file so extract
1336
        // name from the path
1337
0
        const QString tzdir = qEnvironmentVariable("TZDIR");
1338
0
        constexpr auto zoneinfo = "/zoneinfo/"_L1;
1339
0
        QString path = QStringLiteral("/etc/localtime");
1340
0
        long iteration = getSymloopMax();
1341
        // Symlink may point to another symlink etc. before being under zoneinfo/
1342
        // We stop on the first path under /zoneinfo/, even if it is itself a
1343
        // symlink, like America/Montreal pointing to America/Toronto
1344
0
        do {
1345
0
            path = QFile::symLinkTarget(path);
1346
            // If it's a zoneinfo file, extract the zone name from its path:
1347
0
            int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1348
0
            if (index >= 0) {
1349
0
                const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1350
0
                return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1351
0
            }
1352
0
            index = path.indexOf(zoneinfo);
1353
0
            if (index >= 0)
1354
0
                return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1355
0
        } while (!path.isEmpty() && --iteration > 0);
1356
1357
0
        return QByteArray();
1358
0
    }
1359
1360
    static QByteArray etcContent(const QString &path)
1361
0
    {
1362
0
        QFile zone(path);
1363
0
        if (zone.open(QIODevice::ReadOnly))
1364
0
            return zone.readAll().trimmed();
1365
1366
0
        return QByteArray();
1367
0
    }
1368
1369
    // Any chain of symlinks longer than this is assumed to be a loop:
1370
    static long getSymloopMax()
1371
0
    {
1372
#ifdef SYMLOOP_MAX
1373
        // If defined, at runtime it can only be greater than this, so this is a safe bet:
1374
        return SYMLOOP_MAX;
1375
#else
1376
0
        errno = 0;
1377
0
        long result = sysconf(_SC_SYMLOOP_MAX);
1378
0
        if (result >= 0)
1379
0
            return result;
1380
        // result is -1, meaning either error or no limit
1381
0
        Q_ASSERT(!errno); // ... but it can't be an error, POSIX mandates _SC_SYMLOOP_MAX
1382
1383
        // therefore we can make up our own limit
1384
0
#  ifdef MAXSYMLINKS
1385
0
        return MAXSYMLINKS;
1386
#  else
1387
        return 8;
1388
#  endif
1389
0
#endif
1390
0
    }
1391
};
1392
}
1393
1394
QByteArray QTzTimeZonePrivate::systemTimeZoneId() const
1395
0
{
1396
0
    return staticSystemTimeZoneId();
1397
0
}
1398
1399
QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1400
0
{
1401
    // Check TZ env var first, if not populated try find it
1402
0
    QByteArray ianaId = qgetenv("TZ");
1403
1404
    // The TZ value can be ":/etc/localtime" which libc considers
1405
    // to be a "default timezone", in which case it will be read
1406
    // by one of the blocks below, so unset it here so it is not
1407
    // considered as a valid/found ianaId
1408
0
    if (ianaId == ":/etc/localtime")
1409
0
        ianaId.clear();
1410
0
    else if (ianaId.startsWith(':'))
1411
0
        ianaId = ianaId.sliced(1);
1412
1413
0
    if (ianaId.isEmpty()) {
1414
0
        Q_CONSTINIT thread_local static ZoneNameReader reader;
1415
0
        ianaId = reader.name();
1416
0
    }
1417
1418
0
    return ianaId;
1419
0
}
1420
1421
QT_END_NAMESPACE