File indexing completed on 2024-04-28 07:42:58

0001 /*
0002     This file is part of the kholidays library.
0003 
0004     SPDX-FileCopyrightText: 2004, 2007, 2009 Allen Winter <winter@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "lunarphase.h"
0010 
0011 #include <QCoreApplication>
0012 #include <QDateTime>
0013 #include <QTimeZone>
0014 
0015 using namespace KHolidays;
0016 
0017 QString LunarPhase::phaseNameAtDate(const QDate &date)
0018 {
0019     return phaseName(phaseAtDate(date));
0020 }
0021 
0022 QString LunarPhase::phaseName(LunarPhase::Phase phase)
0023 {
0024     switch (phase) {
0025     case NewMoon:
0026         return (QCoreApplication::translate("LunarPhase", "New Moon"));
0027     case FullMoon:
0028         return (QCoreApplication::translate("LunarPhase", "Full Moon"));
0029     case FirstQuarter:
0030         return (QCoreApplication::translate("LunarPhase", "First Quarter Moon"));
0031     case LastQuarter:
0032         return (QCoreApplication::translate("LunarPhase", "Last Quarter Moon"));
0033     case None:
0034         return QString();
0035     case WaxingCrescent:
0036         return (QCoreApplication::translate("LunarPhase", "Waxing Crescent"));
0037     case WaxingGibbous:
0038         return (QCoreApplication::translate("LunarPhase", "Waxing Gibbous"));
0039     case WaningGibbous:
0040         return (QCoreApplication::translate("LunarPhase", "Waning Gibbous"));
0041     case WaningCrescent:
0042         return (QCoreApplication::translate("LunarPhase", "Waning Crescent"));
0043     }
0044     return QString();
0045 }
0046 
0047 static double phaseAngle(int64_t unixmsec);
0048 
0049 LunarPhase::Phase LunarPhase::phaseAtDate(const QDate &date)
0050 {
0051     Phase retPhase = None;
0052 
0053     const QTime midnight(0, 0, 0);
0054     const QDateTime todayStart(date, midnight, QTimeZone::utc());
0055     const double startAngle = phaseAngle(todayStart.toMSecsSinceEpoch());
0056     const QDateTime todayEnd(date.addDays(1), midnight, QTimeZone::utc());
0057     const double endAngle = phaseAngle(todayEnd.toMSecsSinceEpoch());
0058 
0059     if (startAngle > endAngle) {
0060         retPhase = NewMoon;
0061     } else if (startAngle < 90.0 && endAngle > 90.0) {
0062         retPhase = FirstQuarter;
0063     } else if (startAngle < 180.0 && endAngle > 180.0) {
0064         retPhase = FullMoon;
0065     } else if (startAngle < 270.0 && endAngle > 270.0) {
0066         retPhase = LastQuarter;
0067     } else if (endAngle < 90.0) {
0068         retPhase = WaxingCrescent;
0069     } else if (endAngle < 180.0) {
0070         retPhase = WaxingGibbous;
0071     } else if (endAngle < 270.0) {
0072         retPhase = WaningGibbous;
0073     } else if (endAngle < 360.0) {
0074         retPhase = WaningCrescent;
0075     }
0076 
0077     return retPhase;
0078 }
0079 
0080 /*
0081     Algorithm adapted from Moontool by John Walker.
0082     Moontool is in the public domain: "Do what thou wilt shall be the whole of the law".
0083     Unnecessary calculations have been removed.
0084     See https://fourmilab.ch/moontool .
0085 */
0086 
0087 #include <cmath>
0088 
0089 constexpr int64_t epoch = 315446400000; // "1980 January 0.0", a.k.a. midnight on 1979-12-31, in ms Unix time
0090 constexpr double elonge = 278.833540; // ecliptic longitude of sun at epoch
0091 constexpr double elongp = 282.596403; // ecliptic longitude of sun at perigee
0092 constexpr double earthEcc = 0.016718; // eccentricity of earth orbit
0093 static const double ecPrefactor = sqrt((1 + earthEcc) / (1 - earthEcc));
0094 
0095 constexpr double mmlong = 64.975464; // mean longitude of moon at epoch
0096 constexpr double mmlongp = 349.383063; // mean longitude of moon at perigee
0097 
0098 constexpr double PI = 3.14159265358979323846;
0099 
0100 static double fixAngle(double degrees)
0101 {
0102     return degrees - floor(degrees / 360.0) * 360.0;
0103 }
0104 
0105 static constexpr double radToDeg(double rad)
0106 {
0107     return rad / PI * 180.0;
0108 }
0109 
0110 static constexpr double degToRad(double deg)
0111 {
0112     return deg / 180.0 * PI;
0113 }
0114 
0115 constexpr double epsilon = 1e-6;
0116 
0117 static double kepler(double m, double ecc)
0118 {
0119     double mrad = degToRad(m);
0120     double e = mrad;
0121     double delta;
0122     do {
0123         delta = e - ecc * sin(e) - mrad;
0124         e -= delta / (1 - ecc * cos(e));
0125     } while (abs(delta) > epsilon);
0126     return e;
0127 }
0128 
0129 static double phaseAngle(int64_t unixmsec)
0130 {
0131     int64_t msec = unixmsec - epoch;
0132 
0133     double sunMeanAnomaly = fixAngle(msec * (360.0 / 365.2422 / 86400000.0) + elonge - elongp);
0134     double trueAnomaly = 2 * radToDeg(atan(ecPrefactor * tan(kepler(sunMeanAnomaly, earthEcc) / 2)));
0135     double geocentricEclipticLong = fixAngle(trueAnomaly + elongp);
0136 
0137     double moonMeanLong = fixAngle(msec * (13.1763966 / 86400000.0) + mmlong);
0138     double moonMeanAnomaly = fixAngle(moonMeanLong - msec * (0.1114041 / 86400000.0) - mmlongp);
0139     double evection = 1.2739 * sin(degToRad(2 * (moonMeanLong - geocentricEclipticLong) - moonMeanAnomaly));
0140     double annualEquation = 0.1858 * sin(degToRad(sunMeanAnomaly));
0141     double a3 = 0.37 * sin(degToRad(sunMeanAnomaly));
0142     double correctedAnomaly = moonMeanAnomaly + evection - annualEquation - a3;
0143     double centreCorrection = 6.2886 * sin(degToRad(correctedAnomaly));
0144     double a4 = 0.214 * sin(degToRad(2 * correctedAnomaly));
0145     double correctedLong = moonMeanLong + evection + centreCorrection - annualEquation + a4;
0146     double variation = 0.6583 * sin(degToRad(2 * (correctedLong - geocentricEclipticLong)));
0147     double trueLong = correctedLong + variation;
0148 
0149     return fixAngle(trueLong - geocentricEclipticLong);
0150 }
0151 
0152 #include "moc_lunarphase.cpp"