File indexing completed on 2024-06-16 05:03:55

0001 /*
0002     SPDX-FileCopyrightText: 2021 Gary Wang <wzc782970009@gmail.com>
0003     SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "chinesecalendar.h"
0009 
0010 #include <mutex>
0011 #include <shared_mutex>
0012 
0013 #include "icucalendar_p.h"
0014 #include "solarutils.h"
0015 
0016 using namespace Qt::StringLiterals;
0017 
0018 namespace
0019 {
0020 std::shared_mutex s_solarTermsMapMutex;
0021 constinit QHash<int /*year*/, std::array<QDate, 25>> s_solarTermsMap;
0022 const std::array<QString, 25> s_solarTermNames{
0023     u"春分"_s, u"清明"_s, u"谷雨"_s, u"立夏"_s, u"小满"_s, u"芒种"_s, u"夏至"_s, u"小暑"_s, u"大暑"_s, u"立秋"_s, u"处暑"_s, u"白露"_s,
0024     u"秋分"_s, u"寒露"_s, u"霜降"_s, u"立冬"_s, u"小雪"_s, u"大雪"_s, u"冬至"_s, u"小寒"_s, u"大寒"_s, u"立春"_s, u"雨水"_s, u"惊蛰"_s,
0025 };
0026 }
0027 
0028 class ChineseCalendarProviderPrivate : public ICUCalendarPrivate
0029 {
0030 public:
0031     explicit ChineseCalendarProviderPrivate();
0032 
0033     CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date);
0034 
0035 private:
0036     enum SolarTerm {
0037         ChunFen = 0,
0038         QingMing,
0039         GuYu,
0040         LiXia,
0041         XiaoMan,
0042         MangZhong,
0043         XiaZhi,
0044         XiaoShu,
0045         DaShu,
0046         LiQiu,
0047         ChuShu,
0048         BaiLu,
0049         QiuFen,
0050         HanLu,
0051         ShuangJiang,
0052         LiDong,
0053         XiaoXue,
0054         DaXue,
0055         DongZhi,
0056         XiaoHan,
0057         DaHan,
0058         LiChun,
0059         YuShui,
0060         JingZhe,
0061     };
0062 
0063     /**
0064      * For formatting, see the documentation of SimpleDateFormat:
0065      * https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1SimpleDateFormat.html#details
0066      */
0067     QString formattedDateString(const icu::UnicodeString &str, bool hanidays = false) const;
0068 
0069     QString yearDisplayName() const;
0070     QString monthDisplayName() const;
0071     QString dayDisplayName() const;
0072 
0073     decltype(std::declval<decltype(s_solarTermsMap)>().begin()) generateSolarTermsCache(int year);
0074 
0075     /**
0076      * Calculates Julian day of a solar term based on Newton's method
0077      */
0078     QDate getSolarTermDate(int year, SolarTerm order) const;
0079 
0080     icu::Locale m_locale;
0081     icu::Locale m_hanidaysLocale;
0082 };
0083 
0084 ChineseCalendarProviderPrivate::ChineseCalendarProviderPrivate()
0085     : ICUCalendarPrivate()
0086     , m_locale(icu::Locale("zh", 0, 0, "calendar=chinese"))
0087     , m_hanidaysLocale(icu::Locale("zh", 0, 0, "calendar=chinese;numbers=hanidays"))
0088 {
0089     if (U_FAILURE(m_errorCode)) {
0090         return; // Failed to create m_GregorianCalendar
0091     }
0092 
0093     m_calendar.reset(icu::Calendar::createInstance("en_US@calendar=chinese", m_errorCode));
0094 }
0095 
0096 decltype(std::declval<decltype(s_solarTermsMap)>().begin()) ChineseCalendarProviderPrivate::generateSolarTermsCache(int year)
0097 {
0098 #if __has_cpp_attribute(assume)
0099     [[assume(year > 0)]];
0100 #endif
0101     {
0102         std::shared_lock lock(s_solarTermsMapMutex);
0103         auto thisYearIt = s_solarTermsMap.find(year);
0104         if (thisYearIt != s_solarTermsMap.end()) {
0105             // Already generated
0106             return thisYearIt;
0107         }
0108     }
0109 
0110     std::unique_lock lock(s_solarTermsMapMutex);
0111     auto thisYearIt = s_solarTermsMap.insert(year, {});
0112 
0113     SolarTerm solarTermIndex = DongZhi;
0114     for (int i = 0; i < 25; i++) {
0115         (*thisYearIt)[i] = getSolarTermDate(year - 1, solarTermIndex);
0116         if (solarTermIndex == DongZhi) {
0117             year++;
0118         }
0119         solarTermIndex = static_cast<SolarTerm>((solarTermIndex + 1) % 24);
0120     }
0121 
0122     return thisYearIt;
0123 }
0124 
0125 CalendarEvents::CalendarEventsPlugin::SubLabel ChineseCalendarProviderPrivate::subLabel(const QDate &date)
0126 {
0127     auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{};
0128 
0129     if (U_FAILURE(m_errorCode) || !date.isValid() || !setDate(date)) {
0130         return sublabel;
0131     }
0132 
0133     sublabel.yearLabel = yearDisplayName();
0134     sublabel.monthLabel = monthDisplayName();
0135 
0136     // Check solar term cache exists
0137     auto it = generateSolarTermsCache(date.year());
0138 
0139     int solarTermIndex = -1;
0140     {
0141         const int indexInList = 2 * date.month() - 1;
0142         const QDate date1 = it->at(indexInList);
0143         const QDate date2 = it->at(indexInList + 1);
0144         if (date1.day() == date.day()) {
0145             solarTermIndex = (indexInList + DongZhi) % 24;
0146         } else if (date2.day() == date.day()) {
0147             solarTermIndex = (indexInList + 1 + DongZhi) % 24;
0148         }
0149     }
0150 
0151     const QString dayName = dayDisplayName();
0152     sublabel.dayLabel = day() == 1 ? monthDisplayName() : (solarTermIndex >= 0 ? s_solarTermNames.at(solarTermIndex) : dayName);
0153     const QString solarTerm = solarTermIndex >= 0 ? QStringLiteral(" (%1)").arg(s_solarTermNames.at(solarTermIndex)) : QString();
0154     sublabel.label = QStringLiteral("%1%2%3%4").arg(sublabel.yearLabel, sublabel.monthLabel, dayName, solarTerm);
0155     sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low;
0156 
0157     return sublabel;
0158 }
0159 
0160 QString ChineseCalendarProviderPrivate::formattedDateString(const icu::UnicodeString &str, bool hanidays) const
0161 {
0162     UErrorCode errorCode = U_ZERO_ERROR;
0163     icu::UnicodeString dateString;
0164     icu::SimpleDateFormat formatter(str, hanidays ? m_hanidaysLocale : m_locale, errorCode);
0165     formatter.setCalendar(*m_calendar);
0166     formatter.format(m_calendar->getTime(errorCode), dateString);
0167 
0168     return QString::fromUtf16(dateString.getBuffer(), dateString.length());
0169 }
0170 
0171 QString ChineseCalendarProviderPrivate::yearDisplayName() const
0172 {
0173     return formattedDateString("U");
0174 }
0175 
0176 QString ChineseCalendarProviderPrivate::monthDisplayName() const
0177 {
0178     return formattedDateString("MMM");
0179 }
0180 
0181 QString ChineseCalendarProviderPrivate::dayDisplayName() const
0182 {
0183     return formattedDateString("d", true);
0184 }
0185 
0186 QDate ChineseCalendarProviderPrivate::getSolarTermDate(int year, SolarTerm order) const
0187 {
0188 #if __has_cpp_attribute(assume)
0189     [[assume(year > 0)]];
0190 #endif
0191     constexpr double RADIANS_PER_TERM = std::numbers::pi / 12.0;
0192     double angle = double(order) * RADIANS_PER_TERM;
0193     int month = ((order + 1) / 2 + 2) % 12 + 1;
0194     // 春分: Mar 20th
0195     int day = 6;
0196     if (order % 2 == 0) {
0197         day = 20;
0198     }
0199     const int64_t initialJulianDay = SolarUtils::toJulianDay(year, month, day);
0200     // Can't use QDate::toJulianDay because it doesn't support extra hours
0201     double julianDay = SolarUtils::NewtonIteration(angle, initialJulianDay);
0202     // To UTC+8 time
0203     julianDay += 8.0 / 24.0;
0204 
0205     int resultYear, resultMonth, resultDay;
0206     SolarUtils::getDateFromJulianDay(julianDay, resultYear, resultMonth, resultDay);
0207     //  TT -> UTC
0208     julianDay -= SolarUtils::getDeltaT(resultYear, resultMonth) / 86400;
0209     SolarUtils::getDateFromJulianDay(julianDay, resultYear, resultMonth, resultDay);
0210 
0211     return QDate(resultYear, resultMonth, resultDay);
0212 }
0213 
0214 ChineseCalendarProvider::ChineseCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate)
0215     : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate)
0216     , d(std::make_unique<ChineseCalendarProviderPrivate>())
0217 {
0218     Q_ASSERT(m_calendarSystem == CalendarSystem::Chinese);
0219 }
0220 
0221 ChineseCalendarProvider::~ChineseCalendarProvider()
0222 {
0223 }
0224 
0225 CalendarEvents::CalendarEventsPlugin::SubLabel ChineseCalendarProvider::subLabel(const QDate &date) const
0226 {
0227     return d->subLabel(date);
0228 }