From: Konrad Rosenbaum Date: Mon, 29 May 2017 06:58:33 +0000 (+0200) Subject: calendar tools X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=57aebfda0d3ec0fc2d614c8c2d4ccb5263ef3028;p=web%2Fkonrad%2Ftaurus.git calendar tools Change-Id: Iee401bca1dba129923ca3524ef2a00bbb504e429 --- diff --git a/.gitignore b/.gitignore index b1c0c81..03660f8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ core.* *.o *.obj moc_* +*.moc elam/tests/eval/evaltest* elam/tests/parser/parsertest* .qmake.stash @@ -23,4 +24,6 @@ doc/aurora-src/ doc/chester/ doc/elam/source/ doc/zip-src/ - +caltest +caltest.exe +target_wrapper.sh diff --git a/caltools.pri b/caltools.pri new file mode 100644 index 0000000..8054fdd --- /dev/null +++ b/caltools.pri @@ -0,0 +1,6 @@ +#include this into your qmake project file to +#use the Calendar Tools library + +INCLUDEPATH += $$PWD/include/caltools +LIBS += -lm -L$$PWD/lib -lcaltools +CONFIG += link_prl diff --git a/caltools/caltools.pro b/caltools/caltools.pro new file mode 100644 index 0000000..db46149 --- /dev/null +++ b/caltools/caltools.pro @@ -0,0 +1,2 @@ +TEMPLATE=subdirs +SUBDIRS += src tests/easter tests/gregorian tests/lunar tests/solar diff --git a/caltools/src/easterdate.cpp b/caltools/src/easterdate.cpp new file mode 100644 index 0000000..b7c50b0 --- /dev/null +++ b/caltools/src/easterdate.cpp @@ -0,0 +1,94 @@ +// Calculating Easter Dates +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#include + +// Debugger function, make if "#if 1" to enable debugging output +#if 0 +#include +#define DD(x) qDebug()<<""#x "="<11 && e==25)e=26; // second epact 25 rule + DD(e); + if(e==24)e=25; // epact 24 equals epact 25 + DD(e); + int d; + if(e<24)d=44-e; // paschal full moon + else d=74-e; + DD(d); + + d+= 7 - ((d+10-l)%7); // find sunday following this + DD(d); + int mo=3; // + if(d>61){mo=5;d-=61;} // may + else if(d>31){mo=4;d-=31;} // april + return QDate(year,mo,d); +} + +///calculates the eastern orthodox date for Easter sunday +QDate orthodoxEasterSunday(quint16 year) +{ + DD(year); + const int l=7-(year/4+year+4)%7; //dominical + DD(l); + const int g=year%19+1; //gregorian + DD(g); + const int e=(g*11-11)%30; //epact + DD(e); + const int d=(e>16)?(66-e):(36-e); // paschal + DD(d); + const int w=(d+3-l)%7; // weekday of paschal + DD(w); + int d2=d+7-w; // sunday after paschal in julian calendar + DD(d2); + const int q=(year/100-15)*3/4+10; // gregorian correction + DD(q); + d2+=q; + DD(d2); + + int mo=3; + if(d2>61){mo=5;d2-=61;} + else if(d2>31){mo=4;d2-=31;} + return QDate(year,mo,d2); +} + +//TODO: date of jewish pessach + + +//end of namespace +} + +#undef DD diff --git a/caltools/src/lunar.cpp b/caltools/src/lunar.cpp new file mode 100644 index 0000000..5b4b92b --- /dev/null +++ b/caltools/src/lunar.cpp @@ -0,0 +1,36 @@ +// Calculating Easter Dates +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#include +#include + +namespace CalTools { + +/// amount of phases used for internal calculations +const int LunarPhases_CycleLength=8; + +/// average length of a lunar cycle in solar days +const double LunarCycleLengthInDays=29.530588853l; + +/// Julian day of a known new moon +const int JulianDayJan1st1900=2415021; + +///returns the phase of the moon for a specific date +/// note: this function is quite +LunarPhase lunarPhaseForDate(QDate d) +{ + //calculate where in the cycle we are (in days) + double phase=fmod(d.toJulianDay()-JulianDayJan1st1900+1.0, LunarCycleLengthInDays); + if(phase<0.0)phase+=LunarCycleLengthInDays; + //convert to enum base + int p=phase*double(LunarPhases_CycleLength)/LunarCycleLengthInDays; + //this should not be necessary, but you never know how screwed the FPU is... + if(p<0)p=0; + if(p>=LunarPhases_CycleLength)p=LunarPhases_CycleLength-1; + return LunarPhase(p); +} + + +} diff --git a/caltools/src/solar.cpp b/caltools/src/solar.cpp new file mode 100644 index 0000000..3d82d9e --- /dev/null +++ b/caltools/src/solar.cpp @@ -0,0 +1,116 @@ +// Calculating Sun phases +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#include + +#include + + +void CalTools::GeoCoord::moveWest(double w) +{ + mwest+=w; + while(mwest>180)mwest-=360; + while(mwest<-180)mwest+=360; +} + +void CalTools::GeoCoord::moveEast(double e){moveWest(-e);} + +void CalTools::GeoCoord::moveNorth(double n) +{ + mnorth+=n; + while(mnorth>180)mnorth-=360; + while(mnorth<-180)mnorth+=360; + if(mnorth>90){ + moveWest(180); + mnorth=180-mnorth; + } + if(mnorth<-90){ + moveWest(180); + mnorth=-180-mnorth; + } +} + +void CalTools::GeoCoord::moveSouth(double s){moveNorth(-s);} + +static inline double deg2rad(double d){return d*M_PI/180.;} +static inline double rad2deg(double r){return r*180./M_PI;} + +//argument of perihelion +static inline double aperi(double n){return 102.9372 + 0.3179526*n/36525.;} + +CalTools::SolarInfo::SolarInfo(QDate date,GeoCoord geo) +{ + if(!date.isValid() || !geo.isValid())return; + mcoord=geo; + + // ///// + // section: calculate midday UTC + + // day since 2000-1-1 plus slight correction + double n=date.toJulianDay() - 2451545.0 + 0.0008; + // mean solar noon + double J = n - mcoord.degWest()/360.; + // solar mean anomaly + double M = fmod((357.5291 + .98560028 * J), 360.); + // equation of the center + double C = 1.9148*sin(deg2rad(M)) + 0.02*sin(2*deg2rad(M)) + 0.0003*sin(3*deg2rad(M)); + // ecliptic longitude + double lambda = fmod((M + C + 180. + aperi(n)), 360.); + // solar transit + double Jtrans=2451545.5 + J + 0.0053*sin(deg2rad(M)) - 0.0069*sin(deg2rad(2*lambda)); + // convert to date and time + mdate=QDate::fromJulianDay(floor(Jtrans)); + double t=Jtrans - floor(Jtrans); + mmidday=QTime::fromMSecsSinceStartOfDay(t*24*3600*1000); + + // ///// + // section: calculate twilight diffs + auto calcdiff=[&](double angle)->double{ + //declination of the sun + double sindelta=sin(deg2rad(lambda))*sin(deg2rad(23.44)); + double delta=rad2deg(asin(sindelta)); + //hour angle + double cosomega=(sin(deg2rad(angle))-sin(deg2rad(mcoord.degNorth()))*sindelta) / (cos(deg2rad(mcoord.degNorth()))*cos(deg2rad(delta))); + double omega=acos(cosomega); + //calculate difference + return omega/2./M_PI; + }; + mbegin=calcdiff(0.83); + mcivil=calcdiff(6.0); + mnautic=calcdiff(12.0); + mastro=calcdiff(18.0); +} + +QDateTime CalTools::SolarInfo::sunrise(TwilightType type)const +{ + switch(type){ + case TwilightType::Begin:return gentime(-mbegin); + case TwilightType::Civil:return gentime(-mcivil); + case TwilightType::Nautical:return gentime(-mnautic); + case TwilightType::Astronomical:return gentime(-mastro); + //should not happen: + default:return QDateTime(); + } +} + +QDateTime CalTools::SolarInfo::sunset(TwilightType type)const +{ + switch(type){ + case TwilightType::Begin:return gentime(mbegin); + case TwilightType::Civil:return gentime(mcivil); + case TwilightType::Nautical:return gentime(mnautic); + case TwilightType::Astronomical:return gentime(mastro); + //should not happen: + default:return QDateTime(); + } +} + +QDateTime CalTools::SolarInfo::gentime(double diff)const +{ + if(!mdate.isValid() || isnan(diff) || isinf(diff)) + return QDateTime(); + + return highNoon().addSecs(diff*24*3600); +} diff --git a/caltools/src/src.pro b/caltools/src/src.pro new file mode 100644 index 0000000..5ade2f5 --- /dev/null +++ b/caltools/src/src.pro @@ -0,0 +1,12 @@ +TEMPLATE = lib +TARGET = caltools +VERSION = +QT -= gui + +DESTDIR = $$PWD/../../lib + +INCLUDEPATH += ../../include/caltools + +DEFINES += CALTOOLS_EXPORT=Q_DECL_EXPORT + +SOURCES += lunar.cpp easterdate.cpp solar.cpp diff --git a/caltools/tests/easter/easter.pro b/caltools/tests/easter/easter.pro new file mode 100644 index 0000000..0af49ba --- /dev/null +++ b/caltools/tests/easter/easter.pro @@ -0,0 +1,10 @@ +TEMPLATE=app +TARGET=caltest + +QT+=testlib +QT-=gui +CONFIG += testcase + +include(../../../caltools.pri) + +SOURCES+=test2.cpp diff --git a/caltools/tests/easter/test2.cpp b/caltools/tests/easter/test2.cpp new file mode 100644 index 0000000..148946e --- /dev/null +++ b/caltools/tests/easter/test2.cpp @@ -0,0 +1,96 @@ +#include "easterdate.h" +#include +#include + + +class ED{ +public: + ED(quint16 y,quint8 m1,quint8 d1,quint8 m2=0,quint8 d2=0){year=y;wmonth=m1;wday=d1;emonth=m2?m2:m1;eday=d2?d2:d1;} + ED(){year=0;wmonth=wday=emonth=eday=0;} + ED(const ED&)=default; + + inline bool isWest(QDate d)const{return d.year()==year && d.month()==wmonth && d.day()==wday;} + inline bool isEast(QDate d)const{return d.year()==year && d.month()==emonth && d.day()==eday;} + + inline QDate west()const{return QDate(year,wmonth,wday);} + inline QDate east()const{return QDate(year,emonth,eday);} + + quint16 year; + quint8 wmonth,wday,emonth,eday; +}; + +//https://en.wikipedia.org/wiki/List_of_dates_for_Easter +QList initTV() +{ + QList r; + r< + +#include + + +using namespace CalTools; + +class GT:public QObject{ + Q_OBJECT +private slots: + void validity(); + void moveLatitude(); + void moveLongitude(); +}; + +void GT::validity() +{ + GeoCoord c1; + QVERIFY(!c1.isValid()); + GeoCoord c2(10,30); + QVERIFY(c2.isValid()); + c2.moveWest(200); + QVERIFY(c2.isValid()); + c2.moveNorth(100); + QVERIFY(c2.isValid()); +} + +void GT::moveLatitude() +{ + GeoCoord c1(10,30); + c1.moveNorth(10); + QCOMPARE(c1.degNorth(),20); +} +void GT::moveLongitude(){} + +QTEST_MAIN(GT) \ No newline at end of file diff --git a/caltools/tests/gregorian/gregorian.pro b/caltools/tests/gregorian/gregorian.pro new file mode 100644 index 0000000..c653477 --- /dev/null +++ b/caltools/tests/gregorian/gregorian.pro @@ -0,0 +1,10 @@ +TEMPLATE=app +TARGET=caltest + +QT+=testlib +QT-=gui +CONFIG += testcase + +INCLUDEPATH+=../../../include/caltools + +SOURCES+=gregtest.cpp diff --git a/caltools/tests/gregorian/gregtest.cpp b/caltools/tests/gregorian/gregtest.cpp new file mode 100644 index 0000000..9d3ef84 --- /dev/null +++ b/caltools/tests/gregorian/gregtest.cpp @@ -0,0 +1,156 @@ +#include "gregorian.h" +#include + +using namespace CalTools; + +class TClass:public QObject +{ + Q_OBJECT +private slots: + void leapyear(); + void daysInMonth(); + void weekDaysInMonth(); + void firstDay(); + void lastDay(); + void dayAfter(); + void dayBefore(); +}; + +#include "gregtest.moc" + +void TClass::leapyear() +{ + QCOMPARE(isLeapYear(1904),true); + QCOMPARE(isLeapYear(2004),true); + QCOMPARE(isLeapYear(2000),true); + QCOMPARE(isLeapYear(1901),false); + QCOMPARE(isLeapYear(1902),false); + QCOMPARE(isLeapYear(1903),false); + QCOMPARE(isLeapYear(1900),false); + QCOMPARE(isLeapYear(1800),false); +} + +void TClass::daysInMonth() +{ + QCOMPARE(numDaysInMonth(2000,1),(quint8)31); + QCOMPARE(numDaysInMonth(2000,2),(quint8)29); + QCOMPARE(numDaysInMonth(2001,2),(quint8)28); + QCOMPARE(numDaysInMonth(2000,3),(quint8)31); + QCOMPARE(numDaysInMonth(2000,4),(quint8)30); + QCOMPARE(numDaysInMonth(2000,5),(quint8)31); + QCOMPARE(numDaysInMonth(2000,6),(quint8)30); + QCOMPARE(numDaysInMonth(2000,7),(quint8)31); + QCOMPARE(numDaysInMonth(2000,8),(quint8)31); + QCOMPARE(numDaysInMonth(2000,9),(quint8)30); + QCOMPARE(numDaysInMonth(2000,10),(quint8)31); + QCOMPARE(numDaysInMonth(2000,11),(quint8)30); + QCOMPARE(numDaysInMonth(2000,12),(quint8)31); +} + +void TClass::weekDaysInMonth() +{ + QCOMPARE(numWeekDaysInMonth(2017,5),4); + QCOMPARE(numWeekDaysInMonth(2017,5,0),4); + QCOMPARE(numWeekDaysInMonth(2017,5,7),4); + QCOMPARE(numWeekDaysInMonth(2017,5,1),5); + QCOMPARE(numWeekDaysInMonth(2017,5,2),5); + QCOMPARE(numWeekDaysInMonth(2017,5,3),5); + QCOMPARE(numWeekDaysInMonth(2017,5,4),4); + QCOMPARE(numWeekDaysInMonth(2017,5,5),4); + QCOMPARE(numWeekDaysInMonth(2017,5,6),4); +} + +void TClass::firstDay() +{ + QCOMPARE(firstWeekDayInMonth(2017,5,1),QDate(2017,5,1)); + QCOMPARE(firstWeekDayInMonth(2017,5,2),QDate(2017,5,2)); + QCOMPARE(firstWeekDayInMonth(2017,5,3),QDate(2017,5,3)); + QCOMPARE(firstWeekDayInMonth(2017,5,4),QDate(2017,5,4)); + QCOMPARE(firstWeekDayInMonth(2017,5,5),QDate(2017,5,5)); + QCOMPARE(firstWeekDayInMonth(2017,5,6),QDate(2017,5,6)); + QCOMPARE(firstWeekDayInMonth(2017,5,7),QDate(2017,5,7)); + QCOMPARE(firstWeekDayInMonth(2017,5,0),QDate(2017,5,7)); + + QCOMPARE(firstWeekDayInMonth(2017,6,4),QDate(2017,6,1)); + QCOMPARE(firstWeekDayInMonth(2017,6,5),QDate(2017,6,2)); + QCOMPARE(firstWeekDayInMonth(2017,6,6),QDate(2017,6,3)); + QCOMPARE(firstWeekDayInMonth(2017,6,7),QDate(2017,6,4)); + QCOMPARE(firstWeekDayInMonth(2017,6,0),QDate(2017,6,4)); + QCOMPARE(firstWeekDayInMonth(2017,6,1),QDate(2017,6,5)); + QCOMPARE(firstWeekDayInMonth(2017,6,2),QDate(2017,6,6)); + QCOMPARE(firstWeekDayInMonth(2017,6,3),QDate(2017,6,7)); + + QCOMPARE(firstWeekDayInMonth(2017,6,33),QDate()); +} + +void TClass::lastDay() +{ + QCOMPARE(lastWeekDayInMonth(2017,5,1),QDate(2017,5,29)); + QCOMPARE(lastWeekDayInMonth(2017,5,2),QDate(2017,5,30)); + QCOMPARE(lastWeekDayInMonth(2017,5,3),QDate(2017,5,31));// + QCOMPARE(lastWeekDayInMonth(2017,5,4),QDate(2017,5,25)); + QCOMPARE(lastWeekDayInMonth(2017,5,5),QDate(2017,5,26)); + QCOMPARE(lastWeekDayInMonth(2017,5,6),QDate(2017,5,27)); + QCOMPARE(lastWeekDayInMonth(2017,5,7),QDate(2017,5,28)); + QCOMPARE(lastWeekDayInMonth(2017,5,0),QDate(2017,5,28)); + + QCOMPARE(lastWeekDayInMonth(2017,5,10),QDate()); +} + +void TClass::dayAfter() +{ + const QDate d(2017,5,17); + QCOMPARE(findDayAfter(d),QDate(2017,5,21)); + QCOMPARE(findDayAfter(d,0),QDate(2017,5,21)); + QCOMPARE(findDayAfter(d,7),QDate(2017,5,21)); + QCOMPARE(findDayAfter(d,0,false),QDate(2017,5,21)); + QCOMPARE(findDayAfter(d,0,false),QDate(2017,5,21)); + + QCOMPARE(findDayAfter(d,1,false),QDate(2017,5,22)); + QCOMPARE(findDayAfter(d,2,false),QDate(2017,5,23)); + QCOMPARE(findDayAfter(d,3,false),QDate(2017,5,24)); + QCOMPARE(findDayAfter(d,4,false),QDate(2017,5,18)); + QCOMPARE(findDayAfter(d,5,false),QDate(2017,5,19)); + QCOMPARE(findDayAfter(d,6,false),QDate(2017,5,20)); + QCOMPARE(findDayAfter(d,7,false),QDate(2017,5,21)); + + QCOMPARE(findDayAfter(d,1,true),QDate(2017,5,22)); + QCOMPARE(findDayAfter(d,2,true),QDate(2017,5,23)); + QCOMPARE(findDayAfter(d,3,true),QDate(2017,5,17)); + QCOMPARE(findDayAfter(d,4,true),QDate(2017,5,18)); + QCOMPARE(findDayAfter(d,5,true),QDate(2017,5,19)); + QCOMPARE(findDayAfter(d,6,true),QDate(2017,5,20)); + QCOMPARE(findDayAfter(d,7,true),QDate(2017,5,21)); + + QCOMPARE(findDayAfter(d,9,false),QDate()); +} + +void TClass::dayBefore() +{ + const QDate d(2017,5,17); + QCOMPARE(findDayBefore(d),QDate(2017,5,14)); + QCOMPARE(findDayBefore(d,0),QDate(2017,5,14)); + QCOMPARE(findDayBefore(d,7),QDate(2017,5,14)); + QCOMPARE(findDayBefore(d,0,false),QDate(2017,5,14)); + QCOMPARE(findDayBefore(d,0,false),QDate(2017,5,14)); + + QCOMPARE(findDayBefore(d,1,false),QDate(2017,5,15)); + QCOMPARE(findDayBefore(d,2,false),QDate(2017,5,16)); + QCOMPARE(findDayBefore(d,3,false),QDate(2017,5,10)); + QCOMPARE(findDayBefore(d,4,false),QDate(2017,5,11)); + QCOMPARE(findDayBefore(d,5,false),QDate(2017,5,12)); + QCOMPARE(findDayBefore(d,6,false),QDate(2017,5,13)); + QCOMPARE(findDayBefore(d,7,false),QDate(2017,5,14)); + + QCOMPARE(findDayBefore(d,1,true),QDate(2017,5,15)); + QCOMPARE(findDayBefore(d,2,true),QDate(2017,5,16)); + QCOMPARE(findDayBefore(d,3,true),QDate(2017,5,17)); + QCOMPARE(findDayBefore(d,4,true),QDate(2017,5,11)); + QCOMPARE(findDayBefore(d,5,true),QDate(2017,5,12)); + QCOMPARE(findDayBefore(d,6,true),QDate(2017,5,13)); + QCOMPARE(findDayBefore(d,7,true),QDate(2017,5,14)); + + QCOMPARE(findDayBefore(d,9,false),QDate()); +} + +QTEST_MAIN(TClass) \ No newline at end of file diff --git a/caltools/tests/lunar/lunar.cpp b/caltools/tests/lunar/lunar.cpp new file mode 100644 index 0000000..4b6f52f --- /dev/null +++ b/caltools/tests/lunar/lunar.cpp @@ -0,0 +1,28 @@ +#include "lunar.h" +#include + +using namespace CalTools; + +class TClass:public QObject +{ + Q_OBJECT +private slots: + void lunarPhases(); +}; + +#include "lunar.moc" + +void TClass::lunarPhases() +{ + QCOMPARE(lunarPhaseForDate(QDate(2017,4,26)),LunarPhase::NewMoon); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,1)),LunarPhase::Waxing25); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,3)),LunarPhase::Waxing50); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,6)),LunarPhase::Waxing75); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,10)),LunarPhase::FullMoon); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,16)),LunarPhase::Waning75); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,19)),LunarPhase::Waning50); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,21)),LunarPhase::Waning25); + QCOMPARE(lunarPhaseForDate(QDate(2017,5,25)),LunarPhase::NewMoon); +} + +QTEST_MAIN(TClass) \ No newline at end of file diff --git a/caltools/tests/lunar/lunar.pro b/caltools/tests/lunar/lunar.pro new file mode 100644 index 0000000..4f44a4f --- /dev/null +++ b/caltools/tests/lunar/lunar.pro @@ -0,0 +1,10 @@ +TEMPLATE=app +TARGET=caltest + +QT+=testlib +QT-=gui +CONFIG += testcase + +include(../../../caltools.pri) + +SOURCES+=lunar.cpp diff --git a/caltools/tests/solar/solar.pro b/caltools/tests/solar/solar.pro new file mode 100644 index 0000000..bd69f01 --- /dev/null +++ b/caltools/tests/solar/solar.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +TARGET = caltest + +QT-=gui + +include(../../../caltools.pri) + +SOURCES += solartest.cpp diff --git a/caltools/tests/solar/solartest.cpp b/caltools/tests/solar/solartest.cpp new file mode 100644 index 0000000..77f394b --- /dev/null +++ b/caltools/tests/solar/solartest.cpp @@ -0,0 +1,3 @@ +#include + +int main(){} \ No newline at end of file diff --git a/include/caltools/caltools.h b/include/caltools/caltools.h new file mode 100644 index 0000000..42b7bb7 --- /dev/null +++ b/include/caltools/caltools.h @@ -0,0 +1,4 @@ +#include "easterdate.h" +#include "gregorian.h" +#include "lunar.h" +#include "solar.h" diff --git a/include/caltools/easterdate.h b/include/caltools/easterdate.h new file mode 100644 index 0000000..43113bd --- /dev/null +++ b/include/caltools/easterdate.h @@ -0,0 +1,43 @@ +// Calculating Easter Dates +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#ifndef CALTOOLS_EASTER_H +#define CALTOOLS_EASTER_H + +#include + +#if 0 +#include +#define DD(x) qDebug()<<""#x "="< + +namespace CalTools{ + +// https://en.wikipedia.org/wiki/Leap_year +/// checks for leap years (works only within the Gregorian Calendar!) +static inline +bool isLeapYear(quint16 year) +{ + if(year%4 == 0){ + if(year%100 == 0){ + if(year%400 == 0)return true; + return false; + } + return true; + } + return false; +} + +///returns how many days there are in a specific month +static inline +quint8 numDaysInMonth(quint16 year,quint8 month) +{ + switch(month){ + case 1:case 3:case 5:case 7: + case 8:case 10:case 12: + return 31; + case 4:case 6:case 9:case 11: + return 30; + case 2: + return 28+(isLeapYear(year)?1:0); + default: + return 0; + } +} + +///finds the first instance of a week day in a month +/// \param year the Year in which to search +/// \param month the Month in which to search +/// \param dayOfWeek the week day to look for: 0/7 is sunday (\see QDate) +static inline +QDate firstWeekDayInMonth(quint16 year,quint8 month,quint8 dayOfWeek=0) +{ + if(dayOfWeek==0)dayOfWeek=7; + if(dayOfWeek>7)return QDate(); + //check first day + QDate d(year,month,1); + int dw=d.dayOfWeek(); + if(dw==dayOfWeek)return d; + //move on + if(dayOfWeek7)return 0; + //get first day of that type, then calculate + int d=firstWeekDayInMonth(year,month,dayOfWeek).day(); + return (numDaysInMonth(year,month)-d)/7 + 1; +} + +///finds the last instance of a week day in a month +/// \param year the Year in which to search +/// \param month the Month in which to search +/// \param dayOfWeek the week day to look for: 0/7 is sunday (\see QDate) +static inline +QDate lastWeekDayInMonth(quint16 year,quint8 month,quint8 dayOfWeek=0) +{ + if(dayOfWeek==0)dayOfWeek=7; + if(dayOfWeek>7)return QDate(); + //check last day + QDate d(year,month,numDaysInMonth(year,month)); + int dw=d.dayOfWeek(); + if(dw==dayOfWeek)return d; + if(dw7)return QDate(); + //same one? + int dw=date.dayOfWeek(); + if(inclusive && dw==dayOfWeek)return date; + if(dayOfWeek<=dw)dayOfWeek+=7; + return date.addDays(dayOfWeek-dw); +} + +///find the first instance of a specific week day before a given date +/// \param date the date from which to search +/// \param dayOfWeek the week day to look for (default is sunday) +/// \param inclusive if true date is returned if it is the week day in question, otherwise a date 7 days earlier +static inline +QDate findDayBefore(QDate date,quint8 dayOfWeek=0,bool inclusive=false) +{ + if(dayOfWeek==0)dayOfWeek=7; + if(dayOfWeek>7)return QDate(); + //same one? + int dw=date.dayOfWeek(); + if(inclusive && dw==dayOfWeek)return date; + if(dayOfWeek>=dw)dw+=7; + return date.addDays(dayOfWeek-dw); +} + +//end of namespace +} + +#endif diff --git a/include/caltools/lunar.h b/include/caltools/lunar.h new file mode 100644 index 0000000..f890e29 --- /dev/null +++ b/include/caltools/lunar.h @@ -0,0 +1,60 @@ +// Calculating Easter Dates +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#ifndef CALTOOLS_LUNAR_H +#define CALTOOLS_LUNAR_H + +#include + +namespace CalTools{ + +///Phases of the Moon (note: this only applies to earth's moon, not to Tatooine, Europa, Io or any other moon) +enum class LunarPhase { + ///New Moon, not visible with the naked eye + NewMoon=0, + ///getting bigger, it's a crescent + Waxing25=1, + ///getting bigger, it's a half moon + Waxing50=2, + ///getting bigger, it's almost full + Waxing75=3, + ///getting bigger, it's a crescent + WaxingCrescent=1, + ///getting bigger, it's a half moon + WaxingHalf=2, + ///getting bigger, it's almost full + WaxingGibbous=3, + ///getting bigger, it's a half moon, first quarter of the full cycle + FirstQuarter=2, + ///full moon + FullMoon=4, + ///getting smaller, it's almost full + Waning75=5, + ///getting smaller, it's a half moon + Waning50=6, + ///getting smaller, it's a crescent + Waning25=7, + ///getting smaller, it's almost full + WaningGibbous=5, + ///getting smaller, it's a half moon + WaningHalf=6, + ///getting smaller, it's a crescent + WaningCrescent=7, + ///getting smaller, it's a half moon, third quarter of the full cycle + ThirdQuarter=6, +}; + +#ifndef CALTOOLS_EXPORT +#define CALTOOLS_EXPORT Q_DECL_IMPORT +#endif + +///returns the phase of the moon for a specific date +/// note: this function is quite +LunarPhase lunarPhaseForDate(QDate d); + +//end of namespace +} + +#endif diff --git a/include/caltools/solar.h b/include/caltools/solar.h new file mode 100644 index 0000000..a2df863 --- /dev/null +++ b/include/caltools/solar.h @@ -0,0 +1,126 @@ +// Calculating Sun phases +// +// (c) Konrad Rosenbaum, 2017 +// protected under the GNU LGPL v3 or at your option any newer + +#ifndef CALTOOLS_SOLAR_H +#define CALTOOLS_SOLAR_H + +#include + +namespace CalTools { + +//TODO: calculate sunrise/sundown +// https://en.wikipedia.org/wiki/Sunrise_equation + +#ifndef CALTOOLS_EXPORT +#define CALTOOLS_EXPORT Q_DECL_IMPORT +#endif + +///Represents geographical coordinates. +class CALTOOLS_EXPORT GeoCoord +{ + double mwest=1000,mnorth=0; +public: + ///Instantiates an invalid coordinate instance. + GeoCoord(){} + ///Instantiates a coordinate instance. + ///\param north - the latitude in degrees north, negative values are south (-90 .. 0 .. 90) + ///\param west - the longitude in degrees west, negative values are east (-180 .. 0 .. 180) + GeoCoord(double north,double west):mwest(west),mnorth(north){/*for the corrections*/ moveWest(0);moveNorth(0);} + ///Copies the instance. + GeoCoord(const GeoCoord&)=default; + ///Moves the instance. + GeoCoord(GeoCoord&&)=default; + + ///Copies the instance. + GeoCoord& operator=(const GeoCoord&)=default; + ///Moves the instance. + GeoCoord& operator=(GeoCoord&&)=default; + + ///returns true if the coordinates are valid + bool isValid()const{return mwest<=180 && mwest>=-180 && mnorth<=90 && mnorth>=-90;} + ///returns the longitude in degrees west (negative is east) + double degWest()const{return mwest;} + ///returns the longitude in degrees east (negative is west) + double degEast()const{return -mwest;} + ///returns the latitude in degrees north (negative is south) + double degNorth()const{return mnorth;} + ///returns the latitude in degrees south (negative is north) + double degSouth()const{return -mnorth;} + + ///moves the coordinates further west + void moveWest(double); + ///moves the coordinates further east + void moveEast(double); + ///moves the coordinates further north (can wrap around beyond the north pole) + void moveNorth(double); + ///moves the coordinates further south (can wrap around beyond the south pole) + void moveSouth(double); +}; + +///Calculator for sunset and sunrise +class CALTOOLS_EXPORT SolarInfo +{ +public: + ///Instantiates an invalid instance that will return invalid times for all values. + SolarInfo(){} + ///Instantiates an instance for the given date and coordinates on earth. + SolarInfo(QDate date,GeoCoord geo); + + ///copies the instance + SolarInfo(const SolarInfo&)=default; + ///moves the instance + SolarInfo(SolarInfo&&)=default; + + ///copies the instance + SolarInfo& operator=(const SolarInfo&)=default; + ///moves the instance + SolarInfo& operator=(SolarInfo&&)=default; + + ///returns true if the solar calculator has valid date and coordinates + bool isValid()const{return mdate.isValid();} + + ///What exact part of twilight we are calculating. + enum class TwilightType { + ///Begin of sunset or sunrise - when the sun touches the horizon. + Begin, + ///End of civil twilight - the sun is 6 degrees below the horizon. + Civil, + ///End of nautic twilight - the sun is 12 degrees below the horizon. + Nautical, + ///End of astronomical twilight - the sun is 18 degrees below the horizon. + Astronomical + }; + + ///Calculate sunrise. + ///\param type the phase of sunrise that will be calculated + ///\returns the time of the sunrise phase in UTC + QDateTime sunrise(TwilightType type=TwilightType::Begin)const; + + ///Calculate sunset. + ///\param type the phase of sunset that will be calculated + ///\returns the time of the sunset phase in UTC + QDateTime sunset(TwilightType type=TwilightType::Begin)const; + + ///\returns The time of highest sun (zenith). + QDateTime highNoon()const{return QDateTime(mdate,mmidday,Qt::UTC);} + +private: + ///the date for which we calculate times + QDate mdate; + ///the time at which the sun is at the zenith in UTC + QTime mmidday; + ///time differences in fractions of a day + double mbegin=0,mcivil=0,mnautic=0,mastro=0; + ///geographical coordinates + GeoCoord mcoord; + + ///calculate a time relative to the calculated zenith + QDateTime gentime(double diff)const; +}; + +//end of namespace +} + +#endif diff --git a/taurus.pro b/taurus.pro index 9eb0d1b..946a878 100644 --- a/taurus.pro +++ b/taurus.pro @@ -1,3 +1,3 @@ TEMPLATE = subdirs -SUBDIRS = zip elam aurora +SUBDIRS = zip elam aurora caltools