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 }