8000 docs/library: add new module · micropython/micropython@2ce8889 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2ce8889

Browse files
committed
docs/library: add new module
Signed-off-by: Lorenzo Cappelletti <lorenzo.cappelletti@gmail.com>
1 parent 2b6e100 commit 2ce8889

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed

docs/library/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ library.
7171
socket.rst
7272
ssl.rst
7373
struct.rst
74+
suntime.rst
7475
sys.rst
7576
time.rst
7677
uasyncio.rst

docs/library/suntime.rst

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
:mod:`suntime` -- sunrise and sunset time
2+
=========================================
3+
4+
.. module:: suntime
5+
:synopsis: sunrise and sunset time
6+
7+
This library provides an approximated calculation of sunrise and sunset time.
8+
It contains two classes, :class:`Sundatetime` and :class:`Suntime`, which
9+
differ from the input and output data types.
10+
11+
12+
Class :class:`Sundatetime`
13+
--------------------------
14+
15+
The following class makes use of module :mod:`datetime` for expressing input
16+
date and output time.
17+
18+
.. class:: Sundatetime(latitude, longitude, altitude=0)
19+
20+
Arguments *latitude* and *longitude* are floats representing the
21+
coordinates of a place on Earth. *altitude* is an integer number for
22+
observer's elevation in meters.
23+
24+
.. method:: Sundatetime.calc_sunrise_sunset(date)
25+
26+
Calculate the sunrise and sunset for the given date. *date* must be an
27+
*aware* `datetime.datetime` object in the range [2000-01-01; 2100-01-01).
28+
Time information is ignored, whereas time zone ``tzinfo`` is used to
29+
provide meaningful output. The results are cached in :data:`sunrise` and
30+
:data:`sunset` .
31+
32+
.. method:: Sundatetime.is_daytime(now)
33+
Sundatetime.is_nighttime(now)
34+
35+
Argument *now* is an *aware* `datetime.datetime` object representing a
36+
point in time. A boolean value is returned whether Sun is above/below the
37+
horizon or not, ``None`` when data are meaningless.
38+
39+
.. method:: Sundatetime.is_sunrise(now)
40+
Sundatetime.is_sunset(now)
41+
42+
Argument *now* is an *aware* `datetime.datetime` object representing a
43+
point in time. A boolean value is returned whether *now* matches
44+
sunrise/sunset time or not, ``None`` when data are meaningless.
45+
46+
The following instance variables are accessible:
47+
48+
.. data:: Sundatetime.latitude
49+
Sundatetime.longitude
50+
51+
Float numbers for coordinates on Earth.
52+
53+
.. data:: Sundatetime.altitude
54+
55+
Elevation in meters for observations above the sea horizon. It corrects
56+
for both apparent dip and terrestrial refraction.
57+
58+
.. data:: Sundatetime.sunrise
59+
Sundatetime.sunset
60+
61+
They hold ``None`` when an instance is created, an *aware*
62+
`datetime.datetime` after :meth:`calc_sunrise_sunset` is called.
63+
64+
.. note::
65+
:data:`sunrise` may occur before 00:00 and :data:`sunset` after 23:59
66+
on calculated *date*. See :ref:`unexpected-results`.
67+
68+
69+
Class :class:`Suntime`
70+
----------------------
71+
72+
The following class makes use of plain integers for expressing input date and
73+
output time.
74+
75+
.. class:: Suntime(latitude, longitude, altitude=0, timezone=0)
76+
77+
Arguments *latitude* and *longitude* are floats representing the
78+
coordinates of a place on Earth. *altitude* is an integer number for
79+
observer's elevation in meters. *timezone* is an integer holding the
80+
timezone offset from UTC in minutes. The results are cached in
81+
:data:`sunrise` and :data:`sunset`.
82+
83+
.. method:: Suntime.calc_sunrise_sunset(year, month, day, dst=0)
84+
85+
Calculate the sunrise and sunset for the given year, month and day.
86+
*year* must be in the range [2000; 2100). *dst* is an integer holding the
87+
offset in minute (usually 60) that accounts for Daylight Saving Time.
88+
89+
.. method:: Suntime.is_daytime(now)
90+
Suntime.is_nighttime(now)
91+
92+
Argument *now* is an integer holding the number of minutes since midnight.
93+
A boolean value is returned whether Sun is above/below the horizon or not,
94+
``None`` when data are meaningless.
95+
96+
.. method:: Suntime.is_sunrise(now)
97+
Suntime.is_sunset(now)
98+
99+
Argument *now* is an an integer holding the number of minutes since midnight.
100+
A boolean value is returned whether *now* matches sunrise/sunset time or not,
101+
``None`` when data are meaningless.
102+
103+
The following instance variables are accessible:
104+
105+
.. data:: Suntime.latitude
106+
Suntime.longitude
107+
108+
Float numbers for coordinates on Earth.
109+
110+
.. data:: Suntime.altitude
111+
112+
Elevation in meters for observations above the sea horizon. It corrects
113+
for both apparent dip and terrestrial refraction.
114+
115+
.. data:: Suntime.sunrise
116+
Suntime.sunset
117+
118+
It holds ``None`` when an instance is created, an integer for the
119+
difference in minutes since 00:00 after :meth:`calc_sunrise_sunset`
120+
is called.
121+
122+
.. note::
123+
``sunrise`` may be negative and ``sunset`` may be greater than 1439
124+
(24 hours). See :ref:`unexpected-results`.
125+
126+
.. _unexpected-results:
127+
128+
Unexpected results
129+
------------------
130+
131+
:class:`Sundatetime` may return unexpected results: :data:`Sundatetime.sunrise`
132+
may come before 00:00 and/or :data:`Sundatetime.sunset` may come after 23:59.
133+
Similarly, :class:`Suntime` may return a negative :data:`Suntime.sunrise`
134+
and/or a :data:`Suntime.sunset` greater or equal to 1440 (24 hours).
135+
136+
Assuming ``date`` is the current date and ``now`` is the current time,
137+
the conditions which may lead to unexpected results are:
138+
139+
* *incorrect time zone*: *date*'s time zone is not consistent with provided
140+
*longitude*. Suitable values for *timezone* and *dst* should be provided.
141+
See example #3 (Novosibirsk) below.
142+
143+
* *Sun is up all day*: close to the poles, Sun never sets in summer/winter
144+
time. For this dates, ``is_daytime()`` holds true for the whole ``date``.
145+
Note that the following holds true: ``sunrise ≤ now < sunset``.
146+
147+
* *Sun is down all day*: close to the poles, Sun never raises in summer/winter
148+
time. For this dates, ``is_nighttime()`` holds true for the whole ``date``.
149+
Note that the following holds false: ``sunrise < sunset``.
150+
151+
* *Sun sets after midnight*: close to the poles, Sun may sets after
152+
23:59. In this case, ``is_daytime()`` and ``is_nighttime()`` behave as
153+
expected. The following condition is true: ``sunrise ≤ now ≤ 23:59 < sunset``.
154+
155+
156+
Examples of usage
157+
-----------------
158+
159+
A typical use case is::
160+
161+
import datetime, suntime, time
162+
163+
class Cet(datetime.timezone):
164+
# See `datetime` documentation
165+
166+
# initialization
167+
CET = Cet()
168+
Rome = suntime.Sundatetime(42.5966460, 12.4360233)
169+
Rome.calc_sunrise_sunset(datetime.datetime(2000, 1, 1, tzinfo=CET))
170+
171+
# main loop (every minute or more)
172+
now = datetime.datetime(*time.localtime()[:5], tzinfo=CET)
173+
if (now.date() > Rome.sunset.date()):
174+
Rome.calc_sunrise_sunset(now)
175+
Rome.is_daytime(now)
176+
177+
The following script shows sunrise and sunset time for several places and dates::
178+
179+
# place: latitude longitude
180+
pl1 = ( 42.5966460, 12.4360233) # Rome
181+
pl2 = ( 51.1627938,-122.9593616) # Vancouver
182+
pl3 = (-33.9252192, 18.4240762) # CapeTown
183+
pl4 = ( 55.1574890, 82.8547661) # Novosibirsk
184+
pl5 = ( 78.6560170, 16.3447384) # Pyramiden
185+
pl6 = pl5
186+
pl7 = (-77.7817838, 166.4561470) # McMurdo
187+
pl8 = pl7
188+
189+
# date: YY MM DD sunrise sunset
190+
dt1 = (2000, 1, 1) # 7:37 16:49 - https://www.timeanddate.com/sun/italy/rome?month=1&year=2000
191+
dt2 = (2014, 10, 3) # 7:15 18:46 - https://www.timeanddate.com/sun/canada/vancouver?month=10&year=2014
192+
dt3 = (2016, 12, 21) # 5:32 19:57 - https://www.timeanddate.com/sun/south-africa/cape-town?month=12&year=2016
193+
dt4 = (2021, 4, 24) # 6:04 20:50 - https://www.timeanddate.com/sun/russia/novosibirsk?month=4&year=2021
194+
dt5 = (2040, 8, 25) # up all day - https://www.timeanddate.com/sun/@2729216?month=8&year=2033
195+
dt6 = (2040, 8, 26) # 00:09
196+
# 1:45 23:41 - https://www.timeanddate.com/sun/@2729216?month=8&year=2040
197+
dt7 = (2033, 8, 10) # down all day - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=8&year=2033
198+
dt8 = (2033, 10, 21) # 3:00 24:13 - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=10&year=2033
199+
200+
# timezone offsets and DSTs (in hours)
201+
tz1 = ( 1, 0)
202+
tz2 = (-8, 1)
203+
tz3 = ( 2, 0)
204+
tz4 = ( 0, 0) # wrong; it generates negative hour because actual timezone is (7, 0)
205+
tz5 = ( 1, 1)
206+
tz6 = ( 1, 1)
207+
tz7 = (13,-1)
208+
tz8 = (13, 0)
209+
210+
The following snippet of code makes use of class :class:`Sundatetime`::
211+
212+
from suntime import Sundatetime
213+
from datetime import datetime, timedelta, timezone
214+
215+
class Tz(timezone):
216+
def __init__(self, hours, dst=0):
217+
super().__init__(timedelta(hours=hours))
218+
self._dst = dst
219+
220+
def dst(self, dt):
221+
return timedelta(hours=self._dst) if self.isdst(dt) else timedelta(0)
222+
223+
def isdst(self, dt):
224+
return self._dst != 0
225+
226+
print('Rome:')
227+
sd1 = Sundatetime(*pl1)
228+
sd1.calc_sunrise_sunset(datetime(*dt1, tzinfo=Tz(*tz1)))
229+
print('>', sd1.sunrise) # 2000-01-01 07:40:00+01:00
230+
print('>', sd1.sunset ) # 2000-01-01 16:47:00+01:00
231+
232+
print('Vancouver:')
233+
sd2 = Sundatetime(*pl2)
234+
sd2.calc_sunrise_sunset(datetime(*dt2, tzinfo=Tz(*tz2)))
235+
print('>', sd2.sunrise) # 2014-10-03 07:16:00-08:00
236+
print('>', sd2.sunset ) # 2014-10-03 18:46:00-08:00
237+
238+
print('Cape Town:')
239+
sd3 = Sundatetime(*pl3)
240+
sd3.calc_sunrise_sunset(datetime(*dt3, tzinfo=Tz(*tz3)))
241+
print('>', sd3.sunrise) # 2016-12-21 05:32:00+02:00
242+
print('>', sd3.sunset ) # 2016-12-21 19:57:00+02:00
243+
244+
print('Novosibirsk:')
245+
sd4 = Sundatetime(*pl4)
246+
sd4.calc_sunrise_sunset(datetime(*dt4, tzinfo=Tz(*tz4)))
247+
print('>', sd4.sunrise) # 2021-04-23 23:04:00+00:00
248+
print('>', sd4.sunset ) # 2021-04-24 13:49:00+00:00
249+
250+
print('Pyramiden:')
251+
sd5 = Sundatetime(*pl5)
252+
sd5.calc_sunrise_sunset(datetime(*dt5, tzinfo=Tz(*tz5)))
253+
print('>', sd5.sunrise) # 2040-08-24 12:57:00+02:00
254+
print('>', sd5.sunset ) # 2040-08-26 12:57:00+02:00
255+
256+
print('Pyramiden:')
257+
sd6 = Sundatetime(*pl6)
258+
sd6.calc_sunrise_sunset(datetime(*dt6, tzinfo=Tz(*tz6)))
259+
print('>', sd6.sunrise) # 2040-08-26 01:35:00+02:00
260+
print('>', sd6.sunset ) # 2040-08-27 00:18:00+02:00
261+
262+
print('McMurdo:')
263+
sd7 = Sundatetime(*pl7)
264+
sd7.calc_sunrise_sunset(datetime(*dt7, tzinfo=Tz(*tz7)))
265+
print('>', sd7.sunrise) # 2033-08-11 13:00:00+12:00
266+
print('>', sd7.sunset ) # 2033-08-09 13:00:00+12:00
267+
268+
print('McMurdo:')
269+
sd8 = Sundatetime(*pl8)
270+
sd8.calc_sunrise_sunset(datetime(*dt8, tzinfo=Tz(*tz8)))
271+
print('>', sd8.sunrise) # 2033-10-21 03:06:00+13:00
272+
print('>', sd8.sunset ) # 2033-10-22 00:12:00+13:00
273+
274+
If :mod:`datetime` module is not available, the same input data can feed class
275+
:class:`Suntime`::
276+
277+
from suntime import Suntime
278+
279+
st1 = Suntime(*pl1, timezone=tz1[0]*60)
280+
st1.calc_sunrise_sunset(*dt1, dst=tz1[1]*60)
281+
print('Rome:')
282+
print('>', divmod(st1.sunrise, 60)) # (7, 40)
283+
print('>', divmod(st1.sunset , 60)) # (16, 47)
284+
285+
st2 = Suntime(*pl2, timezone=tz2[0]*60)
286+
st2.calc_sunrise_sunset(*dt2, dst=tz2[1]*60)
287+
print('Vancouver:')
288+
print('>', divmod(st2.sunrise, 60)) # (7, 16)
289+
print('>', divmod(st2.sunset , 60)) # (18, 46)
290+
291+
st3 = Suntime(*pl3, timezone=tz3[0]*60)
292+
st3.calc_sunrise_sunset(*dt3, dst=tz3[1]*60)
293+
print('Cape Town:')
294+
print('>', divmod(st3.sunrise, 60)) # (5, 32)
295+
print('>', divmod(st3.sunset , 60)) # (19, 57)
296+
297+
st4 = Suntime(*pl4, timezone=tz4[0]*60)
298+
st4.calc_sunrise_sunset(*dt4, dst=tz4[1]*60)
299+
print('Novosibirsk:')
300+
print('>', divmod(st4.sunrise, 60)) # (-1, 4)
301+
print('>', divmod(st4.sunset , 60)) # (13, 49)
302+
303+
st5 = Suntime(*pl5, timezone=tz5[0]*60)
304+
st5.calc_sunrise_sunset(*dt5, dst=tz5[1]*60)
305+
print('Pyramiden:')
306+
print('>', divmod(st5.sunrise, 60)) # (-12, 57)
307+
print('>', divmod(st5.sunset , 60)) # (36, 57)
308+
309+
st6 = Suntime(*pl6, timezone=tz6[0]*60)
310+
st6.calc_sunrise_sunset(*dt6, dst=tz6[1]*60)
311+
print('Pyramiden:')
312+
print('>', divmod(st6.sunrise, 60)) # (1, 35)
313+
print('>', divmod(st6.sunset , 60)) # (24, 18)
314+
315+
st7 = Suntime(*pl7, timezone=tz7[0]*60)
316+
st7.calc_sunrise_sunset(*dt7, dst=tz7[1]*60)
317+
print('McMurdo:')
318+
print('>', divmod(st7.sunrise, 60)) # (37, 0)
319+
print('>', divmod(st7.sunset , 60)) # (-11, 0)
320+
321+
st8 = Suntime(*pl8, timezone=tz8[0]*60)
322+
st8.calc_sunrise_sunset(*dt8, dst=tz8[1]*60)
323+
print('McMurdo:')
324+
print('>', divmod(st8.sunrise, 60)) # (3, 6)
325+
print('>', divmod(st8.sunset , 60)) # (24, 12)

0 commit comments

Comments
 (0)
0