Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qglobal.h"
#include "qjalalicalendar_p.h"
#include "qjalalicalendar_data_p.h"
#include "qcalendarmath_p.h"
#include <QtCore/qmath.h>
QT_BEGIN_NAMESPACE
using namespace QRoundingDown;
// Constants
constexpr qint64 cycleDays = 1029983;
constexpr int cycleYears = 2820;
constexpr double yearLength = 365.24219858156028368; // 365 + 683 / 2820.
constexpr qint64 jalaliEpoch = 2121446; // 475/01/01 AP, start of 2820 cycle
// This appears to be based on Ahmad Birashk's algorithm.
// Calendar implementation
static inline int cycle(qint64 jdn)
{
return qDiv<cycleDays>(jdn - jalaliEpoch);
}
static qint64 cycleStart(int cycleNo)
{
return jalaliEpoch + cycleNo * qint64(cycleDays);
}
static qint64 firstDayOfYear(int year, int cycleNo)
{
using std::floor;
qint64 firstDOYinEra
= QtPrivate::qCheckedFPConversionToInteger<qint64>(floor(year * yearLength));
return jalaliEpoch + cycleNo * qint64(cycleDays) + firstDOYinEra;
}
// Promoted to qint64 to avoid overflows and handle julianDayToDate()'s qint64.
static bool isLeap(qint64 year)
{
// This handles a year < 0 ? year + 1 : year adjusted year, or a value due
// to be subjected to the reverse of that to deliver a year.
return qMod<2820>((year + 2346) * 683) < 683;
}
/*!
\since 5.14
\internal
\class QJalaliCalendar
\inmodule QtCore
\brief The QJalaliCalendar class provides Jalali (Hijri Shamsi) calendar
system implementation.
\section1 Solar Hijri Calendar System
The Solar Hijri calendar, also called the Solar Hejri calendar, Shamsi
Hijri calendar or Jalali calendar, is the official calendar of Iran and
Afghanistan. It begins on the vernal equinox (Nowruz) as determined by
astronomical calculation for the Iran Standard Time meridian
(52.5°E or GMT+3.5h). This determination of starting moment is more accurate
than the Gregorian calendar for predicting the date of the vernal equinox,
because it uses astronomical observations rather than mathematical rules.
\section2 Calendar Organization
Each of the twelve months corresponds with a zodiac sign. The first six
months have 31 days, the next five have 30 days, and the last month has 29
days in usual years but 30 days in leap years. The New Year's Day always
falls on the March equinox.
\section2 Leap Year Rules
The Solar Hijri calendar produces a five-year leap year interval after about
every seven four-year leap year intervals. It usually follows a 33-year
cycle with occasional interruptions by single 29-year or 37-year subcycles.
The reason for this behavior is that it tracks the observed vernal equinox.
By contrast, some less accurate predictive algorithms are in use based
on confusion between the average tropical year (365.2422 days, approximated
with near 128-year cycles or 2820-year great cycles) and the mean interval
between spring equinoxes (365.2424 days, approximated with a near 33-year
cycle).
page on Solar Hijri Calendar}
*/
QString QJalaliCalendar::name() const
{
return QStringLiteral("Jalali");
}
QStringList QJalaliCalendar::nameList()
{
return {
QStringLiteral("Jalali"),
QStringLiteral("Persian"),
};
}
bool QJalaliCalendar::isLeapYear(int year) const
{
if (year == QCalendar::Unspecified)
return false;
if (year < 0)
++year;
return isLeap(year);
}
bool QJalaliCalendar::isLunar() const
{
return false;
}
bool QJalaliCalendar::isLuniSolar() const
{
return false;
}
bool QJalaliCalendar::isSolar() const
{
return true;
}
bool QJalaliCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
Q_ASSERT(jd);
if (!isDateValid(year, month, day))
return false;
const int yearOffset = year < 0 ? 474 : 475;
auto yModCycle = qDivMod<cycleYears>(year);
if (yModCycle.remainder < yearOffset) {
yModCycle.quotient -= 1;
yModCycle.remainder += cycleYears;
}
yModCycle.remainder -= yearOffset;
int dayInYear = day;
for (int i = 1; i < month; ++i)
dayInYear += daysInMonth(i, year);
*jd = firstDayOfYear(yModCycle.remainder, yModCycle.quotient) + dayInYear - 1;
return true;
}
QCalendar::YearMonthDay QJalaliCalendar::julianDayToDate(qint64 jd) const
{
using Bound = std::numeric_limits<int>;
constexpr qint64 maxInt = Bound::max();
const int c = cycle(jd);
int yearInCycle = qFloor((jd - cycleStart(c)) / yearLength);
int day = jd - firstDayOfYear(yearInCycle, c) + 1;
// Near the bounds of the supported range this is just outside int's range:
qint64 y = yearInCycle + 475 + c * qint64(cycleYears);
// Inline daysInYear() to bypass corrections for no year 0 (and its int parameter):
if (day > (isLeap(y) ? 366 : 365)) {
++y;
day = 1;
}
// We'll be subtracting 1 if negative, so -maxInt is the lower bound, not Bound::min().
if (y > maxInt || y < -maxInt)
return {};
int year = static_cast<int>(y);
if (year <= 0)
year--;
int month;
for (month = 1; month < 12; ++month) {
const int last = daysInMonth(month, year);
if (day <= last)
break;
day -= last;
}
return {year, month, day};
}
int QJalaliCalendar::daysInMonth(int month, int year) const
{
if (!year || month < 1 || month > 12)
return 0;
if (month < 7)
return 31;
if (month < 12 || year == QCalendar::Unspecified || isLeapYear(year))
return 30;
return 29;
}
const QCalendarLocale *QJalaliCalendar::localeMonthIndexData() const
{
return QtPrivate::Jalali::locale_data;
}
const char16_t *QJalaliCalendar::localeMonthData() const
{
return QtPrivate::Jalali::months_data;
}
QT_END_NAMESPACE