Skip to content

Commit

Permalink
Merge pull request #25 from SatAgro/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
kstopa authored Mar 5, 2024
2 parents a4b2b73 + 2cd75f7 commit 6e907a9
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 88 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Test and Quality CI

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]

jobs:
build:

runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [ "3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install flake8 pytest
- name: Lint with Flake8
run: |
flake8 .
- name: Run Tests
run: |
pytest tests.py
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
example.py
suntime.egg-info/
suntime/__pycache__/
__pycache__/
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ You can use the library to get UTC and local time sunrise and sunset times typin

```python3
import datetime
from dateutil import tz
from suntime import Sun, SunTimeException

latitude = 51.21
longitude = 21.01
warsaw_lat = 51.21
warsaw_lon = 21.01

sun = Sun(latitude, longitude)
sun = Sun(warsaw_lat, warsaw_lon)

# Get today's sunrise and sunset in UTC
today_sr = sun.get_sunrise_time()
Expand All @@ -32,8 +33,8 @@ print('Today at Warsaw the sun raised at {} and get down at {} UTC'.

# On a special date in your machine's local time zone
abd = datetime.date(2014, 10, 3)
abd_sr = sun.get_local_sunrise_time(abd)
abd_ss = sun.get_local_sunset_time(abd)
abd_sr = sun.get_sunrise_time(abd, tz.gettz('Europe/Warsaw'))
abd_ss = sun.get_sunset_time(abd, tz.gettz('Europe/Warsaw'))
print('On {} the sun at Warsaw raised at {} and get down at {}.'.
format(abd, abd_sr.strftime('%H:%M'), abd_ss.strftime('%H:%M')))

Expand All @@ -42,23 +43,35 @@ latitude = 87.55
longitude = 0.1
sun = Sun(latitude, longitude)
try:
abd_sr = sun.get_local_sunrise_time(abd)
abd_ss = sun.get_local_sunset_time(abd)
abd_sr = sun.get_sunrise_time(abd)
abd_ss = sun.get_sunset_time(abd)
print('On {} at somewhere in the north the sun raised at {} and get down at {}.'.
format(abd, abd_sr.strftime('%H:%M'), abd_ss.strftime('%H:%M')))
except SunTimeException as e:
print("Error: {0}.".format(e))
```

## Testing

To run the tests, type:

> pytest tests.py
For linting checks, type:

> flake8 .
## License

Copyright © 2019 SatAgro Sp. z o.o. and contributors:
Copyright © 2024 [SatAgro Sp. z o.o.](https://satagro.pl) and our awesome contributors:

* Krzysztof Stopa ([kstopa](https://github.com/kstopa))
* Andrey Kobyshev ([yokotoka](https://github.com/yokotoka))
* Matthias ([palto42](https://github.com/plato42))
* Hadrien Bertrand ([hbertrand](https://github.com/hbertrand))

* Ingo Randolf ([i-n-g-o](https://github.com/i-n-g-o))
* John Vandenberg ([jayvdb](https://github.com/jayvdb))
* Krzysztof Stopa ([kstopa](https://github.com/kstopa))
* Matthias ([palto42](https://github.com/plato42))
* Muhammad Yasirroni ([yasirroni](https://github.com/yasirroni))

This file is part of SunTime library for python (SunTime).

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-dateutil>=2.1.0
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 120
per-file-ignores = __init__.py:F401
exclude = .git,.venv,venv,__pycache__,build,dist
7 changes: 3 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@
long_description = f.read()

setup(name='suntime',
version='1.2.5',
version='1.3.0',
description='Simple sunset and sunrise time calculation python library',
long_description=long_description,
author='Krzysztof Stopa',
url='https://github.com/SatAgro/suntime',
copyright='Copyright 2019 SatAgro',
copyright='Copyright 2024 SatAgro',
license='LGPLv3',
packages=['suntime'],
install_requires=['python-dateutil']
)
install_requires=['python-dateutil'])
124 changes: 51 additions & 73 deletions suntime/suntime.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import math
import datetime
from dateutil.tz import UTC
import warnings
from datetime import date, datetime, timedelta, time, timezone


# 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 @@ -21,92 +24,96 @@ def __init__(self, lat, lon):

self.lngHour = self._lon / 15

def get_sunrise_time(self, date=None, tz=None):
def get_sunrise_time(self, at_date=date.today(), time_zone=timezone.utc):
"""
:param date: Reference date. datetime.now() if not provided.
:param tz: pytz object with .tzinfo() or None
:param at_date: Reference date. datetime.now() if not provided.
:param time_zone: 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.datetime.now() if date is None else date
time_delta = self.get_sun_timedelta(date, tz=tz, isRiseTime=True)
time_delta = self.get_sun_timedelta(at_date, time_zone=time_zone, is_rise_time=True)
if time_delta is None:
raise SunTimeException('The sun never rises on this location (on the specified date)')
else:
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
return datetime.combine(at_date, time(tzinfo=time_zone)) + time_delta

def get_sunset_time(self, date=None, tz=None):
def get_sunset_time(self, at_date=date.today(), time_zone=timezone.utc):
"""
Calculate the sunset time for given date.
:param date: Reference date. datetime.now() if not provided.
:param tz: pytz object with .tzinfo() or None
:param at_date: Reference date. datetime.now() if not provided.
:param time_zone: 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.datetime.now() if date is None else date
time_delta = self.get_sun_timedelta(date, tz=tz, isRiseTime=False)
time_delta = self.get_sun_timedelta(at_date, time_zone=time_zone, is_rise_time=False)
if time_delta is None:
raise SunTimeException('The sun never rises on this location (on the specified date)')
else:
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
return datetime.combine(at_date, time(tzinfo=time_zone)) + time_delta

def get_local_sunrise_time(self, at_date, time_zone):
""" DEPRECATED: Use get_sunrise_time() instead. """
warnings.warn("get_local_sunrise_time is deprecated and will be removed in future versions."
"Use get_sunrise_time with proper time zone", DeprecationWarning)
return self.get_sunrise_time(at_date, time_zone)

def get_local_sunset_time(self, at_date, time_zone):
""" DEPRECATED: Use get_sunset_time() instead. """
warnings.warn("get_local_sunset_time is deprecated and will be removed in future versions."
"Use get_sunset_time with proper time zone.", DeprecationWarning)
return self.get_sunset_time(at_date, time_zone)

def get_sun_timedelta(self, date, tz, isRiseTime=True, zenith=90.8):
def get_sun_timedelta(self, at_date, time_zone, is_rise_time=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 at_date: Reference date
:param time_zone: pytz object with .tzinfo() or None
:param is_rise_time: True if you want to calculate sunrise time.
:param zenith: Sun reference zenith
:return: timedelta showing hour, minute, and second of sunrise or sunset
"""

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

# 2. convert the longitude to hour value and calculate an approximate time
if isRiseTime:
if is_rise_time:
t = N + ((6 - self.lngHour) / 24)
else: #sunset
else: # sunset
t = N + ((18 - self.lngHour) / 24)

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

# 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)
L = self._force_range(L, 360) # NOTE: L adjusted into the range [0,360)

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

# 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))
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)

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

# 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)
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
Lquadrant = (math.floor(L/90)) * 90
RAquadrant = (math.floor(RA/90)) * 90
RA = RA + (Lquadrant - RAquadrant)

Expand All @@ -115,19 +122,23 @@ def get_sun_timedelta(self, date, tz, isRiseTime=True, zenith=90.8):

# 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:

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

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

UT = round(UT, 2)
print("UT 1 {}".format(UT))
if is_rise_time:
UT = self._force_range(UT, 24)
print(UT)

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

@staticmethod
def _force_range(v, max):
Expand All @@ -137,36 +148,3 @@ def _force_range(v, max):
elif v >= max:
return v - max
return v

if __name__ == '__main__':
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("")
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))
Loading

0 comments on commit 6e907a9

Please sign in to comment.