Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qjalalicalendar.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// Qt-Security score:significant reason:default
4
5#include "qglobal.h"
9#include <QtCore/qmath.h>
10
12
13using namespace QRoundingDown;
14
15// Constants
16
17constexpr qint64 cycleDays = 1029983;
18constexpr int cycleYears = 2820;
19constexpr double yearLength = 365.24219858156028368; // 365 + 683 / 2820.
20constexpr qint64 jalaliEpoch = 2121446; // 475/01/01 AP, start of 2820 cycle
21// This appears to be based on Ahmad Birashk's algorithm.
22
23// Calendar implementation
24
25static inline int cycle(qint64 jdn)
26{
27 return qDiv<cycleDays>(jdn - jalaliEpoch);
28}
29
30static qint64 cycleStart(int cycleNo)
31{
32 return jalaliEpoch + cycleNo * qint64(cycleDays);
33}
34
35static qint64 firstDayOfYear(int year, int cycleNo)
36{
37 using std::floor;
38 qint64 firstDOYinEra
39 = QtPrivate::qCheckedFPConversionToInteger<qint64>(floor(year * yearLength));
40 return jalaliEpoch + cycleNo * qint64(cycleDays) + firstDOYinEra;
41}
42
43// Promoted to qint64 to avoid overflows and handle julianDayToDate()'s qint64.
44static bool isLeap(qint64 year)
45{
46 // This handles a year < 0 ? year + 1 : year adjusted year, or a value due
47 // to be subjected to the reverse of that to deliver a year.
48 return qMod<2820>((year + 2346) * 683) < 683;
49}
50
51/*!
52 \since 5.14
53 \internal
54
55 \class QJalaliCalendar
56 \inmodule QtCore
57 \brief The QJalaliCalendar class provides Jalali (Hijri Shamsi) calendar
58 system implementation.
59
60 \section1 Solar Hijri Calendar System
61
62 The Solar Hijri calendar, also called the Solar Hejri calendar, Shamsi
63 Hijri calendar or Jalali calendar, is the official calendar of Iran and
64 Afghanistan. It begins on the vernal equinox (Nowruz) as determined by
65 astronomical calculation for the Iran Standard Time meridian
66 (52.5°E or GMT+3.5h). This determination of starting moment is more accurate
67 than the Gregorian calendar for predicting the date of the vernal equinox,
68 because it uses astronomical observations rather than mathematical rules.
69
70 \section2 Calendar Organization
71
72 Each of the twelve months corresponds with a zodiac sign. The first six
73 months have 31 days, the next five have 30 days, and the last month has 29
74 days in usual years but 30 days in leap years. The New Year's Day always
75 falls on the March equinox.
76
77 \section2 Leap Year Rules
78
79 The Solar Hijri calendar produces a five-year leap year interval after about
80 every seven four-year leap year intervals. It usually follows a 33-year
81 cycle with occasional interruptions by single 29-year or 37-year subcycles.
82 The reason for this behavior is that it tracks the observed vernal equinox.
83 By contrast, some less accurate predictive algorithms are in use based
84 on confusion between the average tropical year (365.2422 days, approximated
85 with near 128-year cycles or 2820-year great cycles) and the mean interval
86 between spring equinoxes (365.2424 days, approximated with a near 33-year
87 cycle).
88
89 Source: \l {https://en.wikipedia.org/wiki/Solar_Hijri_calendar}{Wikipedia
90 page on Solar Hijri Calendar}
91*/
92
93QString QJalaliCalendar::name() const
94{
95 return QStringLiteral("Jalali");
96}
97
98QStringList QJalaliCalendar::nameList()
99{
100 return {
101 QStringLiteral("Jalali"),
102 QStringLiteral("Persian"),
103 };
104}
105
106bool QJalaliCalendar::isLeapYear(int year) const
107{
108 if (year == QCalendar::Unspecified)
109 return false;
110 if (year < 0)
111 ++year;
112
113 return isLeap(year);
114}
115
116bool QJalaliCalendar::isLunar() const
117{
118 return false;
119}
120
121bool QJalaliCalendar::isLuniSolar() const
122{
123 return false;
124}
125
126bool QJalaliCalendar::isSolar() const
127{
128 return true;
129}
130
131bool QJalaliCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
132{
133 Q_ASSERT(jd);
134 if (!isDateValid(year, month, day))
135 return false;
136
137 const int yearOffset = year < 0 ? 474 : 475;
138 auto yModCycle = qDivMod<cycleYears>(year);
139 if (yModCycle.remainder < yearOffset) {
140 yModCycle.quotient -= 1;
141 yModCycle.remainder += cycleYears;
142 }
143 yModCycle.remainder -= yearOffset;
144
145 int dayInYear = day;
146 for (int i = 1; i < month; ++i)
147 dayInYear += daysInMonth(i, year);
148 *jd = firstDayOfYear(yModCycle.remainder, yModCycle.quotient) + dayInYear - 1;
149 return true;
150}
151
152QCalendar::YearMonthDay QJalaliCalendar::julianDayToDate(qint64 jd) const
153{
154 using Bound = std::numeric_limits<int>;
155 constexpr qint64 maxInt = Bound::max();
156 const int c = cycle(jd);
157 int yearInCycle = qFloor((jd - cycleStart(c)) / yearLength);
158 int day = jd - firstDayOfYear(yearInCycle, c) + 1;
159
160 // Near the bounds of the supported range this is just outside int's range:
161 qint64 y = yearInCycle + 475 + c * qint64(cycleYears);
162 // Inline daysInYear() to bypass corrections for no year 0 (and its int parameter):
163 if (day > (isLeap(y) ? 366 : 365)) {
164 ++y;
165 day = 1;
166 }
167 // We'll be subtracting 1 if negative, so -maxInt is the lower bound, not Bound::min().
168 if (y > maxInt || y < -maxInt)
169 return {};
170
171 int year = static_cast<int>(y);
172 if (year <= 0)
173 year--;
174 int month;
175 for (month = 1; month < 12; ++month) {
176 const int last = daysInMonth(month, year);
177 if (day <= last)
178 break;
179 day -= last;
180 }
181 return {year, month, day};
182}
183
184int QJalaliCalendar::daysInMonth(int month, int year) const
185{
186 if (!year || month < 1 || month > 12)
187 return 0;
188
189 if (month < 7)
190 return 31;
191
192 if (month < 12 || year == QCalendar::Unspecified || isLeapYear(year))
193 return 30;
194
195 return 29;
196}
197
198const QCalendarLocale *QJalaliCalendar::localeMonthIndexData() const
199{
200 return QtPrivate::Jalali::locale_data;
201}
202
203const char16_t *QJalaliCalendar::localeMonthData() const
204{
205 return QtPrivate::Jalali::months_data;
206}
207
208QT_END_NAMESPACE
Combined button and popup list for selecting options.
static bool isLeap(qint64 year)
constexpr int cycleYears
constexpr qint64 jalaliEpoch
static int cycle(qint64 jdn)
constexpr double yearLength
static qint64 firstDayOfYear(int year, int cycleNo)
static qint64 cycleStart(int cycleNo)
constexpr qint64 cycleDays