8000 python-ecosys/suntime: add new module · micropython/micropython-lib@9d1a5d3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9d1a5d3

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

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