1/****************************************************************************
2**
3** Copyright (C) 2020 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
31#include <QCalendar>
32Q_DECLARE_METATYPE(QCalendar::System)
33
34class tst_QCalendar : public QObject
35{
36 Q_OBJECT
37private:
38 void checkYear(const QCalendar &cal, int year, bool normal=false);
39
40private slots:
41 void basic_data();
42 void basic();
43 void unspecified_data() { basic_data(); }
44 void unspecified();
45 void nameCase();
46 void specific_data();
47 void specific();
48 void daily_data() { basic_data(); }
49 void daily();
50 void properties_data();
51 void properties();
52 void aliases();
53};
54
55// Support for basic():
56void tst_QCalendar::checkYear(const QCalendar &cal, int year, bool normal)
57{
58 const int moons = cal.monthsInYear(year);
59 // Months are numbered from 1 to moons:
60 QVERIFY(moons > 0);
61 QVERIFY(!cal.isDateValid(year, moons + 1, 1));
62 QVERIFY(!cal.isDateValid(year, 0, 1));
63 QVERIFY(!QDate(year, 0, 1, cal).isValid());
64 QVERIFY(moons <= cal.maximumMonthsInYear());
65 QCOMPARE(cal.standaloneMonthName(QLocale::c(), moons + 1, year), QString());
66 QCOMPARE(cal.monthName(QLocale::c(), 0, year), QString());
67
68 const int days = cal.daysInYear(year);
69 QVERIFY(days > 0);
70
71 int sum = 0;
72 const int longest = cal.maximumDaysInMonth();
73 for (int i = moons; i > 0; i--) {
74 const int last = cal.daysInMonth(month: i, year);
75 sum += last;
76 // Valid month has some days and no more than max:
77 QVERIFY(last > 0);
78 QVERIFY(last <= longest);
79 // Days are numbered from 1 to last:
80 QVERIFY(cal.isDateValid(year, i, 1));
81 QVERIFY(cal.isDateValid(year, i, last));
82 QVERIFY(!cal.isDateValid(year, i, 0));
83 QVERIFY(!cal.isDateValid(year, i, last + 1));
84 if (normal) // Unspecified year gets same daysInMonth():
85 QCOMPARE(cal.daysInMonth(i), last);
86 }
87 // Months add up to the whole year:
88 QCOMPARE(sum, days);
89}
90
91#define CHECKYEAR(cal, year) checkYear(cal, year); \
92 if (QTest::currentTestFailed()) \
93 return
94
95#define NORMALYEAR(cal, year) checkYear(cal, year, true); \
96 if (QTest::currentTestFailed()) \
97 return
98
99void tst_QCalendar::basic_data()
100{
101 QTest::addColumn<QCalendar::System>(name: "system");
102
103 QMetaEnum e = QCalendar::staticMetaObject.enumerator(index: 0);
104 Q_ASSERT(qstrcmp(e.name(), "System") == 0);
105
106 for (int i = 0; i <= int(QCalendar::System::Last); ++i) {
107 // There may be gaps in the enum's numbering; and Last is a duplicate:
108 if (e.value(index: i) != -1 && qstrcmp(str1: e.key(index: i), str2: "Last"))
109 QTest::newRow(dataTag: e.key(index: i)) << QCalendar::System(e.value(index: i));
110 }
111}
112
113void tst_QCalendar::basic()
114{
115 QFETCH(QCalendar::System, system);
116 QCalendar cal(system);
117 QVERIFY(cal.isValid());
118 QCOMPARE(QCalendar(cal.name()).isGregorian(), cal.isGregorian());
119 QCOMPARE(QCalendar(cal.name()).name(), cal.name());
120
121 if (cal.hasYearZero()) {
122 CHECKYEAR(cal, 0);
123 } else {
124 QCOMPARE(cal.monthsInYear(0), 0);
125 QCOMPARE(cal.daysInYear(0), 0);
126 QVERIFY(!cal.isDateValid(0, 1, 1));
127 QVERIFY(!QDate(0, 1, 1, cal).isValid());
128 }
129
130 if (cal.isProleptic()) {
131 CHECKYEAR(cal, -1);
132 } else {
133 QCOMPARE(cal.monthsInYear(-1), 0);
134 QCOMPARE(cal.daysInYear(-1), 0);
135 QVERIFY(!cal.isDateValid(-1, 1, 1));
136 }
137
138 // Look for a leap year in the last decade.
139 int year = QDate::currentDate().year(cal);
140 for (int i = 10; i > 0 && !cal.isLeapYear(year); --i)
141 --year;
142 if (cal.isLeapYear(year)) {
143 // ... and a non-leap year within a decade before it.
144 int leap = year--;
145 for (int i = 10; i > 0 && cal.isLeapYear(year); --i)
146 year--;
147 if (!cal.isLeapYear(year))
148 QVERIFY(cal.daysInYear(year) < cal.daysInYear(leap));
149
150 CHECKYEAR(cal, leap);
151 }
152 // Either year is non-leap or we have a decade of leap years together;
153 // expect daysInMonth() to treat year the same as unspecified.
154 NORMALYEAR(cal, year);
155}
156
157void tst_QCalendar::unspecified()
158{
159 QFETCH(QCalendar::System, system);
160 QCalendar cal(system);
161
162 const QDate today = QDate::currentDate();
163 const int thisYear = today.year();
164 QCOMPARE(cal.monthsInYear(QCalendar::Unspecified), cal.maximumMonthsInYear());
165 for (int month = cal.maximumMonthsInYear(); month > 0; month--) {
166 const int days = cal.daysInMonth(month);
167 int count = 0;
168 // 19 years = one Metonic cycle (used by some lunar calendars)
169 for (int i = 19; i > 0; --i) {
170 if (cal.daysInMonth(month, year: thisYear - i) == days)
171 count++;
172 }
173 // Require a majority of the years tested:
174 QVERIFY2(count > 9, "Default daysInMonth() should be for a normal year");
175 }
176}
177
178void tst_QCalendar::nameCase()
179{
180 QVERIFY(QCalendar::availableCalendars().contains(QStringLiteral("Gregorian")));
181}
182
183void tst_QCalendar::specific_data()
184{
185 QTest::addColumn<QCalendar::System>(name: "system");
186 // Date in that system:
187 QTest::addColumn<QString>(name: "monthName");
188 QTest::addColumn<int>(name: "sysyear");
189 QTest::addColumn<int>(name: "sysmonth");
190 QTest::addColumn<int>(name: "sysday");
191 // Gregorian equivalent:
192 QTest::addColumn<int>(name: "gregyear");
193 QTest::addColumn<int>(name: "gregmonth");
194 QTest::addColumn<int>(name: "gregday");
195
196#define ADDROW(cal, monthName, year, month, day, gy, gm, gd) \
197 QTest::newRow(#cal) << QCalendar::System::cal << QStringLiteral(monthName) \
198 << year << month << day << gy << gm << gd
199
200 ADDROW(Gregorian, "January", 1970, 1, 1, 1970, 1, 1);
201
202 // One known specific date, for each calendar
203#ifndef QT_BOOTSTRAPPED
204 // Julian 1582-10-4 was followed by Gregorian 1582-10-15
205 ADDROW(Julian, "October", 1582, 10, 4, 1582, 10, 14);
206 // Milankovic matches Gregorian for a few centuries
207 ADDROW(Milankovic, "March", 1923, 3, 20, 1923, 3, 20);
208#endif
209
210#if QT_CONFIG(jalalicalendar)
211 // Jalali year 1355 started on Gregorian 1976-3-21:
212 ADDROW(Jalali, "Farvardin", 1355, 1, 1, 1976, 3, 21);
213#endif // jalali
214#if QT_CONFIG(islamiccivilcalendar)
215 // TODO: confirm this is correct
216 ADDROW(IslamicCivil, "Muharram", 1, 1, 1, 622, 7, 19);
217#endif
218
219#undef ADDROW
220}
221
222void tst_QCalendar::specific()
223{
224 QFETCH(QCalendar::System, system);
225 QFETCH(const QString, monthName);
226 QFETCH(int, sysyear);
227 QFETCH(int, sysmonth);
228 QFETCH(int, sysday);
229 QFETCH(int, gregyear);
230 QFETCH(int, gregmonth);
231 QFETCH(int, gregday);
232
233 const QCalendar cal(system);
234 QCOMPARE(cal.monthName(QLocale::c(), sysmonth), monthName);
235 const QDate date(sysyear, sysmonth, sysday, cal), gregory(gregyear, gregmonth, gregday);
236 QCOMPARE(date, gregory);
237 QCOMPARE(gregory.year(cal), sysyear);
238 QCOMPARE(gregory.month(cal), sysmonth);
239 QCOMPARE(gregory.day(cal), sysday);
240 QCOMPARE(date.year(), gregyear);
241 QCOMPARE(date.month(), gregmonth);
242 QCOMPARE(date.day(), gregday);
243}
244
245void tst_QCalendar::daily()
246{
247 QFETCH(QCalendar::System, system);
248 QCalendar calendar(system);
249 const quint64 startJDN = 0, endJDN = 2488070;
250 // Iterate from -4713-01-01 (Julian calendar) to 2100-01-01
251 for (quint64 expect = startJDN; expect <= endJDN; ++expect)
252 {
253 QDate date = QDate::fromJulianDay(jd_: expect);
254 auto parts = calendar.partsFromDate(date);
255 if (!parts.isValid())
256 continue;
257
258 const int year = date.year(cal: calendar);
259 QCOMPARE(year, parts.year);
260 const int month = date.month(cal: calendar);
261 QCOMPARE(month, parts.month);
262 const int day = date.day(cal: calendar);
263 QCOMPARE(day, parts.day);
264 const quint64 actual = QDate(year, month, day, calendar).toJulianDay();
265 QCOMPARE(actual, expect);
266 }
267}
268
269void tst_QCalendar::properties_data()
270{
271 QTest::addColumn<QCalendar::System>(name: "system");
272 QTest::addColumn<bool>(name: "gregory");
273 QTest::addColumn<bool>(name: "lunar");
274 QTest::addColumn<bool>(name: "luniSolar");
275 QTest::addColumn<bool>(name: "solar");
276 QTest::addColumn<bool>(name: "proleptic");
277 QTest::addColumn<bool>(name: "yearZero");
278 QTest::addColumn<int>(name: "monthMax");
279 QTest::addColumn<int>(name: "monthMin");
280 QTest::addColumn<int>(name: "yearMax");
281 QTest::addColumn<QString>(name: "name");
282
283 QTest::addRow(format: "Gregorian")
284 << QCalendar::System::Gregorian << true << false << false << true << true << false
285 << 31 << 28 << 12 << QStringLiteral("Gregorian");
286#ifndef QT_BOOTSTRAPPED
287 QTest::addRow(format: "Julian")
288 << QCalendar::System::Julian << false << false << false << true << true << false
289 << 31 << 28 << 12 << QStringLiteral("Julian");
290 QTest::addRow(format: "Milankovic")
291 << QCalendar::System::Milankovic << false << false << false << true << true << false
292 << 31 << 28 << 12 << QStringLiteral("Milankovic");
293#endif
294
295#if QT_CONFIG(jalalicalendar)
296 QTest::addRow(format: "Jalali")
297 << QCalendar::System::Jalali << false << false << false << true << true << false
298 << 31 << 29 << 12 << QStringLiteral("Jalali");
299#endif
300#if QT_CONFIG(islamiccivilcalendar)
301 QTest::addRow(format: "IslamicCivil")
302 << QCalendar::System::IslamicCivil << false << true << false << false << true << false
303 << 30 << 29 << 12 << QStringLiteral("Islamic Civil");
304#endif
305}
306
307void tst_QCalendar::properties()
308{
309 QFETCH(const QCalendar::System, system);
310 QFETCH(const bool, gregory);
311 QFETCH(const bool, lunar);
312 QFETCH(const bool, luniSolar);
313 QFETCH(const bool, solar);
314 QFETCH(const bool, proleptic);
315 QFETCH(const bool, yearZero);
316 QFETCH(const int, monthMax);
317 QFETCH(const int, monthMin);
318 QFETCH(const int, yearMax);
319 QFETCH(const QString, name);
320
321 const QCalendar cal(system);
322 QCOMPARE(cal.isGregorian(), gregory);
323 QCOMPARE(cal.isLunar(), lunar);
324 QCOMPARE(cal.isLuniSolar(), luniSolar);
325 QCOMPARE(cal.isSolar(), solar);
326 QCOMPARE(cal.isProleptic(), proleptic);
327 QCOMPARE(cal.hasYearZero(), yearZero);
328 QCOMPARE(cal.maximumDaysInMonth(), monthMax);
329 QCOMPARE(cal.minimumDaysInMonth(), monthMin);
330 QCOMPARE(cal.maximumMonthsInYear(), yearMax);
331 QCOMPARE(cal.name(), name);
332}
333
334void tst_QCalendar::aliases()
335{
336 QCOMPARE(QCalendar(u"gregory").name(), u"Gregorian");
337#if QT_CONFIG(jalalicalendar)
338 QCOMPARE(QCalendar(u"Persian").name(), u"Jalali");
339#endif
340#if QT_CONFIG(islamiccivilcalendar)
341 // Exercise all constructors from name, while we're at it:
342 QCOMPARE(QCalendar(u"islamic-civil").name(), u"Islamic Civil");
343 QCOMPARE(QCalendar(QLatin1String("islamic")).name(), u"Islamic Civil");
344 QCOMPARE(QCalendar(QStringLiteral("Islamic")).name(), u"Islamic Civil");
345#endif
346
347 // Invalid is handled gracefully:
348 QCOMPARE(QCalendar(u"").name(), QString());
349 QCOMPARE(QCalendar(QCalendar::System::User).name(), QString());
350}
351
352QTEST_APPLESS_MAIN(tst_QCalendar)
353#include "tst_qcalendar.moc"
354

source code of qtbase/tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp