Skip to content

Commit 02cb952

Browse files
committed
python-ecosys/suntime: add new module
Signed-off-by: Lorenzo Cappelletti <lorenzo.cappelletti@gmail.com>
1 parent 15f4fdd commit 02cb952

File tree

5 files changed

+579
-0
lines changed

5 files changed

+579
-0
lines changed

python-ecosys/suntime/example.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# example.py
2+
3+
import datetime, suntime, time
4+
5+
class Cet(datetime.timezone):
6+
def __init__(self):
7+
super().__init__(datetime.timedelta(hours=1), "CET")
8+
9+
def dst(self, dt):
10+
return datetime.timedelta(hours=1) if self.isdst(dt) else datetime.timedelta(0)
11+
12+
def tzname(self, dt):
13+
return 'CEST' if self.isdst(dt) else 'CET'
14+
15+
def isdst(self, dt):
16+
if dt is None:
17+
return False
18+
year, month, day, hour, minute, second, tz = dt.tuple()
19+
if not 2000 <= year < 2100:
20+
raise ValueError
21+
if 3 < month < 10:
22+
return True
23+
if month == 3:
24+
beg = 31 - (5*year//4 + 4) % 7 # last Sunday of March
25+
if day < beg: return False
26+
if day > beg: return True
27+
return hour >= 3
28+
if month == 10:
29+
end = 31 - (5*year//4 + 1) % 7 # last Sunday of October
30+
if day < end: return True
31+
if day > end: return False
32+
return hour < 3
33+
return False
34+
35+
# initialization
36+
CET = Cet()
37+
Rome = suntime.Sundatetime(42.5966460, 12.4360233)
38+
Rome.calc_sunrise_sunset(datetime.datetime(2000, 1, 1, tzinfo=CET))
39+
40+
# main loop (every minute or more)
41+
now = datetime.datetime(*time.localtime()[:5], tzinfo=CET)
42+
if (now.date() > Rome.sunset.date()):
43+
Rome.calc_sunrise_sunset(now)
44+
print (now, Rome.is_daytime(now))
45+
46+
47+
#######################################################################
48+
49+
# place: latitude longitude
50+
pl1 = ( 42.5966460, 12.4360233) # Rome
51+
pl2 = ( 51.1627938,-122.9593616) # Vancouver
52+
pl3 = (-33.9252192, 18.4240762) # CapeTown
53+
pl4 = ( 55.1574890, 82.8547661) # Novosibirsk
54+
pl5 = ( 78.6560170, 16.3447384) # Pyramiden
55+
pl6 = pl5
56+
pl7 = (-77.7817838, 166.4561470) # McMurdo
57+
pl8 = pl7
58+
59+
# date: YY MM DD sunrise sunset
60+
dt1 = (2000, 1, 1) # 7:37 16:49 - https://www.timeanddate.com/sun/italy/rome?month=1&year=2000
61+
dt2 = (2014, 10, 3) # 7:15 18:46 - https://www.timeanddate.com/sun/canada/vancouver?month=10&year=2014
62+
dt3 = (2016, 12, 21) # 5:32 19:57 - https://www.timeanddate.com/sun/south-africa/cape-town?month=12&year=2016
63+
dt4 = (2021, 4, 24) # 6:04 20:50 - https://www.timeanddate.com/sun/russia/novosibirsk?month=4&year=2021
64+
dt5 = (2040, 8, 25) # up all day - https://www.timeanddate.com/sun/@2729216?month=8&year=2033
65+
dt6 = (2040, 8, 26) # 00:09
66+
# 1:45 23:41 - https://www.timeanddate.com/sun/@2729216?month=8&year=2040
67+
dt7 = (2033, 8, 10) # down all day - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=8&year=2033
68+
dt8 = (2033, 10, 21) # 3:00 24:13 - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=10&year=2033
69+
70+
# timezone offsets and DSTs (in hours)
71+
tz1 = ( 1, 0)
72+
tz2 = (-8, 1)
73+
tz3 = ( 2, 0)
74+
tz4 = ( 0, 0) # wrong; it generates negative hour because actual timezone is (7, 0)
75+
tz5 = ( 1, 1)
76+
tz6 = ( 1, 1)
77+
tz7 = (13,-1)
78+
tz8 = (13, 0)
79+
80+
81+
#######################################################################
82+
83+
# if `datetime` module is available
84+
from suntime import Sundatetime
85+
from datetime import datetime, timedelta, timezone
86+
87+
class Tz(timezone):
88+
def __init__(self, hours, dst=0):
89+
super().__init__(timedelta(hours=hours))
90+
self._dst = dst
91+
92+
def dst(self, dt):
93+
return timedelta(hours=self._dst) if self.isdst(dt) else timedelta(0)
94+
95+
def isdst(self, dt):
96+
return self._dst != 0
97+
98+
now = datetime(*dt1, tzinfo=Tz(*tz1))
99+
sd1 = Sundatetime(*pl1)
100+
sd1.calc_sunrise_sunset(now)
101+
print('Rome:', now)
102+
print('>', sd1.sunrise) # 2000-01-01 07:40:00+01:00
103+
print('>', sd1.sunset ) # 2000-01-01 16:47:00+01:00
104+
105+
now = datetime(*dt2, tzinfo=Tz(*tz2))
106+
sd2 = Sundatetime(*pl2)
107+
sd2.calc_sunrise_sunset(now)
108+
print('Vancouver:', now)
109+
print('>', sd2.sunrise) # 2014-10-03 07:16:00-08:00
110+
print('>', sd2.sunset ) # 2014-10-03 18:46:00-08:00
111+
112+
now = datetime(*dt3, tzinfo=Tz(*tz3))
113+
sd3 = Sundatetime(*pl3)
114+
sd3.calc_sunrise_sunset(now)
115+
print('Cape Town:', now)
116+
print('>', sd3.sunrise) # 2016-12-21 05:32:00+02:00
117+
print('>', sd3.sunset ) # 2016-12-21 19:57:00+02:00
118+
119+
now = datetime(*dt4, tzinfo=Tz(*tz4))
120+
sd4 = Sundatetime(*pl4)
121+
sd4.calc_sunrise_sunset(now)
122+
print('Novosibirsk:', now)
123+
print('>', sd4.sunrise) # 2021-04-23 23:04:00+00:00
124+
print('>', sd4.sunset ) # 2021-04-24 13:49:00+00:00
125+
126+
now = datetime(*dt5, tzinfo=Tz(*tz5))
127+
sd5 = Sundatetime(*pl5)
128+
sd5.calc_sunrise_sunset(now)
129+
print('Pyramiden:', now)
130+
print('>', sd5.sunrise) # 2040-08-24 12:57:00+02:00
131+
print('>', sd5.sunset ) # 2040-08-26 12:57:00+02:00
132+
133+
now = datetime(*dt6, tzinfo=Tz(*tz6))
134+
sd6 = Sundatetime(*pl6)
135+
sd6.calc_sunrise_sunset(now)
136+
print('Pyramiden:', now)
137+
print('>', sd6.sunrise) # 2040-08-26 01:35:00+02:00
138+
print('>', sd6.sunset ) # 2040-08-27 00:18:00+02:00
139+
140+
now = datetime(*dt7, tzinfo=Tz(*tz7))
141+
sd7 = Sundatetime(*pl7)
142+
sd7.calc_sunrise_sunset(now)
143+
print('McMurdo:', now)
144+
print('>', sd7.sunrise) # 2033-08-11 13:00:00+12:00
145+
print('>', sd7.sunset ) # 2033-08-09 13:00:00+12:00
146+
147+
now = datetime(*dt8, tzinfo=Tz(*tz8))
148+
sd8 = Sundatetime(*pl8)
149+
sd8.calc_sunrise_sunset(now)
150+
print('McMurdo:', now)
151+
print('>', sd8.sunrise) # 2033-10-21 03:06:00+13:00
152+
print('>', sd8.sunset ) # 2033-10-22 00:12:00+13:00
153+
154+
155+
#######################################################################
156+
157+
from suntime import Suntime
158+
159+
st1 = Suntime(*pl1, timezone=tz1[0]*60)
160+
st1.calc_sunrise_sunset(*dt1, dst=tz1[1]*60)
161+
print('Rome:', dt1, tz1)
162+
print('>', divmod(st1.sunrise, 60)) # (7, 40)
163+
print('>', divmod(st1.sunset , 60)) # (16, 47)
164+
165+
st2 = Suntime(*pl2, timezone=tz2[0]*60)
166+
st2.calc_sunrise_sunset(*dt2, dst=tz2[1]*60)
167+
print('Vancouver:', dt2, tz2)
168+
print('>', divmod(st2.sunrise, 60)) # (7, 16)
169+
print('>', divmod(st2.sunset , 60)) # (18, 46)
170+
171+
st3 = Suntime(*pl3, timezone=tz3[0]*60)
172+
st3.calc_sunrise_sunset(*dt3, dst=tz3[1]*60)
173+
print('Cape Town:', dt3, tz3)
174+
print('>', divmod(st3.sunrise, 60)) # (5, 32)
175+
print('>', divmod(st3.sunset , 60)) # (19, 57)
176+
177+
st4 = Suntime(*pl4, timezone=tz4[0]*60)
178+
st4.calc_sunrise_sunset(*dt4, dst=tz4[1]*60)
179+
print('Novosibirsk:', dt4, tz4)
180+
print('>', divmod(st4.sunrise, 60)) # (-1, 4)
181+
print('>', divmod(st4.sunset , 60)) # (13, 49)
182+
183+
st5 = Suntime(*pl5, timezone=tz5[0]*60)
184+
st5.calc_sunrise_sunset(*dt5, dst=tz5[1]*60)
185+
print('Pyramiden:', dt5, tz5)
186+
print('>', divmod(st5.sunrise, 60)) # (-12, 57)
187+
print('>', divmod(st5.sunset , 60)) # (36, 57)
188+
189+
st6 = Suntime(*pl6, timezone=tz6[0]*60)
190+
st6.calc_sunrise_sunset(*dt6, dst=tz6[1]*60)
191+
print('Pyramiden:', dt6, tz6)
192+
print('>', divmod(st6.sunrise, 60)) # (1, 35)
193+
print('>', divmod(st6.sunset , 60)) # (24, 18)
194+
195+
st7 = Suntime(*pl7, timezone=tz7[0]*60)
196+
st7.calc_sunrise_sunset(*dt7, dst=tz7[1]*60)
197+
print('McMurdo:', dt7, tz7)
198+
print('>', divmod(st7.sunrise, 60)) # (37, 0)
199+
print('>', divmod(st7.sunset , 60)) # (-11, 0)
200+
201+
st8 = Suntime(*pl8, timezone=tz8[0]*60)
202+
st8.calc_sunrise_sunset(*dt8, dst=tz8[1]*60)
203+
print('McMurdo:', dt8, tz8)
204+
print('>', divmod(st8.sunrise, 60)) # (3, 6)
205+
print('>', divmod(st8.sunset , 60)) # (24, 12)

python-ecosys/suntime/metadata.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
srctype = cpython
2+
type = module
3+
version = 1.0.0
4+
author = Lorenzo Cappelletti

python-ecosys/suntime/setup.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import sys
2+
3+
# Remove current dir from sys.path, otherwise setuptools will peek up our
4+
# module instead of system's.
5+
sys.path.pop(0)
6+
from setuptools import setup
7+
8+
sys.path.append("..")
9+
import sdist_upip
10+
11+
setup(
12+
name="micropython-suntime",
13+
version="1.0.0",
14+
description="suntime module for MicroPython",
15+
long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.",
16+
url="https://github.com/micropython/micropython-lib",
17+
author="micropython-lib Developers",
18+
author_email="micro-python@googlegroups.com",
19+
maintainer="micropython-lib Developers",
20+
maintainer_email="micro-python@googlegroups.com",
21+
license="GPL",
22+
cmdclass={"sdist": sdist_upip.sdist},
23+
py_modules=["suntime"],
24+
)

python-ecosys/suntime/suntime.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# suntime.py
2+
3+
__version__ = "1.0.0"
4+
5+
#--- Help Functions -------------------------------------------------#
6+
7+
from math import acos, asin, ceil, cos, degrees as deg, fmod as mod,\
8+
sqrt, radians as rad, sin
9+
10+
# https://en.wikipedia.org/wiki/Sunrise_equation
11+
# https://en.wikipedia.org/wiki/Julian_day
12+
# m = round((M - 14)/12)
13+
# JDN = round(1461*(Y + 4800 + m)/4)\
14+
# + round((367*(M - 2 - 12*m))/12)\
15+
# - round((3*(round((Y + 4900 + m)/100)))/4)\
16+
# + D - 32075
17+
def equation (n, lat, lon, alt):
18+
# n = ceil(Jd - 2451545.0 + 0.0008)
19+
assert(0 <= n < 36525) # days in 21st century
20+
Js = n - lon/360
21+
M = mod(357.5291 + 0.98560028*Js, 360)
22+
C = 1.9148*sin(rad(M)) + 0.0200*sin(rad(2*M)) + 0.0003*sin(rad(3*M))
23+
λ = mod(M + C + 180 + 102.9372, 360)
24+
Jt = 2451545.0 + Js + 0.0053*sin(rad(M)) - 0.0069*sin(rad(2*λ))
25+
sinδ = sin(rad(λ))*sin(rad(23.44))
26+
cosω0 = (sin(rad(-0.83 - 2.076*sqrt(alt)/60)) - sin(rad(lat))*sinδ)\
27+
/ (cos(rad(lat))*cos(asin(sinδ)))
28+
if cosω0 <= -1.0:
29+
ω0 = 360
30+
elif cosω0 >= 1.0:
31+
ω0 = -360
32+
else:
33+
ω0 = deg(acos(cosω0))
34+
Jr = Jt - ω0/360
35+
Js = Jt + ω0/360
36+
return Jr, Js
37+
38+
39+
#--- Suntime --------------------------------------------------------#
40+
41+
# 500 = 499 (non-leap years before 2000) + 1 (Jan 1st 2000)
42+
def day2000(year, month, day):
43+
assert(2000 <= year < 2100)
44+
assert(1 <= month <= 12)
45+
assert(1 <= day <= 31)
46+
MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30)
47+
return (year - 2000)*365\
48+
+ sum(MONTH_DAYS[:month - 1])\
49+
+ (year if month >= 3 else year - 1)//4\
50+
+ day\
51+
- 500
52+
53+
def jdate2time (Jd, n, tz=0):
54+
jtime = Jd - (2451545 + n)
55+
minutes = round(jtime*1440) + 720 + tz
56+
return minutes
57+
58+
class Suntime:
59+
def __init__(self, latitude, longitude, altitude=0, timezone=0):
60+
self.latitude = latitude
61+
self.longitude = longitude
62+
self.altitude = altitude
63+
self.timezone = timezone
64+
self.sunrise = None
65+
self.sunset = None
66+
67+
def calc_sunrise_sunset(self, year, month, day, dst=0):
68+
n = day2000(year, month, day)
69+
Jr, Js = equation(n, self.latitude, self.longitude, self.altitude)
70+
tz = self.timezone + dst
71+
self.sunrise = jdate2time(Jr, n, tz)
72+
self.sunset = jdate2time(Js, n, tz)
73+
74+
def is_daytime (self, minutes):
75+
if self.sunrise is None or self.sunset is None:
76+
return None
77+
if not 0 <= minutes < 1440:
78+
return None
79+
return self.sunrise <= minutes < self.sunset
80+
81+
def is_nighttime (self, minutes):
82+
daytime = self.is_daytime(minutes)
83+
if daytime is None:
84+
return None
85+
return not daytime
86+
87+
def is_sunrise (self, minutes):
88+
return self.is_daytime(minutes) and minutes == self.sunrise
89+
90+
def is_sunset (self, minutes):
91+
return self.is_nighttime(minutes) and minutes == self.sunset
92+
93+
94+
#--- Sundatetime ----------------------------------------------------#
95+
96+
import datetime as dt
97+
98+
def jdate2datetime(Jd, date):
99+
days = date.toordinal()
100+
n = days - dt.datetime(2000, 1, 1).toordinal()
101+
dt_ = dt.datetime(0, 0, days, minute=jdate2time(Jd, n), tzinfo=dt.timezone.utc)
102+
if date.tzinfo:
103+
dt_ = dt_.astimezone(date.tzinfo)
104+
return dt_
105+
106+
class Sundatetime:
107+
def __init__(self, latitude, longitude, altitude=0):
108+
self.latitude = latitude
109+
self.longitude = longitude
110+
self.altitude = altitude
111+
self.sunrise = None
112+
self.sunset = None
113+
114+
def calc_sunrise_sunset(self, date):
115+
n = date.toordinal() - dt.datetime(2000, 1, 1).toordinal()
116+
Jr, Js = equation(n, self.latitude, self.longitude, self.altitude)
117+
self.sunrise = jdate2datetime(Jr, date)
118+
self.sunset = jdate2datetime(Js, date)
119+
120+
def is_daytime (self, now):
121+
if self.sunrise is None or self.sunset is None:
122+
return None
123+
if self.sunrise >= self.sunset:
124+
return None
125+
return self.sunrise <= now < self.sunset
126+
127+
def is_nighttime (self, now):
128+
daytime = self.is_daytime(now)
129+
if daytime is None:
130+
return None
131+
return not daytime
132+
133+
def is_sunrise (self, now):
134+
return self.is_daytime(now) and self.sunrise == now
135+
136+
def is_sunset (self, now):
137+
return self.is_nighttime(now) and self.sunset == now

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy