File indexing completed on 2024-12-08 09:38:10

0001 /*
0002     This file is part of the kholidays library.
0003 
0004     SPDX-FileCopyrightText: 2004, 2006-2007 Allen Winter <winter@kde.org>
0005     SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "astroseasons.h"
0011 
0012 #include <cmath>
0013 #include <numeric>
0014 
0015 #include <QCoreApplication>
0016 #include <QDate>
0017 
0018 using namespace KHolidays;
0019 
0020 /* Correction tables and calculation algorithms below are based on the book
0021  * Astronomical Algorithms by Jean Meeus, (c) 1991 by Willman-Bell, Inc.,
0022  *
0023  * The correction tables are precise for years -1000 to +3000 but according
0024  * to the book they can be used for several centuries before and after that
0025  * period with the error still being quite small. As we only care about date
0026  * and not the time, the precision of the algorithm is good enough even for
0027  * greater time span, therefore no checks for the given year are performed
0028  * anywhere in the code.
0029  */
0030 namespace
0031 {
0032 double meanJDE(AstroSeasons::Season season, int year)
0033 {
0034     if (year <= 1000) {
0035         // Astronomical Algorithms, Jean Meeus, chapter 26, table 26.A
0036         // mean season Julian dates for years -1000 to 1000
0037         const double y = year / 1000.0;
0038         switch (season) {
0039         case AstroSeasons::MarchEquinox:
0040             return 1721139.29189 + (365242.13740 * y) + (0.06134 * std::pow(y, 2)) + (0.00111 * std::pow(y, 3)) - (0.00071 * std::pow(y, 4));
0041         case AstroSeasons::JuneSolstice:
0042             return 1721233.25401 + (365241.72562 * y) - (0.05323 * std::pow(y, 2)) + (0.00907 * std::pow(y, 3)) + (0.00025 * std::pow(y, 4));
0043         case AstroSeasons::SeptemberEquinox:
0044             return 1721325.70455 + (365242.49558 * y) - (0.11677 * std::pow(y, 2)) - (0.00297 * std::pow(y, 3)) + (0.00074 * std::pow(y, 4));
0045         case AstroSeasons::DecemberSolstice:
0046             return 1721414.39987 + (365242.88257 * y) - (0.00769 * std::pow(y, 2)) - (0.00933 * std::pow(y, 3)) - (0.00006 * std::pow(y, 4));
0047         case AstroSeasons::None:
0048             Q_ASSERT(false);
0049             return 0;
0050         }
0051     } else {
0052         // Astronomical Algorithms, Jean Meeus, chapter 26, table 26.B
0053         // mean season Julian dates for years 1000 to 3000
0054         const double y = (year - 2000) / 1000.0;
0055         switch (season) {
0056         case AstroSeasons::MarchEquinox:
0057             return 2451623.80984 + (365242.37404 * y) + (0.05169 * std::pow(y, 2)) - (0.00411 * std::pow(y, 3)) - (0.00057 * std::pow(y, 4));
0058         case AstroSeasons::JuneSolstice:
0059             return 2451716.56767 + (365241.62603 * y) + (0.00325 * std::pow(y, 2)) + (0.00888 * std::pow(y, 3)) - (0.00030 * std::pow(y, 4));
0060         case AstroSeasons::SeptemberEquinox:
0061             return 2451810.21715 + (365242.01767 * y) - (0.11575 * std::pow(y, 2)) + (0.00337 * std::pow(y, 3)) + (0.00078 * std::pow(y, 4));
0062         case AstroSeasons::DecemberSolstice:
0063             return 2451900.05952 + (365242.74049 * y) - (0.06223 * std::pow(y, 2)) - (0.00823 * std::pow(y, 3)) + (0.00032 * std::pow(y, 4));
0064         case AstroSeasons::None:
0065             Q_ASSERT(false);
0066             return 0;
0067         }
0068     }
0069 
0070     return 0;
0071 }
0072 
0073 double periodicTerms(double t)
0074 {
0075     // Astronomical Algorithms, Jean Meeus, chapter 26, table 26.C
0076     // The table gives the periodic terms in degrees, but the values are converted to radians
0077     // at compile time so that they can be passed to std::cos()
0078     struct Periodic {
0079         constexpr Periodic(int a, double b_deg, double c_deg)
0080             : a(a)
0081             , b_rad(b_deg * (M_PI / 180.0))
0082             , c_rad(c_deg * (M_PI / 180.0))
0083         {
0084         }
0085 
0086         int a;
0087         double b_rad;
0088         double c_rad;
0089     }
0090     // clang-format off
0091     periodic[] = {
0092         {485, 324.96,  1934.136}, {203, 337.23, 32964.467}, {199, 342.08,    20.186}, {182,  27.85, 445267.112},
0093         {156,  73.14, 45036.886}, {136, 171.52, 22518.443}, { 77, 222.54, 65928.934}, { 74, 296.72,   3034.906},
0094         { 70, 243.58,  9037.513}, { 58, 119.81, 33718.147}, { 52, 297.17,   150.678}, { 50,  21.02,   2281.226},
0095         { 45, 247.54, 29929.562}, { 44, 325.15, 31555.956}, { 29,  60.93,  4443.417}, { 18, 155.12,  67555.328},
0096         { 17, 288.79,  4562.452}, { 16, 198.04, 62894.029}, { 14, 199.76, 31436.921}, { 12,  95.39,  14577.848},
0097         { 12, 287.11, 31931.756}, { 12, 320.81, 34777.259}, {  9, 227.73,  1222.114}, {  8,  15.45,  16859.074}
0098     };
0099     // clang-format on
0100 
0101     return std::accumulate(std::begin(periodic), std::end(periodic), 0.0, [t](double s, const Periodic &p) {
0102         return s + p.a * std::cos(p.b_rad + p.c_rad * t);
0103     });
0104 }
0105 
0106 // Returns julian date of given season in given year
0107 double seasonJD(AstroSeasons::Season season, int year)
0108 {
0109     // Astronimical Algorithms, Jean Meeus, chapter 26
0110     const auto jde0 = meanJDE(season, year);
0111     const auto T = (jde0 - 2451545.0) / 36525;
0112     const auto W_deg = 35999.373 * T + 2.47;
0113     const auto W_rad = W_deg * (M_PI / 180.0);
0114     const auto dLambda = 1 + (0.0334 * std::cos(W_rad)) + (0.0007 * std::cos(2 * W_rad));
0115     const auto S = periodicTerms(T);
0116     return jde0 + (0.00001 * S) / dLambda;
0117 }
0118 
0119 }
0120 
0121 QDate AstroSeasons::seasonDate(Season season, int year)
0122 {
0123     if (season == None) {
0124         return {};
0125     }
0126     const qint64 jd = round(seasonJD(season, year));
0127     return QDate::fromJulianDay(jd);
0128 }
0129 
0130 QString AstroSeasons::seasonNameAtDate(const QDate &date)
0131 {
0132     return seasonName(seasonAtDate(date));
0133 }
0134 
0135 QString AstroSeasons::seasonName(AstroSeasons::Season season)
0136 {
0137     switch (season) {
0138     case JuneSolstice:
0139         return QCoreApplication::translate("AstroSeasons", "June Solstice");
0140     case DecemberSolstice:
0141         return QCoreApplication::translate("AstroSeasons", "December Solstice");
0142     case MarchEquinox:
0143         return QCoreApplication::translate("AstroSeasons", "March Equinox");
0144     case SeptemberEquinox:
0145         return QCoreApplication::translate("AstroSeasons", "September Equinox");
0146     case None:
0147         return QString();
0148     }
0149     return QString();
0150 }
0151 
0152 AstroSeasons::Season AstroSeasons::seasonAtDate(const QDate &date)
0153 {
0154     for (auto season : {JuneSolstice, DecemberSolstice, MarchEquinox, SeptemberEquinox}) {
0155         if (seasonDate(season, date.year()) == date) {
0156             return season;
0157         }
0158     }
0159     return None;
0160 }