1/****************************************************************************
2**
3** Copyright (C) 2021 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <qtimezone.h>
31#include <private/qtimezoneprivate_p.h>
32#include <qlocale.h>
33
34#if defined(Q_OS_WIN) && !QT_CONFIG(icu)
35# define USING_WIN_TZ
36#endif
37
38class tst_QTimeZone : public QObject
39{
40 Q_OBJECT
41
42public:
43 tst_QTimeZone();
44
45private slots:
46 // Public class default system tests
47 void createTest();
48 void nullTest();
49 void systemZone();
50 void dataStreamTest();
51 void isTimeZoneIdAvailable();
52 void availableTimeZoneIds();
53 void utcOffsetId_data();
54 void utcOffsetId();
55 void specificTransition_data();
56 void specificTransition();
57 void transitionEachZone_data();
58 void transitionEachZone();
59 void checkOffset_data();
60 void checkOffset();
61 void stressTest();
62 void windowsId();
63 void isValidId_data();
64 void isValidId();
65 void malformed();
66 // Backend tests
67 void utcTest();
68 void icuTest();
69 void tzTest();
70 void macTest();
71 void darwinTypes();
72 void winTest();
73
74private:
75 void printTimeZone(const QTimeZone &tz);
76#ifdef QT_BUILD_INTERNAL
77 // Generic tests of privates, called by implementation-specific private tests:
78 void testCetPrivate(const QTimeZonePrivate &tzp);
79 void testEpochTranPrivate(const QTimeZonePrivate &tzp);
80#endif // QT_BUILD_INTERNAL
81 const bool debug;
82};
83
84tst_QTimeZone::tst_QTimeZone()
85 // Set to true to print debug output, test Display Names and run long stress tests
86 : debug(false)
87{
88}
89
90void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
91{
92 QDateTime now = QDateTime::currentDateTime();
93 QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC);
94 QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC);
95 qDebug() << "";
96 qDebug() << "Time Zone = " << tz;
97 qDebug() << "";
98 qDebug() << "Is Valid = " << tz.isValid();
99 qDebug() << "";
100 qDebug() << "Zone ID = " << tz.id();
101 qDebug() << "Country = " << QLocale::countryToString(country: tz.country());
102 qDebug() << "Comment = " << tz.comment();
103 qDebug() << "";
104 qDebug() << "Locale = " << QLocale().name();
105 qDebug() << "Name Long = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::LongName);
106 qDebug() << "Name Short = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::ShortName);
107 qDebug() << "Name Offset = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::OffsetName);
108 qDebug() << "Name Long DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::LongName);
109 qDebug() << "Name Short DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::ShortName);
110 qDebug() << "Name Offset DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::OffsetName);
111 qDebug() << "Name Long Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::LongName);
112 qDebug() << "Name Short Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::ShortName);
113 qDebug() << "Name Offset Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::OffsetName);
114 qDebug() << "";
115 QLocale locale = QLocale(QStringLiteral("de_DE"));
116 qDebug() << "Locale = " << locale.name();
117 qDebug() << "Name Long = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::LongName, locale);
118 qDebug() << "Name Short = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::ShortName, locale);
119 qDebug() << "Name Offset = " << tz.displayName(timeType: QTimeZone::StandardTime, nameType: QTimeZone::OffsetName, locale);
120 qDebug() << "Name Long DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::LongName,locale);
121 qDebug() << "Name Short DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::ShortName, locale);
122 qDebug() << "Name Offset DST = " << tz.displayName(timeType: QTimeZone::DaylightTime, nameType: QTimeZone::OffsetName, locale);
123 qDebug() << "Name Long Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::LongName, locale);
124 qDebug() << "Name Short Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::ShortName, locale);
125 qDebug() << "Name Offset Generic = " << tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::OffsetName, locale);
126 qDebug() << "";
127 qDebug() << "Abbreviation Now = " << tz.abbreviation(atDateTime: now);
128 qDebug() << "Abbreviation on 1 Jan = " << tz.abbreviation(atDateTime: jan);
129 qDebug() << "Abbreviation on 1 June = " << tz.abbreviation(atDateTime: jun);
130 qDebug() << "";
131 qDebug() << "Offset on 1 January = " << tz.offsetFromUtc(atDateTime: jan);
132 qDebug() << "Offset on 1 June = " << tz.offsetFromUtc(atDateTime: jun);
133 qDebug() << "Offset Now = " << tz.offsetFromUtc(atDateTime: now);
134 qDebug() << "";
135 qDebug() << "UTC Offset Now = " << tz.standardTimeOffset(atDateTime: now);
136 qDebug() << "UTC Offset on 1 January = " << tz.standardTimeOffset(atDateTime: jan);
137 qDebug() << "UTC Offset on 1 June = " << tz.standardTimeOffset(atDateTime: jun);
138 qDebug() << "";
139 qDebug() << "DST Offset on 1 January = " << tz.daylightTimeOffset(atDateTime: jan);
140 qDebug() << "DST Offset on 1 June = " << tz.daylightTimeOffset(atDateTime: jun);
141 qDebug() << "DST Offset Now = " << tz.daylightTimeOffset(atDateTime: now);
142 qDebug() << "";
143 qDebug() << "Has DST = " << tz.hasDaylightTime();
144 qDebug() << "Is DST Now = " << tz.isDaylightTime(atDateTime: now);
145 qDebug() << "Is DST on 1 January = " << tz.isDaylightTime(atDateTime: jan);
146 qDebug() << "Is DST on 1 June = " << tz.isDaylightTime(atDateTime: jun);
147 qDebug() << "";
148 qDebug() << "Has Transitions = " << tz.hasTransitions();
149 qDebug() << "Transition after 1 Jan = " << tz.nextTransition(afterDateTime: jan).atUtc;
150 qDebug() << "Transition after 1 Jun = " << tz.nextTransition(afterDateTime: jun).atUtc;
151 qDebug() << "Transition before 1 Jan = " << tz.previousTransition(beforeDateTime: jan).atUtc;
152 qDebug() << "Transition before 1 Jun = " << tz.previousTransition(beforeDateTime: jun).atUtc;
153 qDebug() << "";
154}
155
156void tst_QTimeZone::createTest()
157{
158 const QTimeZone tz("Pacific/Auckland");
159
160 if (debug)
161 printTimeZone(tz);
162
163 // If the tz is not valid then skip as is probably using the UTC backend which is tested later
164 if (!tz.isValid())
165 QSKIP("System lacks zone used for test"); // This returns.
166
167 QCOMPARE(tz.id(), "Pacific/Auckland");
168 // Comparison tests:
169 const QTimeZone same("Pacific/Auckland");
170 QCOMPARE((tz == same), true);
171 QCOMPARE((tz != same), false);
172 const QTimeZone other("Australia/Sydney");
173 QCOMPARE((tz == other), false);
174 QCOMPARE((tz != other), true);
175
176 QCOMPARE(tz.country(), QLocale::NewZealand);
177
178 QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC);
179 QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC);
180 QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC);
181
182 QCOMPARE(tz.offsetFromUtc(jan), 13 * 3600);
183 QCOMPARE(tz.offsetFromUtc(jun), 12 * 3600);
184
185 QCOMPARE(tz.standardTimeOffset(jan), 12 * 3600);
186 QCOMPARE(tz.standardTimeOffset(jun), 12 * 3600);
187
188 QCOMPARE(tz.daylightTimeOffset(jan), 3600);
189 QCOMPARE(tz.daylightTimeOffset(jun), 0);
190
191 QCOMPARE(tz.hasDaylightTime(), true);
192 QCOMPARE(tz.isDaylightTime(jan), true);
193 QCOMPARE(tz.isDaylightTime(jun), false);
194
195 // Only test transitions if host system supports them
196 if (tz.hasTransitions()) {
197 QTimeZone::OffsetData tran = tz.nextTransition(afterDateTime: jan);
198 // 2012-04-01 03:00 NZDT, +13 -> +12
199 QCOMPARE(tran.atUtc,
200 QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600));
201 QCOMPARE(tran.offsetFromUtc, 12 * 3600);
202 QCOMPARE(tran.standardTimeOffset, 12 * 3600);
203 QCOMPARE(tran.daylightTimeOffset, 0);
204
205 tran = tz.nextTransition(afterDateTime: jun);
206 // 2012-09-30 02:00 NZST, +12 -> +13
207 QCOMPARE(tran.atUtc,
208 QDateTime(QDate(2012, 9, 30), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600));
209 QCOMPARE(tran.offsetFromUtc, 13 * 3600);
210 QCOMPARE(tran.standardTimeOffset, 12 * 3600);
211 QCOMPARE(tran.daylightTimeOffset, 3600);
212
213 tran = tz.previousTransition(beforeDateTime: jan);
214 // 2011-09-25 02:00 NZST, +12 -> +13
215 QCOMPARE(tran.atUtc,
216 QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600));
217 QCOMPARE(tran.offsetFromUtc, 13 * 3600);
218 QCOMPARE(tran.standardTimeOffset, 12 * 3600);
219 QCOMPARE(tran.daylightTimeOffset, 3600);
220
221 tran = tz.previousTransition(beforeDateTime: jun);
222 // 2012-04-01 03:00 NZDT, +13 -> +12 (again)
223 QCOMPARE(tran.atUtc,
224 QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600));
225 QCOMPARE(tran.offsetFromUtc, 12 * 3600);
226 QCOMPARE(tran.standardTimeOffset, 12 * 3600);
227 QCOMPARE(tran.daylightTimeOffset, 0);
228
229 QTimeZone::OffsetDataList expected;
230 tran.atUtc = QDateTime(QDate(2011, 4, 3), QTime(2, 0), Qt::OffsetFromUTC, 13 * 3600);
231 tran.offsetFromUtc = 13 * 3600;
232 tran.standardTimeOffset = 12 * 3600;
233 tran.daylightTimeOffset = 3600;
234 expected << tran;
235 tran.atUtc = QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600);
236 tran.offsetFromUtc = 12 * 3600;
237 tran.standardTimeOffset = 12 * 3600;
238 tran.daylightTimeOffset = 0;
239 expected << tran;
240 QTimeZone::OffsetDataList result = tz.transitions(fromDateTime: janPrev, toDateTime: jan);
241 QCOMPARE(result.count(), expected.count());
242 for (int i = 0; i > expected.count(); ++i) {
243 QCOMPARE(result.at(i).atUtc, expected.at(i).atUtc);
244 QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
245 QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
246 QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
247 }
248 }
249}
250
251void tst_QTimeZone::nullTest()
252{
253 QTimeZone nullTz1;
254 QTimeZone nullTz2;
255 QTimeZone utc("UTC");
256
257 // Validity tests
258 QCOMPARE(nullTz1.isValid(), false);
259 QCOMPARE(nullTz2.isValid(), false);
260 QCOMPARE(utc.isValid(), true);
261
262 // Comparison tests
263 QCOMPARE((nullTz1 == nullTz2), true);
264 QCOMPARE((nullTz1 != nullTz2), false);
265 QCOMPARE((nullTz1 == utc), false);
266 QCOMPARE((nullTz1 != utc), true);
267
268 // Assignment tests
269 nullTz2 = utc;
270 QCOMPARE(nullTz2.isValid(), true);
271 utc = nullTz1;
272 QCOMPARE(utc.isValid(), false);
273
274 QCOMPARE(nullTz1.id(), QByteArray());
275 QCOMPARE(nullTz1.country(), QLocale::AnyCountry);
276 QCOMPARE(nullTz1.comment(), QString());
277
278 QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC);
279 QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC);
280 QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC);
281
282 QCOMPARE(nullTz1.abbreviation(jan), QString());
283 QCOMPARE(nullTz1.displayName(jan), QString());
284 QCOMPARE(nullTz1.displayName(QTimeZone::StandardTime), QString());
285
286 QCOMPARE(nullTz1.offsetFromUtc(jan), 0);
287 QCOMPARE(nullTz1.offsetFromUtc(jun), 0);
288
289 QCOMPARE(nullTz1.standardTimeOffset(jan), 0);
290 QCOMPARE(nullTz1.standardTimeOffset(jun), 0);
291
292 QCOMPARE(nullTz1.daylightTimeOffset(jan), 0);
293 QCOMPARE(nullTz1.daylightTimeOffset(jun), 0);
294
295 QCOMPARE(nullTz1.hasDaylightTime(), false);
296 QCOMPARE(nullTz1.isDaylightTime(jan), false);
297 QCOMPARE(nullTz1.isDaylightTime(jun), false);
298
299 QTimeZone::OffsetData data = nullTz1.offsetData(forDateTime: jan);
300 QCOMPARE(data.atUtc, QDateTime());
301 QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min());
302 QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min());
303 QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min());
304
305 QCOMPARE(nullTz1.hasTransitions(), false);
306
307 data = nullTz1.nextTransition(afterDateTime: jan);
308 QCOMPARE(data.atUtc, QDateTime());
309 QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min());
310 QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min());
311 QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min());
312
313 data = nullTz1.previousTransition(beforeDateTime: jan);
314 QCOMPARE(data.atUtc, QDateTime());
315 QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min());
316 QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min());
317 QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min());
318}
319
320void tst_QTimeZone::systemZone()
321{
322 const QTimeZone zone = QTimeZone::systemTimeZone();
323 QVERIFY(zone.isValid());
324 QCOMPARE(zone.id(), QTimeZone::systemTimeZoneId());
325 QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId()));
326}
327
328void tst_QTimeZone::dataStreamTest()
329{
330 // Test the OffsetFromUtc backend serialization. First with a custom timezone:
331 QTimeZone tz1("QST", 123456, "Qt Standard Time", "QST", QLocale::Norway, "Qt Testing");
332 QByteArray tmp;
333 {
334 QDataStream ds(&tmp, QIODevice::WriteOnly);
335 ds << tz1;
336 }
337 QTimeZone tz2("UTC");
338 {
339 QDataStream ds(&tmp, QIODevice::ReadOnly);
340 ds >> tz2;
341 }
342 QCOMPARE(tz2.id(), QByteArray("QST"));
343 QCOMPARE(tz2.comment(), QString("Qt Testing"));
344 QCOMPARE(tz2.country(), QLocale::Norway);
345 QCOMPARE(tz2.abbreviation(QDateTime::currentDateTime()), QString("QST"));
346 QCOMPARE(tz2.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QString()),
347 QString("Qt Standard Time"));
348 QCOMPARE(tz2.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, QString()),
349 QString("Qt Standard Time"));
350 QCOMPARE(tz2.offsetFromUtc(QDateTime::currentDateTime()), 123456);
351
352 // And then with a standard IANA timezone (QTBUG-60595):
353 tz1 = QTimeZone("UTC");
354 QCOMPARE(tz1.isValid(), true);
355 {
356 QDataStream ds(&tmp, QIODevice::WriteOnly);
357 ds << tz1;
358 }
359 {
360 QDataStream ds(&tmp, QIODevice::ReadOnly);
361 ds >> tz2;
362 }
363 QCOMPARE(tz2.isValid(), true);
364 QCOMPARE(tz2.id(), tz1.id());
365
366 // Test the system backend serialization
367 tz1 = QTimeZone("Pacific/Auckland");
368
369 // If not valid then probably using the UTC system backend so skip
370 if (!tz1.isValid())
371 return;
372
373 {
374 QDataStream ds(&tmp, QIODevice::WriteOnly);
375 ds << tz1;
376 }
377 tz2 = QTimeZone("UTC");
378 {
379 QDataStream ds(&tmp, QIODevice::ReadOnly);
380 ds >> tz2;
381 }
382 QCOMPARE(tz2.id(), tz1.id());
383}
384
385void tst_QTimeZone::isTimeZoneIdAvailable()
386{
387 QList<QByteArray> available = QTimeZone::availableTimeZoneIds();
388 foreach (const QByteArray &id, available) {
389 QVERIFY(QTimeZone::isTimeZoneIdAvailable(id));
390 QVERIFY(QTimeZone(id).isValid());
391 }
392}
393
394void tst_QTimeZone::utcOffsetId_data()
395{
396 QTest::addColumn<QByteArray>(name: "id");
397 QTest::addColumn<bool>(name: "valid");
398 QTest::addColumn<int>(name: "offset"); // ignored unless valid
399
400 // Some of these are actual CLDR zone IDs, some are known Windows IDs; the
401 // rest rely on parsing the offset. Since CLDR and Windows may add to their
402 // known IDs, which fall in which category may vary. Only the CLDR and
403 // Windows ones are known to isTimeZoneAvailable() or listed in
404 // availableTimeZoneIds().
405#define ROW(name, valid, offset) \
406 QTest::newRow(name) << QByteArray(name) << valid << offset
407
408 // See qtbase/util/locale_database/cldr2qtimezone.py for source
409 // CLDR v35.1 IDs:
410 ROW("UTC", true, 0);
411 ROW("UTC-14:00", true, -50400);
412 ROW("UTC-13:00", true, -46800);
413 ROW("UTC-12:00", true, -43200);
414 ROW("UTC-11:00", true, -39600);
415 ROW("UTC-10:00", true, -36000);
416 ROW("UTC-09:00", true, -32400);
417 ROW("UTC-08:00", true, -28800);
418 ROW("UTC-07:00", true, -25200);
419 ROW("UTC-06:00", true, -21600);
420 ROW("UTC-05:00", true, -18000);
421 ROW("UTC-04:30", true, -16200);
422 ROW("UTC-04:00", true, -14400);
423 ROW("UTC-03:30", true, -12600);
424 ROW("UTC-03:00", true, -10800);
425 ROW("UTC-02:00", true, -7200);
426 ROW("UTC-01:00", true, -3600);
427 ROW("UTC-00:00", true, 0);
428 ROW("UTC+00:00", true, 0);
429 ROW("UTC+01:00", true, 3600);
430 ROW("UTC+02:00", true, 7200);
431 ROW("UTC+03:00", true, 10800);
432 ROW("UTC+03:30", true, 12600);
433 ROW("UTC+04:00", true, 14400);
434 ROW("UTC+04:30", true, 16200);
435 ROW("UTC+05:00", true, 18000);
436 ROW("UTC+05:30", true, 19800);
437 ROW("UTC+05:45", true, 20700);
438 ROW("UTC+06:00", true, 21600);
439 ROW("UTC+06:30", true, 23400);
440 ROW("UTC+07:00", true, 25200);
441 ROW("UTC+08:00", true, 28800);
442 ROW("UTC+08:30", true, 30600);
443 ROW("UTC+09:00", true, 32400);
444 ROW("UTC+09:30", true, 34200);
445 ROW("UTC+10:00", true, 36000);
446 ROW("UTC+11:00", true, 39600);
447 ROW("UTC+12:00", true, 43200);
448 ROW("UTC+13:00", true, 46800);
449 ROW("UTC+14:00", true, 50400);
450
451 // Windows IDs known to CLDR v35.1:
452 ROW("UTC-11", true, -39600);
453 ROW("UTC-09", true, -32400);
454 ROW("UTC-08", true, -28800);
455 ROW("UTC-02", true, -7200);
456 ROW("UTC+12", true, 43200);
457 ROW("UTC+13", true, 46800);
458 // Encountered in bug reports:
459 ROW("UTC+10", true, 36000); // QTBUG-77738
460
461 // Bounds:
462 ROW("UTC+23", true, 82800);
463 ROW("UTC-23", true, -82800);
464 ROW("UTC+23:59", true, 86340);
465 ROW("UTC-23:59", true, -86340);
466 ROW("UTC+23:59:59", true, 86399);
467 ROW("UTC-23:59:59", true, -86399);
468
469 // Out of range
470 ROW("UTC+24:0:0", false, 0);
471 ROW("UTC-24:0:0", false, 0);
472 ROW("UTC+0:60:0", false, 0);
473 ROW("UTC-0:60:0", false, 0);
474 ROW("UTC+0:0:60", false, 0);
475 ROW("UTC-0:0:60", false, 0);
476
477 // Malformed
478 ROW("UTC+", false, 0);
479 ROW("UTC-", false, 0);
480 ROW("UTC10", false, 0);
481 ROW("UTC:10", false, 0);
482 ROW("UTC+cabbage", false, 0);
483 ROW("UTC+10:rice", false, 0);
484 ROW("UTC+9:3:oat", false, 0);
485 ROW("UTC+9+3", false, 0);
486 ROW("UTC+9-3", false, 0);
487 ROW("UTC+9:3-4", false, 0);
488 ROW("UTC+9:3:4:more", false, 0);
489 ROW("UTC+9:3:4:5", false, 0);
490}
491
492void tst_QTimeZone::utcOffsetId()
493{
494 QFETCH(QByteArray, id);
495 QFETCH(bool, valid);
496 QTimeZone zone(id);
497 QCOMPARE(zone.isValid(), valid);
498 if (valid) {
499 QDateTime epoch(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::UTC);
500 QFETCH(int, offset);
501 QCOMPARE(zone.offsetFromUtc(epoch), offset);
502 QVERIFY(!zone.hasDaylightTime());
503 QCOMPARE(zone.id(), id);
504 }
505}
506
507void tst_QTimeZone::specificTransition_data()
508{
509 QTest::addColumn<QByteArray>(name: "zone");
510 QTest::addColumn<QDate>(name: "start");
511 QTest::addColumn<QDate>(name: "stop");
512 QTest::addColumn<int>(name: "count");
513 QTest::addColumn<QDateTime>(name: "atUtc");
514 // In minutes:
515 QTest::addColumn<int>(name: "offset");
516 QTest::addColumn<int>(name: "stdoff");
517 QTest::addColumn<int>(name: "dstoff");
518
519 // Moscow ditched DST on 2010-10-31 but has since changed standard offset twice.
520#ifdef USING_WIN_TZ
521 // Win7 is too old to know about this transition:
522 if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7)
523#endif
524 {
525 QTest::newRow(dataTag: "Moscow/2014") // From original bug-report
526 << QByteArray("Europe/Moscow")
527 << QDate(2011, 4, 1) << QDate(2017, 12,31) << 1
528 << QDateTime(QDate(2014, 10, 26), QTime(2, 0, 0),
529 Qt::OffsetFromUTC, 4 * 3600).toUTC()
530 << 3 * 3600 << 3 * 3600 << 0;
531 }
532 QTest::newRow(dataTag: "Moscow/2011") // Transition on 2011-03-27
533 << QByteArray("Europe/Moscow")
534 << QDate(2010, 11, 1) << QDate(2014, 10, 25) << 1
535 << QDateTime(QDate(2011, 3, 27), QTime(2, 0, 0),
536 Qt::OffsetFromUTC, 3 * 3600).toUTC()
537 << 4 * 3600 << 4 * 3600 << 0;
538}
539
540void tst_QTimeZone::specificTransition()
541{
542 // Regression test for QTBUG-42021 (on MS-Win)
543 QFETCH(QByteArray, zone);
544 QFETCH(QDate, start);
545 QFETCH(QDate, stop);
546 QFETCH(int, count);
547 // No attempt to check abbreviations; to much cross-platform variation.
548 QFETCH(QDateTime, atUtc);
549 QFETCH(int, offset);
550 QFETCH(int, stdoff);
551 QFETCH(int, dstoff);
552
553 QTimeZone timeZone(zone);
554 if (!timeZone.isValid())
555 QSKIP("Missing time-zone data");
556 QTimeZone::OffsetDataList transits =
557 timeZone.transitions(fromDateTime: QDateTime(start, QTime(0, 0), timeZone),
558 toDateTime: QDateTime(stop, QTime(23, 59), timeZone));
559 QCOMPARE(transits.length(), count);
560 const QTimeZone::OffsetData &transition = transits.at(i: 0);
561 QCOMPARE(transition.offsetFromUtc, offset);
562 QCOMPARE(transition.standardTimeOffset, stdoff);
563 QCOMPARE(transition.daylightTimeOffset, dstoff);
564 QCOMPARE(transition.atUtc, atUtc);
565}
566
567void tst_QTimeZone::transitionEachZone_data()
568{
569 QTest::addColumn<QByteArray>(name: "zone");
570 QTest::addColumn<qint64>(name: "secs");
571 QTest::addColumn<int>(name: "start");
572 QTest::addColumn<int>(name: "stop");
573
574 struct {
575 qint64 baseSecs;
576 int start, stop;
577 int year;
578 } table[] = {
579 { .baseSecs: 25666200, .start: 3, .stop: 12, .year: 1970 }, // 1970-10-25 01:30 UTC; North America
580 { .baseSecs: 1288488600, .start: -4, .stop: 8, .year: 2010 } // 2010-10-31 01:30 UTC; Europe, Russia
581 };
582
583 const auto zones = QTimeZone::availableTimeZoneIds();
584 for (int k = sizeof(table) / sizeof(table[0]); k-- > 0; ) {
585 for (const QByteArray &zone : zones) {
586 const QString name = QString::asprintf(format: "%s@%d", zone.constData(), table[k].year);
587 QTest::newRow(dataTag: name.toUtf8().constData())
588 << zone
589 << table[k].baseSecs
590 << table[k].start
591 << table[k].stop;
592 }
593 }
594}
595
596void tst_QTimeZone::transitionEachZone()
597{
598 // Regression test: round-trip fromMsecs/toMSecs should be idempotent; but
599 // various zones failed during fall-back transitions.
600 QFETCH(QByteArray, zone);
601 QFETCH(qint64, secs);
602 QFETCH(int, start);
603 QFETCH(int, stop);
604 QTimeZone named(zone);
605
606 for (int i = start; i < stop; i++) {
607#ifdef USING_WIN_TZ
608 // See QTBUG-64985: MS's TZ APIs' misdescription of Europe/Samara leads
609 // to mis-disambiguation of its fall-back here.
610 if (zone == "Europe/Samara" && i == -3) {
611 continue;
612 }
613#endif
614 qint64 here = secs + i * 3600;
615 QDateTime when = QDateTime::fromMSecsSinceEpoch(msecs: here * 1000, timeZone: named);
616 qint64 stamp = when.toMSecsSinceEpoch();
617 if (here * 1000 != stamp) // (The +1 is due to using *1*:30 as baseSecs.)
618 qDebug() << "Failing for" << zone << "at half past" << (i + 1) << "UTC";
619 QCOMPARE(stamp % 1000, 0);
620 QCOMPARE(here - stamp / 1000, 0);
621 }
622}
623
624void tst_QTimeZone::checkOffset_data()
625{
626 QTest::addColumn<QByteArray>(name: "zoneName");
627 QTest::addColumn<QDateTime>(name: "when");
628 QTest::addColumn<int>(name: "netOffset");
629 QTest::addColumn<int>(name: "stdOffset");
630 QTest::addColumn<int>(name: "dstOffset");
631
632 struct {
633 const char *zone, *nick;
634 int year, month, day, hour, min, sec;
635 int std, dst;
636 } table[] = {
637 // Zone with no transitions (QTBUG-74614, QTBUG-74666, when TZ backend uses minimal data)
638 { .zone: "Etc/UTC", .nick: "epoch", .year: 1970, .month: 1, .day: 1, .hour: 0, .min: 0, .sec: 0, .std: 0, .dst: 0 },
639 { .zone: "Etc/UTC", .nick: "pre_int32", .year: 1901, .month: 12, .day: 13, .hour: 20, .min: 45, .sec: 51, .std: 0, .dst: 0 },
640 { .zone: "Etc/UTC", .nick: "post_int32", .year: 2038, .month: 1, .day: 19, .hour: 3, .min: 14, .sec: 9, .std: 0, .dst: 0 },
641 { .zone: "Etc/UTC", .nick: "post_uint32", .year: 2106, .month: 2, .day: 7, .hour: 6, .min: 28, .sec: 17, .std: 0, .dst: 0 },
642 { .zone: "Etc/UTC", .nick: "initial", .year: -292275056, .month: 5, .day: 16, .hour: 16, .min: 47, .sec: 5, .std: 0, .dst: 0 },
643 { .zone: "Etc/UTC", .nick: "final", .year: 292278994, .month: 8, .day: 17, .hour: 7, .min: 12, .sec: 55, .std: 0, .dst: 0 },
644 // Kiev: regression test for QTBUG-64122 (on MS):
645 { .zone: "Europe/Kiev", .nick: "summer", .year: 2017, .month: 10, .day: 27, .hour: 12, .min: 0, .sec: 0, .std: 2 * 3600, .dst: 3600 },
646 { .zone: "Europe/Kiev", .nick: "winter", .year: 2017, .month: 10, .day: 29, .hour: 12, .min: 0, .sec: 0, .std: 2 * 3600, .dst: 0 }
647 };
648 for (const auto &entry : table) {
649 QTimeZone zone(entry.zone);
650 if (zone.isValid()) {
651 QTest::addRow(format: "%s@%s", entry.zone, entry.nick)
652 << QByteArray(entry.zone)
653 << QDateTime(QDate(entry.year, entry.month, entry.day),
654 QTime(entry.hour, entry.min, entry.sec), zone)
655 << entry.dst + entry.std << entry.std << entry.dst;
656 } else {
657 qWarning(msg: "Skipping %s@%s test as zone is invalid", entry.zone, entry.nick);
658 }
659 }
660}
661
662void tst_QTimeZone::checkOffset()
663{
664 QFETCH(QByteArray, zoneName);
665 QFETCH(QDateTime, when);
666 QFETCH(int, netOffset);
667 QFETCH(int, stdOffset);
668 QFETCH(int, dstOffset);
669
670 QTimeZone zone(zoneName);
671 QVERIFY(zone.isValid()); // It was when _data() added the row !
672 QCOMPARE(zone.offsetFromUtc(when), netOffset);
673 QCOMPARE(zone.standardTimeOffset(when), stdOffset);
674 QCOMPARE(zone.daylightTimeOffset(when), dstOffset);
675 QCOMPARE(zone.isDaylightTime(when), dstOffset != 0);
676}
677
678void tst_QTimeZone::availableTimeZoneIds()
679{
680 if (debug) {
681 qDebug() << "";
682 qDebug() << "Available Time Zones" ;
683 qDebug() << QTimeZone::availableTimeZoneIds();
684 qDebug() << "";
685 qDebug() << "Available Time Zones in the US";
686 qDebug() << QTimeZone::availableTimeZoneIds(country: QLocale::UnitedStates);
687 qDebug() << "";
688 qDebug() << "Available Time Zones with UTC Offset 0";
689 qDebug() << QTimeZone::availableTimeZoneIds(offsetSeconds: 0);
690 qDebug() << "";
691 } else {
692 //Just test the calls work, we cannot know what any test machine has available
693 QList<QByteArray> listAll = QTimeZone::availableTimeZoneIds();
694 QList<QByteArray> listUs = QTimeZone::availableTimeZoneIds(country: QLocale::UnitedStates);
695 QList<QByteArray> listZero = QTimeZone::availableTimeZoneIds(offsetSeconds: 0);
696 }
697}
698
699void tst_QTimeZone::stressTest()
700{
701 QList<QByteArray> idList = QTimeZone::availableTimeZoneIds();
702 foreach (const QByteArray &id, idList) {
703 QTimeZone testZone = QTimeZone(id);
704 QCOMPARE(testZone.isValid(), true);
705 QCOMPARE(testZone.id(), id);
706 QDateTime testDate = QDateTime(QDate(2015, 1, 1), QTime(0, 0, 0), Qt::UTC);
707 testZone.country();
708 testZone.comment();
709 testZone.displayName(atDateTime: testDate);
710 testZone.displayName(timeType: QTimeZone::DaylightTime);
711 testZone.displayName(timeType: QTimeZone::StandardTime);
712 testZone.abbreviation(atDateTime: testDate);
713 testZone.offsetFromUtc(atDateTime: testDate);
714 testZone.standardTimeOffset(atDateTime: testDate);
715 testZone.daylightTimeOffset(atDateTime: testDate);
716 testZone.hasDaylightTime();
717 testZone.isDaylightTime(atDateTime: testDate);
718 testZone.offsetData(forDateTime: testDate);
719 testZone.hasTransitions();
720 testZone.nextTransition(afterDateTime: testDate);
721 testZone.previousTransition(beforeDateTime: testDate);
722 // Dates known to be outside possible tz file pre-calculated rules range
723 QDateTime lowDate1 = QDateTime(QDate(1800, 1, 1), QTime(0, 0, 0), Qt::UTC);
724 QDateTime lowDate2 = QDateTime(QDate(1800, 6, 1), QTime(0, 0, 0), Qt::UTC);
725 QDateTime highDate1 = QDateTime(QDate(2200, 1, 1), QTime(0, 0, 0), Qt::UTC);
726 QDateTime highDate2 = QDateTime(QDate(2200, 6, 1), QTime(0, 0, 0), Qt::UTC);
727 testZone.nextTransition(afterDateTime: lowDate1);
728 testZone.nextTransition(afterDateTime: lowDate2);
729 testZone.previousTransition(beforeDateTime: lowDate2);
730 testZone.previousTransition(beforeDateTime: lowDate2);
731 testZone.nextTransition(afterDateTime: highDate1);
732 testZone.nextTransition(afterDateTime: highDate2);
733 testZone.previousTransition(beforeDateTime: highDate1);
734 testZone.previousTransition(beforeDateTime: highDate2);
735 if (debug) {
736 // This could take a long time, depending on platform and database
737 qDebug() << "Stress test calculating transistions for" << testZone.id();
738 testZone.transitions(fromDateTime: lowDate1, toDateTime: highDate1);
739 }
740 testDate.setTimeZone(testZone);
741 testDate.isValid();
742 testDate.offsetFromUtc();
743 testDate.timeZoneAbbreviation();
744 }
745}
746
747void tst_QTimeZone::windowsId()
748{
749/*
750 Current Windows zones for "Central Standard Time":
751 Region IANA Id(s)
752 Default "America/Chicago"
753 Canada "America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute"
754 Mexico "America/Matamoros"
755 USA "America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee"
756 "America/North_Dakota/Beulah America/North_Dakota/Center"
757 "America/North_Dakota/New_Salem"
758 AnyCountry "CST6CDT"
759*/
760 QCOMPARE(QTimeZone::ianaIdToWindowsId("America/Chicago"),
761 QByteArray("Central Standard Time"));
762 QCOMPARE(QTimeZone::ianaIdToWindowsId("America/Resolute"),
763 QByteArray("Central Standard Time"));
764
765 // Partials shouldn't match
766 QCOMPARE(QTimeZone::ianaIdToWindowsId("America/Chi"), QByteArray());
767 QCOMPARE(QTimeZone::ianaIdToWindowsId("InvalidZone"), QByteArray());
768 QCOMPARE(QTimeZone::ianaIdToWindowsId(QByteArray()), QByteArray());
769
770 // Check default value
771 QCOMPARE(QTimeZone::windowsIdToDefaultIanaId("Central Standard Time"),
772 QByteArray("America/Chicago"));
773 QCOMPARE(QTimeZone::windowsIdToDefaultIanaId("Central Standard Time", QLocale::Canada),
774 QByteArray("America/Winnipeg"));
775 QCOMPARE(QTimeZone::windowsIdToDefaultIanaId("Central Standard Time", QLocale::AnyCountry),
776 QByteArray("CST6CDT"));
777 QCOMPARE(QTimeZone::windowsIdToDefaultIanaId(QByteArray()), QByteArray());
778
779 // No country is sorted list of all zones
780 QList<QByteArray> list;
781 list << "America/Chicago" << "America/Indiana/Knox" << "America/Indiana/Tell_City"
782 << "America/Matamoros" << "America/Menominee" << "America/North_Dakota/Beulah"
783 << "America/North_Dakota/Center" << "America/North_Dakota/New_Salem"
784 << "America/Rainy_River" << "America/Rankin_Inlet" << "America/Resolute"
785 << "America/Winnipeg" << "CST6CDT";
786 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time"), list);
787
788 // Check country with no match returns empty list
789 list.clear();
790 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::NewZealand),
791 list);
792
793 // Check valid country returns list in preference order
794 list.clear();
795 list << "America/Winnipeg" << "America/Rainy_River" << "America/Rankin_Inlet"
796 << "America/Resolute";
797 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Canada), list);
798
799 list.clear();
800 list << "America/Matamoros";
801 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Mexico), list);
802
803 list.clear();
804 list << "America/Chicago" << "America/Indiana/Knox" << "America/Indiana/Tell_City"
805 << "America/Menominee" << "America/North_Dakota/Beulah" << "America/North_Dakota/Center"
806 << "America/North_Dakota/New_Salem";
807 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::UnitedStates),
808 list);
809
810 list.clear();
811 list << "CST6CDT";
812 QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::AnyCountry),
813 list);
814
815 // Check no windowsId return empty
816 list.clear();
817 QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray()), list);
818 QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray(), QLocale::AnyCountry), list);
819}
820
821void tst_QTimeZone::isValidId_data()
822{
823#ifdef QT_BUILD_INTERNAL
824 QTest::addColumn<QByteArray>(name: "input");
825 QTest::addColumn<bool>(name: "valid");
826
827 // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
828 // Can't start with '-'
829 // Parts separated by '/', each part min 1 and max of 14 chars
830 // (Android has parts with lengths up to 17, so tolerates this as a special case.)
831#define TESTSET(name, section, valid) \
832 QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \
833 QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \
834 QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid
835
836 // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
837 // Can't start with '-'
838 // Parts separated by '/', each part min 1 and max of 14 chars
839 TESTSET("empty", "", false);
840 TESTSET("minimal", "m", true);
841#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
842 TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this
843 TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this.
844#else
845 TESTSET("maximal", "12345678901234", true);
846 TESTSET("maximal twice", "12345678901234/12345678901234", true);
847 TESTSET("too long", "123456789012345", false);
848 TESTSET("too-long/maximal", "123456789012345/12345678901234", false);
849 TESTSET("maximal/too-long", "12345678901234/123456789012345", false);
850#endif
851
852 TESTSET("bad hyphen", "-hyphen", false);
853 TESTSET("good hyphen", "hy-phen", true);
854
855 TESTSET("valid char _", "_", true);
856 TESTSET("valid char .", ".", true);
857 TESTSET("valid char :", ":", true);
858 TESTSET("valid char +", "+", true);
859 TESTSET("valid char A", "A", true);
860 TESTSET("valid char Z", "Z", true);
861 TESTSET("valid char a", "a", true);
862 TESTSET("valid char z", "z", true);
863 TESTSET("valid char 0", "0", true);
864 TESTSET("valid char 9", "9", true);
865
866 TESTSET("valid pair az", "az", true);
867 TESTSET("valid pair AZ", "AZ", true);
868 TESTSET("valid pair 09", "09", true);
869 TESTSET("valid pair .z", ".z", true);
870 TESTSET("valid pair _z", "_z", true);
871 TESTSET("invalid pair -z", "-z", false);
872
873 TESTSET("valid triple a/z", "a/z", true);
874 TESTSET("valid triple a.z", "a.z", true);
875 TESTSET("valid triple a-z", "a-z", true);
876 TESTSET("valid triple a_z", "a_z", true);
877 TESTSET("invalid triple a z", "a z", false);
878 TESTSET("invalid triple a\\z", "a\\z", false);
879 TESTSET("invalid triple a,z", "a,z", false);
880
881 TESTSET("invalid space", " ", false);
882 TESTSET("invalid char ^", "^", false);
883 TESTSET("invalid char \"", "\"", false);
884 TESTSET("invalid char $", "$", false);
885 TESTSET("invalid char %", "%", false);
886 TESTSET("invalid char &", "&", false);
887 TESTSET("invalid char (", "(", false);
888 TESTSET("invalid char )", ")", false);
889 TESTSET("invalid char =", "=", false);
890 TESTSET("invalid char -", "-", false);
891 TESTSET("invalid char ?", "?", false);
892 TESTSET("invalid char ß", "ß", false);
893 TESTSET("invalid char \\x01", "\x01", false);
894 TESTSET("invalid char ' '", " ", false);
895
896#undef TESTSET
897
898 QTest::newRow(dataTag: "az alone") << QByteArray("az") << true;
899 QTest::newRow(dataTag: "AZ alone") << QByteArray("AZ") << true;
900 QTest::newRow(dataTag: "09 alone") << QByteArray("09") << true;
901 QTest::newRow(dataTag: "a/z alone") << QByteArray("a/z") << true;
902 QTest::newRow(dataTag: "a.z alone") << QByteArray("a.z") << true;
903 QTest::newRow(dataTag: "a-z alone") << QByteArray("a-z") << true;
904 QTest::newRow(dataTag: "a_z alone") << QByteArray("a_z") << true;
905 QTest::newRow(dataTag: ".z alone") << QByteArray(".z") << true;
906 QTest::newRow(dataTag: "_z alone") << QByteArray("_z") << true;
907 QTest::newRow(dataTag: "a z alone") << QByteArray("a z") << false;
908 QTest::newRow(dataTag: "a\\z alone") << QByteArray("a\\z") << false;
909 QTest::newRow(dataTag: "a,z alone") << QByteArray("a,z") << false;
910 QTest::newRow(dataTag: "/z alone") << QByteArray("/z") << false;
911 QTest::newRow(dataTag: "-z alone") << QByteArray("-z") << false;
912#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
913 QTest::newRow("long alone") << QByteArray("12345678901234567") << true;
914 QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false;
915#else
916 QTest::newRow(dataTag: "long alone") << QByteArray("12345678901234") << true;
917 QTest::newRow(dataTag: "over-long alone") << QByteArray("123456789012345") << false;
918#endif
919
920#else
921 QSKIP("This test requires a Qt -developer-build.");
922#endif // QT_BUILD_INTERNAL
923}
924
925void tst_QTimeZone::isValidId()
926{
927#ifdef QT_BUILD_INTERNAL
928 QFETCH(QByteArray, input);
929 QFETCH(bool, valid);
930
931 QCOMPARE(QTimeZonePrivate::isValidId(input), valid);
932#endif
933}
934
935void tst_QTimeZone::malformed()
936{
937 // Regression test for QTBUG-92808
938 // Strings that look enough like a POSIX zone specifier that the constructor
939 // accepts them, but the specifier is invalid.
940 // Must not crash or trigger assertions when calling offsetFromUtc()
941 const QDateTime now = QDateTime::currentDateTime();
942 QTimeZone barf("QUT4tCZ0 , /");
943 if (barf.isValid())
944 barf.offsetFromUtc(atDateTime: now);
945 barf = QTimeZone("QtC+09,,MA");
946 if (barf.isValid())
947 barf.offsetFromUtc(atDateTime: now);
948}
949
950void tst_QTimeZone::utcTest()
951{
952#ifdef QT_BUILD_INTERNAL
953 // Test default UTC constructor
954 QUtcTimeZonePrivate tzp;
955 QCOMPARE(tzp.isValid(), true);
956 QCOMPARE(tzp.id(), QByteArray("UTC"));
957 QCOMPARE(tzp.country(), QLocale::AnyCountry);
958 QCOMPARE(tzp.abbreviation(0), QString("UTC"));
959 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QString()), QString("UTC"));
960 QCOMPARE(tzp.offsetFromUtc(0), 0);
961 QCOMPARE(tzp.standardTimeOffset(0), 0);
962 QCOMPARE(tzp.daylightTimeOffset(0), 0);
963 QCOMPARE(tzp.hasDaylightTime(), false);
964 QCOMPARE(tzp.hasTransitions(), false);
965
966 // Test create from UTC Offset (uses minimal id, skipping minutes if 0)
967 QDateTime now = QDateTime::currentDateTime();
968 QTimeZone tz(36000);
969 QVERIFY(tz.isValid());
970 QCOMPARE(tz.id(), QByteArray("UTC+10"));
971 QCOMPARE(tz.offsetFromUtc(now), 36000);
972 QCOMPARE(tz.standardTimeOffset(now), 36000);
973 QCOMPARE(tz.daylightTimeOffset(now), 0);
974
975 // Test invalid UTC offset, must be in range -14 to +14 hours
976 int min = -14*60*60;
977 int max = 14*60*60;
978 QCOMPARE(QTimeZone(min - 1).isValid(), false);
979 QCOMPARE(QTimeZone(min).isValid(), true);
980 QCOMPARE(QTimeZone(min + 1).isValid(), true);
981 QCOMPARE(QTimeZone(max - 1).isValid(), true);
982 QCOMPARE(QTimeZone(max).isValid(), true);
983 QCOMPARE(QTimeZone(max + 1).isValid(), false);
984
985 // Test create from standard name (preserves :00 for minutes in id):
986 tz = QTimeZone("UTC+10:00");
987 QVERIFY(tz.isValid());
988 QCOMPARE(tz.id(), QByteArray("UTC+10:00"));
989 QCOMPARE(tz.offsetFromUtc(now), 36000);
990 QCOMPARE(tz.standardTimeOffset(now), 36000);
991 QCOMPARE(tz.daylightTimeOffset(now), 0);
992
993 // Test create custom zone
994 tz = QTimeZone("QST", 123456, "Qt Standard Time", "QST", QLocale::Norway, "Qt Testing");
995 QCOMPARE(tz.isValid(), true);
996 QCOMPARE(tz.id(), QByteArray("QST"));
997 QCOMPARE(tz.comment(), QString("Qt Testing"));
998 QCOMPARE(tz.country(), QLocale::Norway);
999 QCOMPARE(tz.abbreviation(now), QString("QST"));
1000 QCOMPARE(tz.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QString()),
1001 QString("Qt Standard Time"));
1002 QCOMPARE(tz.offsetFromUtc(now), 123456);
1003 QCOMPARE(tz.standardTimeOffset(now), 123456);
1004 QCOMPARE(tz.daylightTimeOffset(now), 0);
1005#endif // QT_BUILD_INTERNAL
1006}
1007
1008void tst_QTimeZone::icuTest()
1009{
1010#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu)
1011 // Known datetimes
1012 qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1013 qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1014
1015 // Test default constructor
1016 QIcuTimeZonePrivate tzpd;
1017 QVERIFY(tzpd.isValid());
1018
1019 // Test invalid constructor
1020 QIcuTimeZonePrivate tzpi("Gondwana/Erewhon");
1021 QCOMPARE(tzpi.isValid(), false);
1022
1023 // Test named constructor
1024 QIcuTimeZonePrivate tzp("Europe/Berlin");
1025 QVERIFY(tzp.isValid());
1026
1027 // Only test names in debug mode, names used can vary by ICU version installed
1028 if (debug) {
1029 // Test display names by type
1030 QLocale enUS("en_US");
1031 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS),
1032 QString("Central European Standard Time"));
1033 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS),
1034 QString("GMT+01:00"));
1035 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS),
1036 QString("UTC+01:00"));
1037 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS),
1038 QString("Central European Summer Time"));
1039 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS),
1040 QString("GMT+02:00"));
1041 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS),
1042 QString("UTC+02:00"));
1043 // ICU C api does not support Generic Time yet, C++ api does
1044 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS),
1045 QString("Central European Standard Time"));
1046 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS),
1047 QString("GMT+01:00"));
1048 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS),
1049 QString("UTC+01:00"));
1050
1051 // Test Abbreviations
1052 QCOMPARE(tzp.abbreviation(std), QString("CET"));
1053 QCOMPARE(tzp.abbreviation(dst), QString("CEST"));
1054 }
1055
1056 testCetPrivate(tzp);
1057 testEpochTranPrivate(tzp: QIcuTimeZonePrivate("America/Toronto"));
1058#endif // icu
1059}
1060
1061void tst_QTimeZone::tzTest()
1062{
1063#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX && !defined Q_OS_DARWIN && !defined Q_OS_ANDROID
1064 // Known datetimes
1065 qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1066 qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1067
1068 // Test default constructor
1069 QTzTimeZonePrivate tzpd;
1070 QVERIFY(tzpd.isValid());
1071
1072 // Test invalid constructor
1073 QTzTimeZonePrivate tzpi("Gondwana/Erewhon");
1074 QCOMPARE(tzpi.isValid(), false);
1075
1076 // Test named constructor
1077 QTzTimeZonePrivate tzp("Europe/Berlin");
1078 QVERIFY(tzp.isValid());
1079
1080 // Test POSIX-format value for $TZ:
1081 QTzTimeZonePrivate tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00");
1082 QVERIFY(tzposix.isValid());
1083
1084 QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule
1085 QVERIFY(tzBrazil.isValid());
1086 QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800);
1087
1088 // Test display names by type, either ICU or abbreviation only
1089 QLocale enUS("en_US");
1090 // Only test names in debug mode, names used can vary by ICU version installed
1091 if (debug) {
1092#if QT_CONFIG(icu)
1093 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS),
1094 QString("Central European Standard Time"));
1095 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS),
1096 QString("GMT+01:00"));
1097 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS),
1098 QString("UTC+01:00"));
1099 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS),
1100 QString("Central European Summer Time"));
1101 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS),
1102 QString("GMT+02:00"));
1103 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS),
1104 QString("UTC+02:00"));
1105 // ICU C api does not support Generic Time yet, C++ api does
1106 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS),
1107 QString("Central European Standard Time"));
1108 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS),
1109 QString("GMT+01:00"));
1110 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS),
1111 QString("UTC+01:00"));
1112#else
1113 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS),
1114 QString("CET"));
1115 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS),
1116 QString("CET"));
1117 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS),
1118 QString("CET"));
1119 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS),
1120 QString("CEST"));
1121 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS),
1122 QString("CEST"));
1123 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS),
1124 QString("CEST"));
1125 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS),
1126 QString("CET"));
1127 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS),
1128 QString("CET"));
1129 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS),
1130 QString("CET"));
1131#endif // icu
1132
1133 // Test Abbreviations
1134 QCOMPARE(tzp.abbreviation(std), QString("CET"));
1135 QCOMPARE(tzp.abbreviation(dst), QString("CEST"));
1136 }
1137
1138 testCetPrivate(tzp);
1139 testEpochTranPrivate(tzp: QTzTimeZonePrivate("America/Toronto"));
1140
1141 // Test first and last transition rule
1142 // Warning: This could vary depending on age of TZ file!
1143
1144 // Test low date uses first rule found
1145 // Note: Depending on the OS in question, the database may be carrying the
1146 // Local Mean Time. which for Berlin is 0:53:28
1147 QTimeZonePrivate::Data dat = tzp.data(forMSecsSinceEpoch: -9999999999999);
1148 QCOMPARE(dat.atMSecsSinceEpoch, (qint64)-9999999999999);
1149 QCOMPARE(dat.daylightTimeOffset, 0);
1150 if (dat.abbreviation == "LMT") {
1151 QCOMPARE(dat.standardTimeOffset, 3208);
1152 } else {
1153 QCOMPARE(dat.standardTimeOffset, 3600);
1154
1155 // Test previous to low value is invalid
1156 dat = tzp.previousTransition(beforeMSecsSinceEpoch: -9999999999999);
1157 QCOMPARE(dat.atMSecsSinceEpoch, std::numeric_limits<qint64>::min());
1158 QCOMPARE(dat.standardTimeOffset, std::numeric_limits<int>::min());
1159 QCOMPARE(dat.daylightTimeOffset, std::numeric_limits<int>::min());
1160 }
1161
1162 dat = tzp.nextTransition(afterMSecsSinceEpoch: -9999999999999);
1163 QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600),
1164 QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), Qt::OffsetFromUTC, 3600));
1165 QCOMPARE(dat.standardTimeOffset, 3600);
1166 QCOMPARE(dat.daylightTimeOffset, 0);
1167
1168 // Date-times late enough to exercise POSIX rules:
1169 qint64 stdHi = QDate(2100, 1, 1).startOfDay(spec: Qt::UTC).toMSecsSinceEpoch();
1170 qint64 dstHi = QDate(2100, 6, 1).startOfDay(spec: Qt::UTC).toMSecsSinceEpoch();
1171 // Relevant last Sundays in October and March:
1172 QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday);
1173 QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday);
1174 QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday);
1175
1176 dat = tzp.data(forMSecsSinceEpoch: stdHi);
1177 QCOMPARE(dat.atMSecsSinceEpoch - stdHi, (qint64)0);
1178 QCOMPARE(dat.offsetFromUtc, 3600);
1179 QCOMPARE(dat.standardTimeOffset, 3600);
1180 QCOMPARE(dat.daylightTimeOffset, 0);
1181
1182 dat = tzp.data(forMSecsSinceEpoch: dstHi);
1183 QCOMPARE(dat.atMSecsSinceEpoch - dstHi, (qint64)0);
1184 QCOMPARE(dat.offsetFromUtc, 7200);
1185 QCOMPARE(dat.standardTimeOffset, 3600);
1186 QCOMPARE(dat.daylightTimeOffset, 3600);
1187
1188 dat = tzp.previousTransition(beforeMSecsSinceEpoch: stdHi);
1189 QCOMPARE(dat.abbreviation, QStringLiteral("CET"));
1190 QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC),
1191 QDateTime(QDate(2099, 10, 25), QTime(3, 0), Qt::OffsetFromUTC, 7200));
1192 QCOMPARE(dat.offsetFromUtc, 3600);
1193 QCOMPARE(dat.standardTimeOffset, 3600);
1194 QCOMPARE(dat.daylightTimeOffset, 0);
1195
1196 dat = tzp.previousTransition(beforeMSecsSinceEpoch: dstHi);
1197 QCOMPARE(dat.abbreviation, QStringLiteral("CEST"));
1198 QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC),
1199 QDateTime(QDate(2100, 3, 28), QTime(2, 0), Qt::OffsetFromUTC, 3600));
1200 QCOMPARE(dat.offsetFromUtc, 7200);
1201 QCOMPARE(dat.standardTimeOffset, 3600);
1202 QCOMPARE(dat.daylightTimeOffset, 3600);
1203
1204 dat = tzp.nextTransition(afterMSecsSinceEpoch: stdHi);
1205 QCOMPARE(dat.abbreviation, QStringLiteral("CEST"));
1206 QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC),
1207 QDateTime(QDate(2100, 3, 28), QTime(2, 0), Qt::OffsetFromUTC, 3600));
1208 QCOMPARE(dat.offsetFromUtc, 7200);
1209 QCOMPARE(dat.standardTimeOffset, 3600);
1210 QCOMPARE(dat.daylightTimeOffset, 3600);
1211
1212 dat = tzp.nextTransition(afterMSecsSinceEpoch: dstHi);
1213 QCOMPARE(dat.abbreviation, QStringLiteral("CET"));
1214 QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600),
1215 QDateTime(QDate(2100, 10, 31), QTime(3, 0), Qt::OffsetFromUTC, 7200));
1216 QCOMPARE(dat.offsetFromUtc, 3600);
1217 QCOMPARE(dat.standardTimeOffset, 3600);
1218 QCOMPARE(dat.daylightTimeOffset, 0);
1219
1220 // Test TZ timezone vs UTC timezone for fractionary negative offset
1221 QTzTimeZonePrivate tztz1("America/Caracas");
1222 QUtcTimeZonePrivate tzutc1("UTC-04:30");
1223 QVERIFY(tztz1.isValid());
1224 QVERIFY(tzutc1.isValid());
1225 QTzTimeZonePrivate::Data datatz1 = tztz1.data(forMSecsSinceEpoch: std);
1226 QTzTimeZonePrivate::Data datautc1 = tzutc1.data(forMSecsSinceEpoch: std);
1227 QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc);
1228
1229 // Test TZ timezone vs UTC timezone for fractionary positive offset
1230 QTzTimeZonePrivate tztz2("Asia/Calcutta");
1231 QUtcTimeZonePrivate tzutc2("UTC+05:30");
1232 QVERIFY(tztz2.isValid());
1233 QVERIFY(tzutc2.isValid());
1234 QTzTimeZonePrivate::Data datatz2 = tztz2.data(forMSecsSinceEpoch: std);
1235 QTzTimeZonePrivate::Data datautc2 = tzutc2.data(forMSecsSinceEpoch: std);
1236 QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc);
1237
1238 // Test a timezone with a name that isn't all letters
1239 QTzTimeZonePrivate tzBarnaul("Asia/Barnaul");
1240 if (tzBarnaul.isValid()) {
1241 QCOMPARE(tzBarnaul.data(std).abbreviation, QString("+07"));
1242
1243 // first full day of the new rule (tzdata2016b)
1244 QDateTime dt(QDate(2016, 3, 28), QTime(0, 0, 0), Qt::UTC);
1245 QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, QString("+07"));
1246 }
1247#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !Q_OS_DARWIN
1248}
1249
1250void tst_QTimeZone::macTest()
1251{
1252#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN)
1253 // Known datetimes
1254 qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1255 qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1256
1257 // Test default constructor
1258 QMacTimeZonePrivate tzpd;
1259 QVERIFY(tzpd.isValid());
1260
1261 // Test invalid constructor
1262 QMacTimeZonePrivate tzpi("Gondwana/Erewhon");
1263 QCOMPARE(tzpi.isValid(), false);
1264
1265 // Test named constructor
1266 QMacTimeZonePrivate tzp("Europe/Berlin");
1267 QVERIFY(tzp.isValid());
1268
1269 // Only test names in debug mode, names used can vary by version
1270 if (debug) {
1271 // Test display names by type
1272 QLocale enUS("en_US");
1273 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS),
1274 QString("Central European Standard Time"));
1275 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS),
1276 QString("GMT+01:00"));
1277 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS),
1278 QString("UTC+01:00"));
1279 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS),
1280 QString("Central European Summer Time"));
1281 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS),
1282 QString("GMT+02:00"));
1283 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS),
1284 QString("UTC+02:00"));
1285 // ICU C api does not support Generic Time yet, C++ api does
1286 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS),
1287 QString("Central European Time"));
1288 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS),
1289 QString("Germany Time"));
1290 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS),
1291 QString("UTC+01:00"));
1292
1293 // Test Abbreviations
1294 QCOMPARE(tzp.abbreviation(std), QString("CET"));
1295 QCOMPARE(tzp.abbreviation(dst), QString("CEST"));
1296 }
1297
1298 testCetPrivate(tzp);
1299 testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto"));
1300#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN
1301}
1302
1303void tst_QTimeZone::darwinTypes()
1304{
1305#ifndef Q_OS_DARWIN
1306 QSKIP("This is an Apple-only test");
1307#else
1308 extern void tst_QTimeZone_darwinTypes(); // in tst_qtimezone_darwin.mm
1309 tst_QTimeZone_darwinTypes();
1310#endif
1311}
1312
1313void tst_QTimeZone::winTest()
1314{
1315#if defined(QT_BUILD_INTERNAL) && defined(USING_WIN_TZ)
1316 // Known datetimes
1317 qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1318 qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1319
1320 // Test default constructor
1321 QWinTimeZonePrivate tzpd;
1322 if (debug)
1323 qDebug() << "System ID = " << tzpd.id()
1324 << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale())
1325 << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale());
1326 QVERIFY(tzpd.isValid());
1327
1328 // Test invalid constructor
1329 QWinTimeZonePrivate tzpi("Gondwana/Erewhon");
1330 QCOMPARE(tzpi.isValid(), false);
1331
1332 // Test named constructor
1333 QWinTimeZonePrivate tzp("Europe/Berlin");
1334 QVERIFY(tzp.isValid());
1335
1336 // Only test names in debug mode, names used can vary by version
1337 if (debug) {
1338 // Test display names by type
1339 QLocale enUS("en_US");
1340 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS),
1341 QString("W. Europe Standard Time"));
1342 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS),
1343 QString("W. Europe Standard Time"));
1344 QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS),
1345 QString("UTC+01:00"));
1346 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS),
1347 QString("W. Europe Daylight Time"));
1348 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS),
1349 QString("W. Europe Daylight Time"));
1350 QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS),
1351 QString("UTC+02:00"));
1352 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS),
1353 QString("(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"));
1354 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS),
1355 QString("(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"));
1356 QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS),
1357 QString("UTC+01:00"));
1358
1359 // Test Abbreviations
1360 QCOMPARE(tzp.abbreviation(std), QString("W. Europe Standard Time"));
1361 QCOMPARE(tzp.abbreviation(dst), QString("W. Europe Daylight Time"));
1362 }
1363
1364 testCetPrivate(tzp);
1365 testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto"));
1366#endif // QT_BUILD_INTERNAL && USING_WIN_TZ
1367}
1368
1369#ifdef QT_BUILD_INTERNAL
1370// Test each private produces the same basic results for CET
1371void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp)
1372{
1373 // Known datetimes
1374 qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1375 qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1376 qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
1377
1378 QCOMPARE(tzp.offsetFromUtc(std), 3600);
1379 QCOMPARE(tzp.offsetFromUtc(dst), 7200);
1380
1381 QCOMPARE(tzp.standardTimeOffset(std), 3600);
1382 QCOMPARE(tzp.standardTimeOffset(dst), 3600);
1383
1384 QCOMPARE(tzp.daylightTimeOffset(std), 0);
1385 QCOMPARE(tzp.daylightTimeOffset(dst), 3600);
1386
1387 QCOMPARE(tzp.hasDaylightTime(), true);
1388 QCOMPARE(tzp.isDaylightTime(std), false);
1389 QCOMPARE(tzp.isDaylightTime(dst), true);
1390
1391 QTimeZonePrivate::Data dat = tzp.data(forMSecsSinceEpoch: std);
1392 QCOMPARE(dat.atMSecsSinceEpoch, std);
1393 QCOMPARE(dat.offsetFromUtc, 3600);
1394 QCOMPARE(dat.standardTimeOffset, 3600);
1395 QCOMPARE(dat.daylightTimeOffset, 0);
1396 QCOMPARE(dat.abbreviation, tzp.abbreviation(std));
1397
1398 dat = tzp.data(forMSecsSinceEpoch: dst);
1399 QCOMPARE(dat.atMSecsSinceEpoch, dst);
1400 QCOMPARE(dat.offsetFromUtc, 7200);
1401 QCOMPARE(dat.standardTimeOffset, 3600);
1402 QCOMPARE(dat.daylightTimeOffset, 3600);
1403 QCOMPARE(dat.abbreviation, tzp.abbreviation(dst));
1404
1405 // Only test transitions if host system supports them
1406 if (tzp.hasTransitions()) {
1407 QTimeZonePrivate::Data tran = tzp.nextTransition(afterMSecsSinceEpoch: std);
1408 // 2012-03-25 02:00 CET, +1 -> +2
1409 QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC),
1410 QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600));
1411 QCOMPARE(tran.offsetFromUtc, 7200);
1412 QCOMPARE(tran.standardTimeOffset, 3600);
1413 QCOMPARE(tran.daylightTimeOffset, 3600);
1414
1415 tran = tzp.nextTransition(afterMSecsSinceEpoch: dst);
1416 // 2012-10-28 03:00 CEST, +2 -> +1
1417 QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC),
1418 QDateTime(QDate(2012, 10, 28), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600));
1419 QCOMPARE(tran.offsetFromUtc, 3600);
1420 QCOMPARE(tran.standardTimeOffset, 3600);
1421 QCOMPARE(tran.daylightTimeOffset, 0);
1422
1423 tran = tzp.previousTransition(beforeMSecsSinceEpoch: std);
1424 // 2011-10-30 03:00 CEST, +2 -> +1
1425 QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC),
1426 QDateTime(QDate(2011, 10, 30), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600));
1427 QCOMPARE(tran.offsetFromUtc, 3600);
1428 QCOMPARE(tran.standardTimeOffset, 3600);
1429 QCOMPARE(tran.daylightTimeOffset, 0);
1430
1431 tran = tzp.previousTransition(beforeMSecsSinceEpoch: dst);
1432 // 2012-03-25 02:00 CET, +1 -> +2 (again)
1433 QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC),
1434 QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600));
1435 QCOMPARE(tran.offsetFromUtc, 7200);
1436 QCOMPARE(tran.standardTimeOffset, 3600);
1437 QCOMPARE(tran.daylightTimeOffset, 3600);
1438
1439 QTimeZonePrivate::DataList expected;
1440 // 2011-03-27 02:00 CET, +1 -> +2
1441 tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0),
1442 Qt::OffsetFromUTC, 3600).toMSecsSinceEpoch();
1443 tran.offsetFromUtc = 7200;
1444 tran.standardTimeOffset = 3600;
1445 tran.daylightTimeOffset = 3600;
1446 expected << tran;
1447 // 2011-10-30 03:00 CEST, +2 -> +1
1448 tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0),
1449 Qt::OffsetFromUTC, 2 * 3600).toMSecsSinceEpoch();
1450 tran.offsetFromUtc = 3600;
1451 tran.standardTimeOffset = 3600;
1452 tran.daylightTimeOffset = 0;
1453 expected << tran;
1454 QTimeZonePrivate::DataList result = tzp.transitions(fromMSecsSinceEpoch: prev, toMSecsSinceEpoch: std);
1455 QCOMPARE(result.count(), expected.count());
1456 for (int i = 0; i < expected.count(); ++i) {
1457 QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch,
1458 Qt::OffsetFromUTC, 3600),
1459 QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch,
1460 Qt::OffsetFromUTC, 3600));
1461 QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
1462 QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
1463 QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
1464 }
1465 }
1466}
1467
1468// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT)
1469void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp)
1470{
1471 if (!tzp.hasTransitions())
1472 return; // test only viable for transitions
1473
1474 QTimeZonePrivate::Data tran = tzp.nextTransition(afterMSecsSinceEpoch: 0); // i.e. first after epoch
1475 // 1970-04-26 02:00 EST, -5 -> -4
1476 const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), Qt::OffsetFromUTC, -5 * 3600);
1477 const QDateTime found = QDateTime::fromMSecsSinceEpoch(msecs: tran.atMSecsSinceEpoch, spec: Qt::UTC);
1478#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th.
1479 QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time());
1480#else
1481 QCOMPARE(found, after);
1482#endif
1483 QCOMPARE(tran.offsetFromUtc, -4 * 3600);
1484 QCOMPARE(tran.standardTimeOffset, -5 * 3600);
1485 QCOMPARE(tran.daylightTimeOffset, 3600);
1486
1487 // Pre-epoch time-zones might not be supported at all:
1488 tran = tzp.nextTransition(afterMSecsSinceEpoch: QDateTime(QDate(1601, 1, 1), QTime(0, 0),
1489 Qt::UTC).toMSecsSinceEpoch());
1490 if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()
1491 // Toronto *did* have a transition before 1970 (DST since 1918):
1492 && tran.atMSecsSinceEpoch < 0) {
1493 // ... but, if they are, we should be able to search back to them:
1494 tran = tzp.previousTransition(beforeMSecsSinceEpoch: 0); // i.e. last before epoch
1495 // 1969-10-26 02:00 EDT, -4 -> -5
1496 QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC),
1497 QDateTime(QDate(1969, 10, 26), QTime(2, 0), Qt::OffsetFromUTC, -4 * 3600));
1498 QCOMPARE(tran.offsetFromUtc, -5 * 3600);
1499 QCOMPARE(tran.standardTimeOffset, -5 * 3600);
1500 QCOMPARE(tran.daylightTimeOffset, 0);
1501 } else {
1502 // Do not use QSKIP(): that would discard the rest of this sub-test's caller.
1503 qDebug() << "No support for pre-epoch time-zone transitions";
1504 }
1505}
1506#endif // QT_BUILD_INTERNAL
1507
1508QTEST_APPLESS_MAIN(tst_QTimeZone)
1509#include "tst_qtimezone.moc"
1510

source code of qtbase/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp