didier/functions/sunrise.py

124 lines
4.7 KiB
Python

from math import cos, sin, acos, asin, tan
from math import degrees as deg, radians as rad
from datetime import date, datetime, time
import pytz
class Sun:
"""
Calculate sunrise and sunset based on equations from NOAA
http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html
typical use, calculating the sunrise at the present day:
import datetime
import sunrise
s = sun()
print('sunrise at ',s.sunrise(when=datetime.datetime.now())
"""
def __init__(self):
self.lat = 51.027169
self.long = 3.710359
def sunrise(self, when=None):
"""
return the time of sunrise as a datetime.time object
when is a datetime.datetime object. If none is given
a local time zone is assumed (including daylight saving
if present)
"""
if when is None: when = datetime.now(tz=pytz.timezone("Europe/Brussels"))
self.__preptime(when)
self.__calc()
return Sun.__timefromdecimalday(self.sunrise_t)
def sunset(self, when=None):
if when is None: when = datetime.now(tz=pytz.timezone("Europe/Brussels"))
self.__preptime(when)
self.__calc()
return Sun.__timefromdecimalday(self.sunset_t)
def solarnoon(self, when=None):
if when is None: when = datetime.now(tz=pytz.timezone("Europe/Brussels"))
self.__preptime(when)
self.__calc()
return Sun.__timefromdecimalday(self.solarnoon_t)
@staticmethod
def __timefromdecimalday(day):
"""
returns a datetime.time object.
day is a decimal day between 0.0 and 1.0, e.g. noon = 0.5
"""
hours = 24.0 * day
h = int(hours)
minutes = (hours - h) * 60
m = int(minutes)
seconds = (minutes - m) * 60
s = int(seconds)
return time(hour=h, minute=m, second=s)
def __preptime(self, when):
"""
Extract information in a suitable format from when,
a datetime.datetime object.
"""
# datetime days are numbered in the Gregorian calendar
# while the calculations from NOAA are distibuted as
# OpenOffice spreadsheets with days numbered from
# 1/1/1900. The difference are those numbers taken for
# 18/12/2010
self.day = when.toordinal() - (734124 - 40529)
t = when.time()
self.time = (t.hour + t.minute / 60.0 + t.second / 3600.0) / 24.0
self.timezone = 0
offset = when.utcoffset()
if not offset is None:
self.timezone = offset.seconds / 3600.0
def __calc(self):
"""
Perform the actual calculations for sunrise, sunset and
a number of related quantities.
The results are stored in the instance variables
sunrise_t, sunset_t and solarnoon_t
"""
timezone = self.timezone # in hours, east is positive
longitude = self.long # in decimal degrees, east is positive
latitude = self.lat # in decimal degrees, north is positive
time = self.time # percentage past midnight, i.e. noon is 0.5
day = self.day # daynumber 1=1/1/1900
Jday = day + 2415018.5 + time - timezone / 24 # Julian day
Jcent = (Jday - 2451545) / 36525 # Julian century
Manom = 357.52911 + Jcent * (35999.05029 - 0.0001537 * Jcent)
Mlong = 280.46646 + Jcent * (36000.76983 + Jcent * 0.0003032) % 360
Eccent = 0.016708634 - Jcent * (0.000042037 + 0.0001537 * Jcent)
Mobliq = 23 + (26 + ((21.448 - Jcent * (46.815 + Jcent * (0.00059 - Jcent * 0.001813)))) / 60) / 60
obliq = Mobliq + 0.00256 * cos(rad(125.04 - 1934.136 * Jcent))
vary = tan(rad(obliq / 2)) * tan(rad(obliq / 2))
Seqcent = sin(rad(Manom)) * (1.914602 - Jcent * (0.004817 + 0.000014 * Jcent)) + sin(rad(2 * Manom)) * (
0.019993 - 0.000101 * Jcent) + sin(rad(3 * Manom)) * 0.000289
Struelong = Mlong + Seqcent
Sapplong = Struelong - 0.00569 - 0.00478 * sin(rad(125.04 - 1934.136 * Jcent))
declination = deg(asin(sin(rad(obliq)) * sin(rad(Sapplong))))
eqtime = 4 * deg(
vary * sin(2 * rad(Mlong)) - 2 * Eccent * sin(rad(Manom)) + 4 * Eccent * vary * sin(rad(Manom)) * cos(
2 * rad(Mlong)) - 0.5 * vary * vary * sin(4 * rad(Mlong)) - 1.25 * Eccent * Eccent * sin(
2 * rad(Manom)))
hourangle = deg(acos(cos(rad(90.833)) / (cos(rad(latitude)) * cos(rad(declination))) - tan(rad(latitude)) * tan(
rad(declination))))
self.solarnoon_t = (720 - 4 * longitude - eqtime + timezone * 60) / 1440
self.sunrise_t = self.solarnoon_t - hourangle * 4 / 1440
self.sunset_t = self.solarnoon_t + hourangle * 4 / 1440