Coverage Report

Created: 2023-11-19 06:45

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