Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qgregoriancalendar.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "qcalendarmath_p.h"
6
7#include <QtCore/qdatetime.h>
8
10
11using namespace QRoundingDown;
12
13// Verification that QRoundingDown::qDivMod() works correctly:
14static_assert(qDivMod<2>(-86400).quotient == -43200);
15static_assert(qDivMod<2>(-86400).remainder == 0);
16static_assert(qDivMod<86400>(-86400).quotient == -1);
17static_assert(qDivMod<86400>(-86400).remainder == 0);
18static_assert(qDivMod<86400>(-86401).quotient == -2);
19static_assert(qDivMod<86400>(-86401).remainder == 86399);
20static_assert(qDivMod<86400>(-100000).quotient == -2);
21static_assert(qDivMod<86400>(-100000).remainder == 72800);
22static_assert(qDivMod<86400>(-172799).quotient == -2);
23static_assert(qDivMod<86400>(-172799).remainder == 1);
24static_assert(qDivMod<86400>(-172800).quotient == -2);
25static_assert(qDivMod<86400>(-172800).remainder == 0);
26
27// Uncomment to verify error on bad denominator is clear and intelligible:
28// static_assert(qDivMod<1>(17).remainder == 0);
29// static_assert(qDivMod<0>(17).remainder == 0);
30// static_assert(qDivMod<std::numeric_limits<unsigned>::max()>(17).remainder == 0);
31
48{
49 return QStringLiteral("Gregorian");
50}
51
53{
54 return {
55 QStringLiteral("Gregorian"),
56 QStringLiteral("gregory"),
57 };
58}
59
61{
62 return leapTest(year);
63}
64
66{
67 if (year == QCalendar::Unspecified)
68 return false;
69
70 // No year 0 in Gregorian calendar, so -1, -5, -9 etc are leap years
71 if (year < 1)
72 ++year;
73
74 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
75}
76
77// Duplicating code from QRomanCalendar, but inlining isLeapYear() as leapTest():
78int QGregorianCalendar::monthLength(int month, int year)
79{
80 if (month < 1 || month > 12)
81 return 0;
82
83 if (month == 2)
84 return leapTest(year) ? 29 : 28;
85
86 return 30 | ((month & 1) ^ (month >> 3));
87}
88
89bool QGregorianCalendar::validParts(int year, int month, int day)
90{
91 return year && 0 < day && day <= monthLength(month, year);
92}
93
95{
96 return int(qMod<7>(jd) + 1);
97}
98
99bool QGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
100{
101 const auto maybe = julianFromParts(year, month, day);
102 if (maybe)
103 *jd = *maybe;
104 return bool(maybe);
105}
106
108{
109 return partsFromJulian(jd);
110}
111
113{
114 // Equivalent to weekDayOfJulian(julianForParts({year, 1, 1})
115 const int y = year - (year < 0 ? 800 : 801);
116 return qMod<7>(y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y)) + 1;
117}
118
120{
121 // Returns a post-epoch year, no later than 2400, that has the same pattern
122 // of week-days (in the proleptic Gregorian calendar) as the year in which
123 // the given date falls. This will be the year in question if it's in the
124 // given range. Otherwise, the returned year's last two (decimal) digits
125 // won't coincide with the month number or day-of-month of the given date.
126 // For positive years, except when necessary to avoid such a clash, the
127 // returned year's last two digits shall coincide with those of the original
128 // year.
129
130 // Needed when formatting dates using system APIs with limited year ranges
131 // and possibly only a two-digit year. (The need to be able to safely
132 // replace the two-digit form of the returned year with a suitable form of
133 // the true year, when they don't coincide, is why the last two digits are
134 // treated specially.)
135
136 static_assert((400 * 365 + 97) % 7 == 0);
137 // A full 400-year cycle of the Gregorian calendar has 97 + 400 * 365 days;
138 // as 365 is one more than a multiple of seven and 497 is a multiple of
139 // seven, that full cycle is a whole number of weeks. So adding a multiple
140 // of four hundred years should get us a result that meets our needs.
141
142 const int year = date.year();
143 int res = (year < 1970
144 ? 2400 - (2000 - (year < 0 ? year + 1 : year)) % 400
145 : year > 2399 ? 2000 + (year - 2000) % 400 : year);
146 Q_ASSERT(res > 0);
147 if (res != year) {
148 const int lastTwo = res % 100;
149 if (lastTwo == date.month() || lastTwo == date.day()) {
150 Q_ASSERT(lastTwo && !(lastTwo & ~31));
151 // Last two digits of these years are all > 31:
152 static constexpr int usual[] = { 2198, 2199, 2098, 2099, 2399, 2298, 2299 };
153 static constexpr int leaps[] = { 2396, 2284, 2296, 2184, 2196, 2084, 2096 };
154 // Indexing is: first day of year's day-of-week, Monday = 0, one less
155 // than Qt's, as it's simpler to subtract one than to s/7/0/.
156 res = (leapTest(year) ? leaps : usual)[yearStartWeekDay(year) - 1];
157 }
158 Q_ASSERT(QDate(res, 1, 1).dayOfWeek() == QDate(year, 1, 1).dayOfWeek());
159 Q_ASSERT(QDate(res, 12, 31).dayOfWeek() == QDate(year, 12, 31).dayOfWeek());
160 }
161 Q_ASSERT(res >= 1970 && res <= 2400);
162 return res;
163}
164
165/*
166 * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php
167 * This formula is correct for all julian days, when using mathematical integer
168 * division (round to negative infinity), not c++11 integer division (round to zero).
169 *
170 * The source given uses 4801 BCE as base date; the following adjusts that by
171 * 4800 years to simplify part of the arithmetic (and match more closely what we
172 * do for Milankovic).
173 */
174
175using namespace QRomanCalendrical;
176// End a Gregorian four-century cycle on 1 BC's leap day:
178// Every four centures there are 97 leap years:
179constexpr unsigned FourCenturies = 400 * 365 + 97;
180
181std::optional<qint64> QGregorianCalendar::julianFromParts(int year, int month, int day)
182{
183 if (!validParts(year, month, day))
184 return std::nullopt;
185
186 const auto yearDays = yearMonthToYearDays(year, month);
187 const qint64 y = yearDays.year;
188 const qint64 fromYear = 365 * y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y);
189 return fromYear + yearDays.days + day + BaseJd;
190}
191
193{
194 const qint64 dayNumber = jd - BaseJd;
195 const qint64 century = qDiv<FourCenturies>(4 * dayNumber - 1);
196 const int dayInCentury = dayNumber - qDiv<4>(FourCenturies * century);
197
198 const int yearInCentury = qDiv<FourYears>(4 * dayInCentury - 1);
199 const int dayInYear = dayInCentury - qDiv<4>(FourYears * yearInCentury);
200 const int m = qDiv<FiveMonths>(5 * dayInYear - 3);
201 Q_ASSERT(m < 12 && m >= 0);
202 // That m is a month adjusted to March = 0, with Jan = 10, Feb = 11 in the previous year.
203 const int yearOffset = m < 10 ? 0 : 1;
204
205 const int y = 100 * century + yearInCentury + yearOffset;
206 const int month = m + 3 - 12 * yearOffset;
207 const int day = dayInYear - qDiv<5>(FiveMonths * m + 2);
208
209 // Adjust for no year 0
210 return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
211}
212
virtual int dayOfWeek(qint64 jd) const
Returns the day of the week for the given Julian Day Number jd.
@ Unspecified
Definition qcalendar.h:57
\inmodule QtCore \reentrant
Definition qdatetime.h:27
int month() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int day() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int year() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static std::optional< qint64 > julianFromParts(int year, int month, int day)
static int yearStartWeekDay(int year)
QCalendar::YearMonthDay julianDayToDate(qint64 jd) const override
Computes the year, month, and day in this calendar for the given Julian day number jd.
static int monthLength(int month, int year)
static QStringList nameList()
static bool leapTest(int year)
bool dateToJulianDay(int year, int month, int day, qint64 *jd) const override
Computes the Julian day number corresponding to the specified year, month, and day.
QString name() const override
Returns the primary name of the calendar.
static QCalendar::YearMonthDay partsFromJulian(qint64 jd)
static bool validParts(int year, int month, int day)
static int yearSharingWeekDays(QDate date)
bool isLeapYear(int year) const override
Returns true if the specified year is a leap year for this calendar.
static int weekDayOfJulian(qint64 jd)
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QDate date
[1]
constexpr qint64 LeapDayGregorian1Bce
constexpr auto yearMonthToYearDays(int year, int month)
constexpr unsigned FourYears
constexpr unsigned FiveMonths
Combined button and popup list for selecting options.
constexpr qint64 BaseJd
constexpr unsigned FourCenturies
const GLfloat * m
GLint y
GLuint res
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
long long qint64
Definition qtypes.h:55