Skip to content

Commit

Permalink
Merge pull request #19 from yasirroni/optimize
Browse files Browse the repository at this point in the history
Fix local change date
  • Loading branch information
SatAgro authored Feb 29, 2024
2 parents 230aa04 + fa9b24f commit a4b2b73
Showing 1 changed file with 92 additions and 108 deletions.
200 changes: 92 additions & 108 deletions suntime/suntime.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import calendar
import math
import datetime
from dateutil import tz
from dateutil.tz import UTC

# CONSTANT
TO_RAD = math.pi/180.0

class SunTimeException(Exception):

def __init__(self, message):
super(SunTimeException, self).__init__(message)


class Sun:
"""
Approximated calculation of sunrise and sunset datetimes. Adapted from:
Expand All @@ -19,146 +19,115 @@ def __init__(self, lat, lon):
self._lat = lat
self._lon = lon

def get_sunrise_time(self, date=None):
"""
Calculate the sunrise time for given date.
:param lat: Latitude
:param lon: Longitude
:param date: Reference date. Today if not provided.
:return: UTC sunrise datetime
:raises: SunTimeException when there is no sunrise and sunset on given location and date
"""
date = datetime.date.today() if date is None else date
sr = self._calc_sun_time(date, True)
if sr is None:
raise SunTimeException('The sun never rises on this location (on the specified date)')
else:
return sr
self.lngHour = self._lon / 15

def get_local_sunrise_time(self, date=None, local_time_zone=tz.tzlocal()):
def get_sunrise_time(self, date=None, tz=None):
"""
Get sunrise time for local or custom time zone.
:param date: Reference date. Today if not provided.
:param local_time_zone: Local or custom time zone.
:return: Local time zone sunrise datetime
:param date: Reference date. datetime.now() if not provided.
:param tz: pytz object with .tzinfo() or None
:return: sunrise datetime.
:raises: SunTimeException when there is no sunrise and sunset on given location and date.
"""
date = datetime.date.today() if date is None else date
sr = self._calc_sun_time(date, True)
if sr is None:
date = datetime.datetime.now() if date is None else date
time_delta = self.get_sun_timedelta(date, tz=tz, isRiseTime=True)
if time_delta is None:
raise SunTimeException('The sun never rises on this location (on the specified date)')
else:
return sr.astimezone(local_time_zone)
if tz:
return datetime.datetime.combine(date, datetime.time(0, 0, tzinfo=tz)) + time_delta
else:
return datetime.datetime.combine(date, datetime.time(0, 0, tzinfo=UTC)) + time_delta

def get_sunset_time(self, date=None):
def get_sunset_time(self, date=None, tz=None):
"""
Calculate the sunset time for given date.
:param lat: Latitude
:param lon: Longitude
:param date: Reference date. Today if not provided.
:return: UTC sunset datetime
:param date: Reference date. datetime.now() if not provided.
:param tz: pytz object with .tzinfo() or None
:return: sunset datetime.
:raises: SunTimeException when there is no sunrise and sunset on given location and date.
"""
date = datetime.date.today() if date is None else date
ss = self._calc_sun_time(date, False)
if ss is None:
raise SunTimeException('The sun never sets on this location (on the specified date)')
else:
return ss

def get_local_sunset_time(self, date=None, local_time_zone=tz.tzlocal()):
"""
Get sunset time for local or custom time zone.
:param date: Reference date
:param local_time_zone: Local or custom time zone.
:return: Local time zone sunset datetime
"""
date = datetime.date.today() if date is None else date
ss = self._calc_sun_time(date, False)
if ss is None:
raise SunTimeException('The sun never sets on this location (on the specified date)')
date = datetime.datetime.now() if date is None else date
time_delta = self.get_sun_timedelta(date, tz=tz, isRiseTime=False)
if time_delta is None:
raise SunTimeException('The sun never rises on this location (on the specified date)')
else:
return ss.astimezone(local_time_zone)
if tz:
return datetime.datetime.combine(date, datetime.time(0, 0, tzinfo=tz)) + time_delta
else:
return datetime.datetime.combine(date, datetime.time(0, 0, tzinfo=UTC)) + time_delta

def _calc_sun_time(self, date, isRiseTime=True, zenith=90.8):
def get_sun_timedelta(self, date, tz, isRiseTime=True, zenith=90.8):
"""
Calculate sunrise or sunset date.
:param date: Reference date
:param tz: pytz object with .tzinfo() or None
:param isRiseTime: True if you want to calculate sunrise time.
:param zenith: Sun reference zenith
:return: UTC sunset or sunrise datetime
:raises: SunTimeException when there is no sunrise and sunset on given location and date
:return: timedelta showing hour, minute, and second of sunrise or sunset
"""
# isRiseTime == False, returns sunsetTime
day = date.day
month = date.month
year = date.year

TO_RAD = math.pi/180.0

# 1. first get the day of the year
N = date.timetuple().tm_yday

# 2. convert the longitude to hour value and calculate an approximate time
lngHour = self._lon / 15

if isRiseTime:
t = N + ((6 - lngHour) / 24)
t = N + ((6 - self.lngHour) / 24)
else: #sunset
t = N + ((18 - lngHour) / 24)
t = N + ((18 - self.lngHour) / 24)

# 3. calculate the Sun's mean anomaly
# 3a. calculate the Sun's mean anomaly
M = (0.9856 * t) - 3.289

# 4. calculate the Sun's true longitude
# 3b. calculate the Sun's true longitude
L = M + (1.916 * math.sin(TO_RAD*M)) + (0.020 * math.sin(TO_RAD * 2 * M)) + 282.634
L = self._force_range(L, 360 ) #NOTE: L adjusted into the range [0,360)

# 5a. calculate the Sun's right ascension

RA = (1/TO_RAD) * math.atan(0.91764 * math.tan(TO_RAD*L))
RA = self._force_range(RA, 360 ) #NOTE: RA adjusted into the range [0,360)

# 5b. right ascension value needs to be in the same quadrant as L
Lquadrant = (math.floor( L/90)) * 90
RAquadrant = (math.floor(RA/90)) * 90
RA = RA + (Lquadrant - RAquadrant)

# 5c. right ascension value needs to be converted into hours
RA = RA / 15
L = self._force_range(L, 360) #NOTE: L adjusted into the range [0,360)

# 6. calculate the Sun's declination
# 4a. calculate the Sun's declination
sinDec = 0.39782 * math.sin(TO_RAD*L)
cosDec = math.cos(math.asin(sinDec))

# 7a. calculate the Sun's local hour angle
cosH = (math.cos(TO_RAD*zenith) - (sinDec * math.sin(TO_RAD*self._lat))) / (cosDec * math.cos(TO_RAD*self._lat))
# 4b. calculate the Sun's local hour angle
cosH = (math.cos(TO_RAD*zenith) - (sinDec * math.sin(TO_RAD*self._lat))) / (cosDec * math.cos(TO_RAD*self._lat))

if cosH > 1:
return None # The sun never rises on this location (on the specified date)
if cosH < -1:
return None # The sun never sets on this location (on the specified date)

# 7b. finish calculating H and convert into hours

# 4c. finish calculating H and convert into hours
if isRiseTime:
H = 360 - (1/TO_RAD) * math.acos(cosH)
else: #setting
H = (1/TO_RAD) * math.acos(cosH)

H = H / 15

#8. calculate local mean time of rising/setting
T = H + RA - (0.06571 * t) - 6.622
# 5a. calculate the Sun's right ascension
RA = (1/TO_RAD) * math.atan(0.91764 * math.tan(TO_RAD*L))
RA = self._force_range(RA, 360) #NOTE: RA adjusted into the range [0,360)

#9. adjust back to UTC
UT = T - lngHour
UT = self._force_range(UT, 24) # UTC time in decimal format (e.g. 23.23)
# 5b. right ascension value needs to be in the same quadrant as L
Lquadrant = (math.floor(L/90)) * 90
RAquadrant = (math.floor(RA/90)) * 90
RA = RA + (Lquadrant - RAquadrant)

#10. Return
hr = self._force_range(int(UT), 24)
min = round((UT - int(UT))*60, 0)
# 5c. right ascension value needs to be converted into hours
RA = RA / 15

return datetime.datetime.combine(date, datetime.time(0, 0, tzinfo=tz.tzutc())) + datetime.timedelta(hours=hr, minutes=min)
# 6. calculate local mean time of rising/setting
T = H + RA - (0.06571 * t) - 6.622

# 7a. adjust back to UTC
UT = T - self.lngHour

if tz:
# 7b. adjust back to local time
UT += tz.utcoffset(date).total_seconds()/3600

# 7c. rounding and impose range bounds
UT = self._force_range(round(UT, 2), 24)

# 8. return timedelta
return datetime.timedelta(hours=UT)

@staticmethod
def _force_range(v, max):
Expand All @@ -167,22 +136,37 @@ def _force_range(v, max):
return v + max
elif v >= max:
return v - max

return v


if __name__ == '__main__':
sun = Sun(85.0, 21.00)
import datetime
import pytz
from suntime import Sun, SunTimeException

latitude = 7.7956
longitude = 110.3695
tz = pytz.timezone("Asia/Jakarta")

day = datetime.datetime(2022, 4, 24)
print(tz.utcoffset(day))

sun = Sun(latitude, longitude)
try:
print(sun.get_local_sunrise_time())
print(sun.get_local_sunset_time())

# On a special date in UTC

abd = datetime.date(2014, 1, 3)
abd_sr = sun.get_local_sunrise_time(abd)
abd_ss = sun.get_local_sunset_time(abd)
print(abd_sr)
print(abd_ss)
print("")
print(datetime.datetime.now())
print()
print(sun.get_sunrise_time())
print(sun.get_sunset_time())
print("")
print(sun.get_sunrise_time(tz=tz))
print(sun.get_sunset_time(tz=tz))
print("")
print(day)
print("")
print(sun.get_sunrise_time(day))
print(sun.get_sunset_time(day))
print("")
print(sun.get_sunrise_time(day, tz=tz))
print(sun.get_sunset_time(day, tz=tz))
except SunTimeException as e:
print("Error: {0}".format(e))

0 comments on commit a4b2b73

Please sign in to comment.