supermoon 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Tony Rice
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: supermoon
3
+ Version: 0.1.0
4
+ Summary: Find supermoons according to each of the popular (and conflicting) definitions
5
+ Author-email: Tony Rice <tony@rtphokie.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rtphokie/supermoon
8
+ Project-URL: Issues, https://github.com/rtphokie/supermoon/issues
9
+ Keywords: supermoon,moon,perigee,full moon,astronomy
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: skyfield>=1.31
19
+ Requires-Dist: numpy
20
+ Requires-Dist: tzlocal
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest; extra == "test"
23
+ Requires-Dist: pytz; extra == "test"
24
+ Requires-Dist: requests; extra == "test"
25
+ Requires-Dist: beautifulsoup4; extra == "test"
26
+ Requires-Dist: lxml; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # supermoon
30
+
31
+ Finds supermoons, and shows which of the popular (and conflicting) definitions each one meets.
32
+ For background, see the [Wikipedia article on supermoons](https://en.wikipedia.org/wiki/Supermoon).
33
+
34
+ A supermoon, or perigean full moon, is a full Moon that happens near the point in the Moon's orbit
35
+ closest to Earth, so the Moon looks slightly larger. The term has no astronomical meaning. An
36
+ astrologer coined it, not an astronomer. It describes a timing coincidence in the Moon's synodic
37
+ month, and every popular definition of it is essentially arbitrary.
38
+
39
+ ## Installation
40
+
41
+ ```
42
+ pip install supermoon
43
+ ```
44
+
45
+ The first time you run it, supermoon downloads the JPL DE421 ephemeris (about 17 MB) to
46
+ `~/.supermoon`. To keep it somewhere else, or to reuse a copy you already have, set the
47
+ `SUPERMOON_DATA` environment variable to that directory. DE421 covers the years 1900 through 2050.
48
+
49
+ ## Command line usage
50
+
51
+ ```
52
+ usage: supermoon [-h] [--cnt CNT] [-B] [-P] [-D] [-A] [-C] [year] [endyear]
53
+
54
+ positional arguments:
55
+ year find supermoons for this year (optional, defaults to current date forward)
56
+ endyear stop finding supermoons (optional)
57
+
58
+ options:
59
+ -h, --help show this help message and exit
60
+ --cnt CNT moons to show
61
+ -B, --brief brief output
62
+ -P, --perigee include perigee time
63
+ -D, --distance include distances
64
+ -A, --angulardiameter
65
+ include angular diameter
66
+ -C, --csv also write results to supermoons.csv
67
+ ```
68
+
69
+ Examples:
70
+
71
+ ```
72
+ $ supermoon # the next supermoon
73
+ $ supermoon --cnt 3 -P -D -A # the next 3, with perigee, distances and angular diameter
74
+ $ supermoon 2029 # every supermoon in 2029
75
+ $ supermoon 2020 2035 -B # how many supermoons there are each year from 2020 to 2035
76
+ $ supermoon 2020 2035 -C # also save them to supermoons.csv
77
+ ```
78
+
79
+ `python -m supermoon` works the same way.
80
+
81
+ ## Python usage
82
+
83
+ ```python
84
+ from datetime import datetime, timezone
85
+ import supermoon
86
+
87
+ # the next supermoon after a date (defaults to now; naive datetimes are treated as UTC)
88
+ result = supermoon.next_supermoon(datetime(2025, 1, 1, tzinfo=timezone.utc))
89
+ result['fullmoon']['date'] # datetime.datetime(2025, 10, 7, 3, 47, 36, ..., tzinfo=UTC)
90
+ result['fullmoon']['distance'] # km
91
+ result['definitions'] # {'Sky & Telescope': False, 'Time & Date': False,
92
+ # 'Espenak': True, 'Nolle': True, 'within 1 day of perigee': False}
93
+
94
+ supermoon.supermoons(2029) # list of every supermoon in a year
95
+ supermoon.next_supermoons(count=3) # the next 3 supermoons
96
+ supermoon.describe(result, perigee=True, distance=True, angulardiameter=True) # printable lines
97
+ supermoon.write_csv(supermoon.supermoons(2029), "supermoons.csv")
98
+ ```
99
+
100
+ Each result is a dictionary with these keys:
101
+
102
+ | key | contents |
103
+ |---|---|
104
+ | `definitions` | each definition's name, mapped to whether this full Moon meets it |
105
+ | `fullmoon` | `date` (UTC), `localdate` (local time zone), `distance` (km) |
106
+ | `perigee` | `date`, `localdate` and `distance` of the closest perigee |
107
+ | `relative distance` | `thisorbit` (Espenak) and `thisyear` (Nolle) ratios |
108
+ | `full perigee delta hours` / `full perigee delta seconds` | time between the full Moon and perigee |
109
+ | `angular diameter` / `angular diameter raw` | the Moon's apparent size, as a string / in degrees |
110
+
111
+ ## Definitions used
112
+
113
+ * Richard Nolle, an astrologer, coined the term in 1979 in an article in _Dell Horoscope_ magazine.
114
+ He has refined his definition twice:
115
+ - rule 1 (1979): a full or new Moon at a distance 90% or greater than the perigee in a given orbit
116
+ - rule 2 (2000): a full or new Moon at a distance 90% or greater than mean perigee
117
+ [source](https://www.astropro.com/features/articles/supermoon/)
118
+ - rule 3 (2011): a full or new Moon at a distance 90% or greater than the closest perigee for
119
+ the calendar year. This package calculates this rule.
120
+ [source](https://www.astropro.com/features/tables/cen21ce/suprmoon.html)
121
+ * Fred Espenak (retired NASA astrophysicist, best known for lunar and solar eclipse predictions):
122
+ a full Moon at a distance 90% or greater of perigee during the current lunation. EarthSky also
123
+ [uses this definition](https://earthsky.org/astronomy-essentials/why-experts-disagree-on-what-makes-a-supermoon#nolle).
124
+ [source](http://astropixels.com/ephemeris/moon/fullperigee2001.html)
125
+ * Sky and Telescope magazine: a full Moon within 223,000 miles (358,884 km)
126
+ [source](https://skyandtelescope.org/observing/what-is-a-supermoon/)
127
+ * TimeandDate.com (a Norwegian company offering website and data services on time and astronomy):
128
+ a full Moon within 360,000 kilometers (223,694 mi)
129
+ [source](https://www.timeanddate.com/astronomy/moon/super-full-moon.html)
130
+ * Additionally, some sources have labeled full Moons within 24 hours of perigee as supermoons.
131
+
132
+ Nolle was presumably inspired by the real increase in tidal effects at a perigee
133
+ [syzygy](https://en.wikipedia.org/wiki/Syzygy_%28astronomy%29), so his tables include both new and
134
+ full Moons. Most mentions of supermoons in the popular media focus on full Moons near perigee,
135
+ because a new Moon is hard to see. This package only considers full Moons.
136
+
137
+ This collection of conflicting definitions is further described in
138
+ [this article I wrote on the subject](https://www.wral.com/weather/blogpost/11487264/).
139
+
140
+ ## Supermoons, 2020 through 2035
141
+
142
+ Times are US Eastern.
143
+
144
+ ```
145
+ 4 supermoons during 2020:
146
+ Sun 02/09/2020 02:33 AM EST (07:33 UTC) according to Espenak
147
+ Mon 03/09/2020 01:47 PM EDT (17:47 UTC) according to all known definitions
148
+ Tue 04/07/2020 10:35 PM EDT (02:35 UTC) according to all known definitions
149
+ Thu 05/07/2020 06:45 AM EDT (10:45 UTC) according to Espenak, and, Nolle
150
+ 4 supermoons during 2021:
151
+ Sun 03/28/2021 02:48 PM EDT (18:48 UTC) according to Espenak
152
+ Mon 04/26/2021 11:31 PM EDT (03:31 UTC) according to all known definitions
153
+ Wed 05/26/2021 07:13 AM EDT (11:13 UTC) according to all known definitions
154
+ Thu 06/24/2021 02:39 PM EDT (18:39 UTC) according to Espenak, and, Nolle
155
+ 4 supermoons during 2022:
156
+ Mon 05/16/2022 12:14 AM EDT (04:14 UTC) according to Espenak, and, Nolle
157
+ Tue 06/14/2022 07:51 AM EDT (11:51 UTC) according to all known definitions
158
+ Wed 07/13/2022 02:37 PM EDT (18:37 UTC) according to all known definitions
159
+ Thu 08/11/2022 09:35 PM EDT (01:35 UTC) according to Espenak, and, Nolle
160
+ 4 supermoons during 2023:
161
+ Mon 07/03/2023 07:38 AM EDT (11:38 UTC) according to Espenak
162
+ Tue 08/01/2023 02:31 PM EDT (18:31 UTC) according to all known definitions
163
+ Wed 08/30/2023 09:35 PM EDT (01:35 UTC) according to all known definitions
164
+ Fri 09/29/2023 05:57 AM EDT (09:57 UTC) according to Espenak, and, Nolle
165
+ 4 supermoons during 2024:
166
+ Mon 08/19/2024 02:25 PM EDT (18:25 UTC) according to Espenak
167
+ Tue 09/17/2024 10:34 PM EDT (02:34 UTC) according to all known definitions
168
+ Thu 10/17/2024 07:26 AM EDT (11:26 UTC) according to all known definitions
169
+ Fri 11/15/2024 04:28 PM EST (21:28 UTC) according to Espenak
170
+ 3 supermoons during 2025:
171
+ Mon 10/06/2025 11:47 PM EDT (03:47 UTC) according to Espenak, and, Nolle
172
+ Wed 11/05/2025 08:19 AM EST (13:19 UTC) according to all known definitions
173
+ Thu 12/04/2025 06:14 PM EST (23:14 UTC) according to all known definitions
174
+ 3 supermoons during 2026:
175
+ Sat 01/03/2026 05:02 AM EST (10:02 UTC) according to Espenak
176
+ Tue 11/24/2026 09:53 AM EST (14:53 UTC) according to Espenak, and, Nolle
177
+ Wed 12/23/2026 08:28 PM EST (01:28 UTC) according to all known definitions
178
+ 3 supermoons during 2027:
179
+ Fri 01/22/2027 07:17 AM EST (12:17 UTC) according to all known definitions
180
+ Sat 02/20/2027 06:23 PM EST (23:23 UTC) according to Espenak
181
+ Mon 12/13/2027 11:08 AM EST (16:08 UTC) according to Espenak
182
+ 4 supermoons during 2028:
183
+ Tue 01/11/2028 11:03 PM EST (04:03 UTC) according to Espenak, and, Nolle
184
+ Thu 02/10/2028 10:03 AM EST (15:03 UTC) according to all known definitions
185
+ Fri 03/10/2028 08:06 PM EST (01:06 UTC) according to all known definitions
186
+ Sun 04/09/2028 06:26 AM EDT (10:26 UTC) according to Espenak
187
+ 5 supermoons during 2029:
188
+ Tue 01/30/2029 01:03 AM EST (06:03 UTC) according to Espenak
189
+ Wed 02/28/2029 12:10 PM EST (17:10 UTC) according to Time & Date, Espenak, and, Nolle
190
+ Thu 03/29/2029 10:26 PM EDT (02:26 UTC) according to all known definitions
191
+ Sat 04/28/2029 06:36 AM EDT (10:36 UTC) according to all known definitions
192
+ Sun 05/27/2029 02:37 PM EDT (18:37 UTC) according to Espenak
193
+ 5 supermoons during 2030:
194
+ Tue 03/19/2030 01:56 PM EDT (17:56 UTC) according to Espenak
195
+ Wed 04/17/2030 11:20 PM EDT (03:20 UTC) according to Time & Date, Espenak, and, Nolle
196
+ Fri 05/17/2030 07:19 AM EDT (11:19 UTC) according to all known definitions
197
+ Sat 06/15/2030 02:41 PM EDT (18:41 UTC) according to all known definitions
198
+ Sun 07/14/2030 10:12 PM EDT (02:12 UTC) according to Espenak
199
+ 5 supermoons during 2031:
200
+ Tue 05/06/2031 11:39 PM EDT (03:39 UTC) according to Espenak
201
+ Thu 06/05/2031 07:58 AM EDT (11:58 UTC) according to Time & Date, Espenak, and, Nolle
202
+ Fri 07/04/2031 03:01 PM EDT (19:01 UTC) according to all known definitions
203
+ Sat 08/02/2031 09:45 PM EDT (01:45 UTC) according to all known definitions
204
+ Mon 09/01/2031 05:20 AM EDT (09:20 UTC) according to Espenak
205
+ 5 supermoons during 2032:
206
+ Wed 06/23/2032 07:32 AM EDT (11:32 UTC) according to Espenak
207
+ Thu 07/22/2032 02:51 PM EDT (18:51 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
208
+ Fri 08/20/2032 09:46 PM EDT (01:46 UTC) according to all known definitions
209
+ Sun 09/19/2032 05:30 AM EDT (09:30 UTC) according to all known definitions
210
+ Mon 10/18/2032 02:58 PM EDT (18:58 UTC) according to Espenak
211
+ 5 supermoons during 2033:
212
+ Wed 08/10/2033 02:07 PM EDT (18:07 UTC) according to Espenak
213
+ Thu 09/08/2033 10:20 PM EDT (02:20 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
214
+ Sat 10/08/2033 06:58 AM EDT (10:58 UTC) according to all known definitions
215
+ Sun 11/06/2033 03:32 PM EST (20:32 UTC) according to all known definitions
216
+ Tue 12/06/2033 02:22 AM EST (07:22 UTC) according to Espenak
217
+ 4 supermoons during 2034:
218
+ Wed 09/27/2034 10:56 PM EDT (02:56 UTC) according to Espenak
219
+ Fri 10/27/2034 08:42 AM EDT (12:42 UTC) according to all known definitions
220
+ Sat 11/25/2034 05:32 PM EST (22:32 UTC) according to all known definitions
221
+ Mon 12/25/2034 03:54 AM EST (08:54 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
222
+ 3 supermoons during 2035:
223
+ Tue 01/23/2035 03:16 PM EST (20:16 UTC) according to Espenak
224
+ Thu 11/15/2035 08:48 AM EST (13:48 UTC) according to Espenak
225
+ Fri 12/14/2035 07:33 PM EST (00:33 UTC) according to all known definitions
226
+ ```
227
+
228
+ ## Development
229
+
230
+ The project uses [uv](https://docs.astral.sh/uv/):
231
+
232
+ ```
233
+ uv sync --extra test
234
+ uv run pytest tests/supermoon_tests.py
235
+ uv run supermoon 2025
236
+ ```
237
+
238
+ `tests/basic.py` checks the perigee and apogee calculations against Fred Espenak's published
239
+ tables. It downloads those tables from astropixels.com, so it needs network access.
240
+
241
+ ## Releasing to PyPI
242
+
243
+ ```
244
+ uv build
245
+ uv publish
246
+ ```
247
+
248
+ Before each release, bump `__version__` in `supermoon/__init__.py`.
@@ -0,0 +1,220 @@
1
+ # supermoon
2
+
3
+ Finds supermoons, and shows which of the popular (and conflicting) definitions each one meets.
4
+ For background, see the [Wikipedia article on supermoons](https://en.wikipedia.org/wiki/Supermoon).
5
+
6
+ A supermoon, or perigean full moon, is a full Moon that happens near the point in the Moon's orbit
7
+ closest to Earth, so the Moon looks slightly larger. The term has no astronomical meaning. An
8
+ astrologer coined it, not an astronomer. It describes a timing coincidence in the Moon's synodic
9
+ month, and every popular definition of it is essentially arbitrary.
10
+
11
+ ## Installation
12
+
13
+ ```
14
+ pip install supermoon
15
+ ```
16
+
17
+ The first time you run it, supermoon downloads the JPL DE421 ephemeris (about 17 MB) to
18
+ `~/.supermoon`. To keep it somewhere else, or to reuse a copy you already have, set the
19
+ `SUPERMOON_DATA` environment variable to that directory. DE421 covers the years 1900 through 2050.
20
+
21
+ ## Command line usage
22
+
23
+ ```
24
+ usage: supermoon [-h] [--cnt CNT] [-B] [-P] [-D] [-A] [-C] [year] [endyear]
25
+
26
+ positional arguments:
27
+ year find supermoons for this year (optional, defaults to current date forward)
28
+ endyear stop finding supermoons (optional)
29
+
30
+ options:
31
+ -h, --help show this help message and exit
32
+ --cnt CNT moons to show
33
+ -B, --brief brief output
34
+ -P, --perigee include perigee time
35
+ -D, --distance include distances
36
+ -A, --angulardiameter
37
+ include angular diameter
38
+ -C, --csv also write results to supermoons.csv
39
+ ```
40
+
41
+ Examples:
42
+
43
+ ```
44
+ $ supermoon # the next supermoon
45
+ $ supermoon --cnt 3 -P -D -A # the next 3, with perigee, distances and angular diameter
46
+ $ supermoon 2029 # every supermoon in 2029
47
+ $ supermoon 2020 2035 -B # how many supermoons there are each year from 2020 to 2035
48
+ $ supermoon 2020 2035 -C # also save them to supermoons.csv
49
+ ```
50
+
51
+ `python -m supermoon` works the same way.
52
+
53
+ ## Python usage
54
+
55
+ ```python
56
+ from datetime import datetime, timezone
57
+ import supermoon
58
+
59
+ # the next supermoon after a date (defaults to now; naive datetimes are treated as UTC)
60
+ result = supermoon.next_supermoon(datetime(2025, 1, 1, tzinfo=timezone.utc))
61
+ result['fullmoon']['date'] # datetime.datetime(2025, 10, 7, 3, 47, 36, ..., tzinfo=UTC)
62
+ result['fullmoon']['distance'] # km
63
+ result['definitions'] # {'Sky & Telescope': False, 'Time & Date': False,
64
+ # 'Espenak': True, 'Nolle': True, 'within 1 day of perigee': False}
65
+
66
+ supermoon.supermoons(2029) # list of every supermoon in a year
67
+ supermoon.next_supermoons(count=3) # the next 3 supermoons
68
+ supermoon.describe(result, perigee=True, distance=True, angulardiameter=True) # printable lines
69
+ supermoon.write_csv(supermoon.supermoons(2029), "supermoons.csv")
70
+ ```
71
+
72
+ Each result is a dictionary with these keys:
73
+
74
+ | key | contents |
75
+ |---|---|
76
+ | `definitions` | each definition's name, mapped to whether this full Moon meets it |
77
+ | `fullmoon` | `date` (UTC), `localdate` (local time zone), `distance` (km) |
78
+ | `perigee` | `date`, `localdate` and `distance` of the closest perigee |
79
+ | `relative distance` | `thisorbit` (Espenak) and `thisyear` (Nolle) ratios |
80
+ | `full perigee delta hours` / `full perigee delta seconds` | time between the full Moon and perigee |
81
+ | `angular diameter` / `angular diameter raw` | the Moon's apparent size, as a string / in degrees |
82
+
83
+ ## Definitions used
84
+
85
+ * Richard Nolle, an astrologer, coined the term in 1979 in an article in _Dell Horoscope_ magazine.
86
+ He has refined his definition twice:
87
+ - rule 1 (1979): a full or new Moon at a distance 90% or greater than the perigee in a given orbit
88
+ - rule 2 (2000): a full or new Moon at a distance 90% or greater than mean perigee
89
+ [source](https://www.astropro.com/features/articles/supermoon/)
90
+ - rule 3 (2011): a full or new Moon at a distance 90% or greater than the closest perigee for
91
+ the calendar year. This package calculates this rule.
92
+ [source](https://www.astropro.com/features/tables/cen21ce/suprmoon.html)
93
+ * Fred Espenak (retired NASA astrophysicist, best known for lunar and solar eclipse predictions):
94
+ a full Moon at a distance 90% or greater of perigee during the current lunation. EarthSky also
95
+ [uses this definition](https://earthsky.org/astronomy-essentials/why-experts-disagree-on-what-makes-a-supermoon#nolle).
96
+ [source](http://astropixels.com/ephemeris/moon/fullperigee2001.html)
97
+ * Sky and Telescope magazine: a full Moon within 223,000 miles (358,884 km)
98
+ [source](https://skyandtelescope.org/observing/what-is-a-supermoon/)
99
+ * TimeandDate.com (a Norwegian company offering website and data services on time and astronomy):
100
+ a full Moon within 360,000 kilometers (223,694 mi)
101
+ [source](https://www.timeanddate.com/astronomy/moon/super-full-moon.html)
102
+ * Additionally, some sources have labeled full Moons within 24 hours of perigee as supermoons.
103
+
104
+ Nolle was presumably inspired by the real increase in tidal effects at a perigee
105
+ [syzygy](https://en.wikipedia.org/wiki/Syzygy_%28astronomy%29), so his tables include both new and
106
+ full Moons. Most mentions of supermoons in the popular media focus on full Moons near perigee,
107
+ because a new Moon is hard to see. This package only considers full Moons.
108
+
109
+ This collection of conflicting definitions is further described in
110
+ [this article I wrote on the subject](https://www.wral.com/weather/blogpost/11487264/).
111
+
112
+ ## Supermoons, 2020 through 2035
113
+
114
+ Times are US Eastern.
115
+
116
+ ```
117
+ 4 supermoons during 2020:
118
+ Sun 02/09/2020 02:33 AM EST (07:33 UTC) according to Espenak
119
+ Mon 03/09/2020 01:47 PM EDT (17:47 UTC) according to all known definitions
120
+ Tue 04/07/2020 10:35 PM EDT (02:35 UTC) according to all known definitions
121
+ Thu 05/07/2020 06:45 AM EDT (10:45 UTC) according to Espenak, and, Nolle
122
+ 4 supermoons during 2021:
123
+ Sun 03/28/2021 02:48 PM EDT (18:48 UTC) according to Espenak
124
+ Mon 04/26/2021 11:31 PM EDT (03:31 UTC) according to all known definitions
125
+ Wed 05/26/2021 07:13 AM EDT (11:13 UTC) according to all known definitions
126
+ Thu 06/24/2021 02:39 PM EDT (18:39 UTC) according to Espenak, and, Nolle
127
+ 4 supermoons during 2022:
128
+ Mon 05/16/2022 12:14 AM EDT (04:14 UTC) according to Espenak, and, Nolle
129
+ Tue 06/14/2022 07:51 AM EDT (11:51 UTC) according to all known definitions
130
+ Wed 07/13/2022 02:37 PM EDT (18:37 UTC) according to all known definitions
131
+ Thu 08/11/2022 09:35 PM EDT (01:35 UTC) according to Espenak, and, Nolle
132
+ 4 supermoons during 2023:
133
+ Mon 07/03/2023 07:38 AM EDT (11:38 UTC) according to Espenak
134
+ Tue 08/01/2023 02:31 PM EDT (18:31 UTC) according to all known definitions
135
+ Wed 08/30/2023 09:35 PM EDT (01:35 UTC) according to all known definitions
136
+ Fri 09/29/2023 05:57 AM EDT (09:57 UTC) according to Espenak, and, Nolle
137
+ 4 supermoons during 2024:
138
+ Mon 08/19/2024 02:25 PM EDT (18:25 UTC) according to Espenak
139
+ Tue 09/17/2024 10:34 PM EDT (02:34 UTC) according to all known definitions
140
+ Thu 10/17/2024 07:26 AM EDT (11:26 UTC) according to all known definitions
141
+ Fri 11/15/2024 04:28 PM EST (21:28 UTC) according to Espenak
142
+ 3 supermoons during 2025:
143
+ Mon 10/06/2025 11:47 PM EDT (03:47 UTC) according to Espenak, and, Nolle
144
+ Wed 11/05/2025 08:19 AM EST (13:19 UTC) according to all known definitions
145
+ Thu 12/04/2025 06:14 PM EST (23:14 UTC) according to all known definitions
146
+ 3 supermoons during 2026:
147
+ Sat 01/03/2026 05:02 AM EST (10:02 UTC) according to Espenak
148
+ Tue 11/24/2026 09:53 AM EST (14:53 UTC) according to Espenak, and, Nolle
149
+ Wed 12/23/2026 08:28 PM EST (01:28 UTC) according to all known definitions
150
+ 3 supermoons during 2027:
151
+ Fri 01/22/2027 07:17 AM EST (12:17 UTC) according to all known definitions
152
+ Sat 02/20/2027 06:23 PM EST (23:23 UTC) according to Espenak
153
+ Mon 12/13/2027 11:08 AM EST (16:08 UTC) according to Espenak
154
+ 4 supermoons during 2028:
155
+ Tue 01/11/2028 11:03 PM EST (04:03 UTC) according to Espenak, and, Nolle
156
+ Thu 02/10/2028 10:03 AM EST (15:03 UTC) according to all known definitions
157
+ Fri 03/10/2028 08:06 PM EST (01:06 UTC) according to all known definitions
158
+ Sun 04/09/2028 06:26 AM EDT (10:26 UTC) according to Espenak
159
+ 5 supermoons during 2029:
160
+ Tue 01/30/2029 01:03 AM EST (06:03 UTC) according to Espenak
161
+ Wed 02/28/2029 12:10 PM EST (17:10 UTC) according to Time & Date, Espenak, and, Nolle
162
+ Thu 03/29/2029 10:26 PM EDT (02:26 UTC) according to all known definitions
163
+ Sat 04/28/2029 06:36 AM EDT (10:36 UTC) according to all known definitions
164
+ Sun 05/27/2029 02:37 PM EDT (18:37 UTC) according to Espenak
165
+ 5 supermoons during 2030:
166
+ Tue 03/19/2030 01:56 PM EDT (17:56 UTC) according to Espenak
167
+ Wed 04/17/2030 11:20 PM EDT (03:20 UTC) according to Time & Date, Espenak, and, Nolle
168
+ Fri 05/17/2030 07:19 AM EDT (11:19 UTC) according to all known definitions
169
+ Sat 06/15/2030 02:41 PM EDT (18:41 UTC) according to all known definitions
170
+ Sun 07/14/2030 10:12 PM EDT (02:12 UTC) according to Espenak
171
+ 5 supermoons during 2031:
172
+ Tue 05/06/2031 11:39 PM EDT (03:39 UTC) according to Espenak
173
+ Thu 06/05/2031 07:58 AM EDT (11:58 UTC) according to Time & Date, Espenak, and, Nolle
174
+ Fri 07/04/2031 03:01 PM EDT (19:01 UTC) according to all known definitions
175
+ Sat 08/02/2031 09:45 PM EDT (01:45 UTC) according to all known definitions
176
+ Mon 09/01/2031 05:20 AM EDT (09:20 UTC) according to Espenak
177
+ 5 supermoons during 2032:
178
+ Wed 06/23/2032 07:32 AM EDT (11:32 UTC) according to Espenak
179
+ Thu 07/22/2032 02:51 PM EDT (18:51 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
180
+ Fri 08/20/2032 09:46 PM EDT (01:46 UTC) according to all known definitions
181
+ Sun 09/19/2032 05:30 AM EDT (09:30 UTC) according to all known definitions
182
+ Mon 10/18/2032 02:58 PM EDT (18:58 UTC) according to Espenak
183
+ 5 supermoons during 2033:
184
+ Wed 08/10/2033 02:07 PM EDT (18:07 UTC) according to Espenak
185
+ Thu 09/08/2033 10:20 PM EDT (02:20 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
186
+ Sat 10/08/2033 06:58 AM EDT (10:58 UTC) according to all known definitions
187
+ Sun 11/06/2033 03:32 PM EST (20:32 UTC) according to all known definitions
188
+ Tue 12/06/2033 02:22 AM EST (07:22 UTC) according to Espenak
189
+ 4 supermoons during 2034:
190
+ Wed 09/27/2034 10:56 PM EDT (02:56 UTC) according to Espenak
191
+ Fri 10/27/2034 08:42 AM EDT (12:42 UTC) according to all known definitions
192
+ Sat 11/25/2034 05:32 PM EST (22:32 UTC) according to all known definitions
193
+ Mon 12/25/2034 03:54 AM EST (08:54 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
194
+ 3 supermoons during 2035:
195
+ Tue 01/23/2035 03:16 PM EST (20:16 UTC) according to Espenak
196
+ Thu 11/15/2035 08:48 AM EST (13:48 UTC) according to Espenak
197
+ Fri 12/14/2035 07:33 PM EST (00:33 UTC) according to all known definitions
198
+ ```
199
+
200
+ ## Development
201
+
202
+ The project uses [uv](https://docs.astral.sh/uv/):
203
+
204
+ ```
205
+ uv sync --extra test
206
+ uv run pytest tests/supermoon_tests.py
207
+ uv run supermoon 2025
208
+ ```
209
+
210
+ `tests/basic.py` checks the perigee and apogee calculations against Fred Espenak's published
211
+ tables. It downloads those tables from astropixels.com, so it needs network access.
212
+
213
+ ## Releasing to PyPI
214
+
215
+ ```
216
+ uv build
217
+ uv publish
218
+ ```
219
+
220
+ Before each release, bump `__version__` in `supermoon/__init__.py`.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "supermoon"
7
+ dynamic = ["version"]
8
+ description = "Find supermoons according to each of the popular (and conflicting) definitions"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{name = "Tony Rice", email = "tony@rtphokie.org"}]
13
+ requires-python = ">=3.8"
14
+ dependencies = [
15
+ "skyfield>=1.31",
16
+ "numpy",
17
+ "tzlocal",
18
+ ]
19
+ keywords = ["supermoon", "moon", "perigee", "full moon", "astronomy"]
20
+ classifiers = [
21
+ "Programming Language :: Python :: 3",
22
+ "Operating System :: OS Independent",
23
+ "Environment :: Console",
24
+ "Intended Audience :: Science/Research",
25
+ "Topic :: Scientific/Engineering :: Astronomy",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ test = ["pytest", "pytz", "requests", "beautifulsoup4", "lxml"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/rtphokie/supermoon"
33
+ Issues = "https://github.com/rtphokie/supermoon/issues"
34
+
35
+ [project.scripts]
36
+ supermoon = "supermoon.cli:main"
37
+
38
+ [tool.setuptools]
39
+ packages = ["supermoon"]
40
+
41
+ [tool.setuptools.dynamic]
42
+ version = {attr = "supermoon.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """Top-level package for supermoon"""
2
+
3
+ from .core import describe, next_supermoon, next_supermoons, supermoons, write_csv
4
+
5
+ name = "supermoon"
6
+ __author__ = """Tony Rice"""
7
+ __email__ = "tony@rtphokie.org"
8
+ __version__ = "0.1.0"
9
+ __all__ = ["describe", "next_supermoon", "next_supermoons", "supermoons", "write_csv"]
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,61 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from .ephemeris import planets, timescale
4
+
5
+
6
+ def next_apogee(dt=None, days=30):
7
+ return next_apsis(dt=dt, days=days, extrema="max")
8
+
9
+
10
+ def next_perigee(dt=None, days=30):
11
+ return next_apsis(dt=dt, days=days, extrema="min")
12
+
13
+
14
+ def next_apsis(dt=None, days=30, extrema="min"):
15
+ if dt is None:
16
+ dt = datetime.now(timezone.utc)
17
+ earth = planets()["earth"]
18
+ moon = planets()["moon"]
19
+ ts = timescale()
20
+
21
+ # synodic month 29d 12h 44m 03s
22
+ # day granulartiy
23
+ t = ts.utc(dt.year, dt.month, range(dt.day, dt.day + days))
24
+ dt, _ = _find_apsis(earth, moon, t, extrema)
25
+ # hour granulartiy
26
+ t = ts.utc(dt.year, dt.month, dt.day, range(dt.hour - 24, dt.hour + 24))
27
+ dt, _ = _find_apsis(earth, moon, t, extrema)
28
+ # minute granulartiy
29
+ t = ts.utc(
30
+ dt.year, dt.month, dt.day, dt.hour, range(dt.minute - 60, dt.minute + 60)
31
+ )
32
+ dt, _ = _find_apsis(earth, moon, t, extrema)
33
+ # second granulartiy
34
+ t = ts.utc(
35
+ dt.year,
36
+ dt.month,
37
+ dt.day,
38
+ dt.hour,
39
+ dt.minute,
40
+ range(dt.second - 60, dt.second + 60),
41
+ )
42
+ dt, d = _find_apsis(earth, moon, t, extrema)
43
+
44
+ return dt, round(d, 0)
45
+
46
+
47
+ def _find_apsis(earth, moon, t, extrema):
48
+ dt = None
49
+ value = None
50
+ position = (moon - earth).at(t)
51
+ d = position.distance().km
52
+ if extrema == "min":
53
+ dt = t[d.argmin()].utc_datetime()
54
+ value = d.min()
55
+ elif extrema == "max":
56
+ dt = t[d.argmax()].utc_datetime()
57
+ value = d.max()
58
+ else:
59
+ raise ValueError("please use extremas of min or max")
60
+
61
+ return dt, value
@@ -0,0 +1,101 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from .core import MAX_YEAR, MIN_YEAR, describe, next_supermoons, supermoons, write_csv
5
+
6
+ helpmsg = """
7
+ Supermoon definitions used:
8
+ * Richard Nolle (coined the term in 1979): A full or new Moon occurring at a
9
+ distance 90% or greater than the closest perigee for the calendar year.
10
+ https://www.astropro.com/features/tables/cen21ce/suprmoon.html
11
+ * Fred Espenak (retired NASA astrophysicist, best known for lunar and solar
12
+ eclipse predictions)- A full Moon occurring at a distance 90% or greater
13
+ of perigee during the current lunation.
14
+ http://astropixels.com/ephemeris/moon/fullperigee2001.html
15
+ * Sky and Telescope magazine - A full Moon occurring within 223,000 miles (358,884 km)
16
+ * TimeandDate.com (Norwegian company offering website and data services on
17
+ time and astronomy)- A full Moon within 360,000 kilometres (223,694 mi)
18
+ https://www.timeanddate.com/astronomy/moon/super-full-moon.html
19
+ """
20
+
21
+
22
+ def main(argv=None):
23
+ parser = argparse.ArgumentParser(
24
+ prog="supermoon", formatter_class=argparse.RawTextHelpFormatter, epilog=helpmsg
25
+ )
26
+ parser.add_argument(
27
+ "year",
28
+ type=int,
29
+ nargs="?",
30
+ default=None,
31
+ help="find supermoons for this year (optional, defaults to current date forward)",
32
+ )
33
+ parser.add_argument(
34
+ "endyear",
35
+ type=int,
36
+ nargs="?",
37
+ default=None,
38
+ help="stop finding supermoons (optional)",
39
+ )
40
+ parser.add_argument("--cnt", type=int, default=1, help="moons to show")
41
+ parser.add_argument("-B", "--brief", action="store_true", help="brief output")
42
+ parser.add_argument(
43
+ "-P", "--perigee", action="store_true", help="include perigee time"
44
+ )
45
+ parser.add_argument(
46
+ "-D", "--distance", action="store_true", help="include distances"
47
+ )
48
+ parser.add_argument(
49
+ "-A", "--angulardiameter", action="store_true", help="include angular diameter"
50
+ )
51
+ parser.add_argument(
52
+ "-C", "--csv", action="store_true", help="also write results to supermoons.csv"
53
+ )
54
+ args = parser.parse_args(argv)
55
+ options = {
56
+ "perigee": args.perigee,
57
+ "distance": args.distance,
58
+ "angulardiameter": args.angulardiameter,
59
+ }
60
+
61
+ if args.year is None:
62
+ if args.cnt < 1:
63
+ parser.error(f"expecting count of 1 or more, got {args.cnt}")
64
+ if args.cnt > 1:
65
+ print(f"The next {args.cnt} supermoons will be:")
66
+ else:
67
+ print("The next supermoon will be:")
68
+ results = next_supermoons(count=args.cnt)
69
+ for result in results:
70
+ print("\n".join(describe(result, **options)))
71
+ if args.csv:
72
+ _write_csv(results)
73
+ return 0
74
+
75
+ if args.endyear is None:
76
+ args.endyear = args.year
77
+ for year in (args.year, args.endyear):
78
+ if not MIN_YEAR <= year <= MAX_YEAR:
79
+ parser.error(
80
+ f"Please provide a year between {MIN_YEAR} and {MAX_YEAR}, got {year} (per JPL DE421)"
81
+ )
82
+ all_results = []
83
+ for year in range(args.year, args.endyear + 1):
84
+ results = supermoons(year)
85
+ all_results.extend(results)
86
+ print(f"{len(results)} supermoons during {year}:")
87
+ if not args.brief:
88
+ for result in results:
89
+ print("\n".join(describe(result, **options)))
90
+ if args.csv:
91
+ _write_csv(all_results)
92
+ return 0
93
+
94
+
95
+ def _write_csv(results, filename="supermoons.csv"):
96
+ write_csv(results, filename)
97
+ print(f"Wrote {len(results)} rows to {filename}")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ sys.exit(main())
@@ -0,0 +1,203 @@
1
+ """
2
+ In general, a supermoon is a full moon which occurs near the closest point in its orbit making it appear
3
+ bigger and brighter. However, there is no official nor even consistent definition for the concept of
4
+ supermoon leading to disagreements on which full moons should receive the label and which should not.
5
+
6
+ Known definitions are calculated here
7
+ * Richard Nolle
8
+ rule 1 (1979) - A full or new Moon occurring at a distance 90% or greater than the perigee in a given orbit
9
+ Dell Horoscope, 1979
10
+ rule 2 (2000) - A full or new Moon occurring at a distance 90% or greater than mean perigee (
11
+ https://www.astropro.com/features/articles/supermoon/
12
+ rule 3 (2011) - A full or new Moon occurring at a distance 90% or greater than the closest perigee for the calendar year. This definition is also [preferred by EarthSky.com](https://earthsky.org/astronomy-essentials/why-experts-disagree-on-what-makes-a-supermoon#nolle)
13
+ https://www.astropro.com/features/tables/cen21ce/suprmoon.html
14
+ * Fred Espenak - A full or new Moon occurring at a distance 90% or greater of perigee during the current lunation, also used by Earth Sky
15
+ http://astropixels.com/ephemeris/moon/fullperigee2001.html
16
+ * Sky and Telescope magazine - A full Moon within 223,000 miles (358,884 km) of Earth
17
+ * TimeandDate.com - A full Moon within 360,000 kilometres (223,694 mi) of Earth
18
+ https://www.timeanddate.com/astronomy/moon/super-full-moon.html, https://www.timeanddate.com/moon/phases/
19
+ """
20
+
21
+ import csv
22
+ from datetime import datetime, timedelta, timezone
23
+ from functools import lru_cache
24
+
25
+ from tzlocal import get_localzone
26
+
27
+ from .apsis import next_apogee, next_perigee
28
+ from .lunarphases import next_full_moon
29
+
30
+ # range covered by the JPL DE421 ephemeris
31
+ MIN_YEAR = 1900
32
+ MAX_YEAR = 2050
33
+ CSV_FIELDS = (
34
+ "fullmoon_local_date",
35
+ "perigee_local_date",
36
+ "perigee_distance_km",
37
+ "perigee_distance_mi",
38
+ "angular_diameter",
39
+ )
40
+
41
+
42
+ def next_supermoon(dt=None):
43
+ """
44
+ calculates when the next supermoon will occur based on known criteria
45
+ :param dt: timezone aware datetime, defaults to current time (UTC)
46
+ :return: dictionary
47
+ """
48
+ if dt is None:
49
+ dt = datetime.now(timezone.utc)
50
+ elif dt.tzinfo is None:
51
+ dt = dt.replace(tzinfo=timezone.utc)
52
+ result = _full_moon(dt)
53
+ while not any(result["definitions"].values()):
54
+ result = _full_moon(result["fullmoon"]["date"] + timedelta(days=1))
55
+ return result
56
+
57
+
58
+ @lru_cache(maxsize=512)
59
+ def _full_moon(dt):
60
+ """
61
+ evaluates the first full moon after dt against each supermoon definition
62
+ """
63
+ # find datetime and distance of next full moon from the date given
64
+ DATEfm, Dfm, diameter = next_full_moon(dt)
65
+ jan1 = datetime(year=DATEfm.year, month=1, day=1, tzinfo=timezone.utc)
66
+
67
+ # find distance of next perigee and apogee (for Espenak definition)
68
+ DATEp, Dp = next_perigee(DATEfm - timedelta(days=14))
69
+ _, Da = next_apogee(DATEp)
70
+
71
+ RelativeDistance_thisorbit = (Da - Dfm) / (Da - Dp)
72
+
73
+ # find closest perigee and furthest apogee of the year for (Nolle definition)
74
+ _, MinDp = next_perigee(jan1, days=366)
75
+ _, MaxDa = next_apogee(jan1, days=366)
76
+ RelativeDistance_thisyear = (MaxDa - Dfm) / (MaxDa - MinDp)
77
+
78
+ # time seperation between perigee and full moon (for within 24 hours definition)
79
+ perigeedelta = abs((DATEp - DATEfm).total_seconds())
80
+
81
+ return {
82
+ "definitions": {
83
+ "Sky & Telescope": bool(Dfm <= 358884),
84
+ "Time & Date": bool(Dfm <= 360000),
85
+ "Espenak": bool(RelativeDistance_thisorbit >= 0.9),
86
+ "Nolle": bool(RelativeDistance_thisyear >= 0.9),
87
+ "within 1 day of perigee": perigeedelta <= 86400.0,
88
+ },
89
+ "relative distance": {
90
+ "thisorbit": float(RelativeDistance_thisorbit),
91
+ "thisyear": float(RelativeDistance_thisyear),
92
+ },
93
+ "fullmoon": {
94
+ "date": DATEfm,
95
+ "localdate": DATEfm.astimezone(get_localzone()),
96
+ "distance": float(Dfm),
97
+ },
98
+ "perigee": {
99
+ "date": DATEp,
100
+ "localdate": DATEp.astimezone(get_localzone()),
101
+ "distance": float(Dp),
102
+ },
103
+ "full perigee delta seconds": perigeedelta,
104
+ "full perigee delta hours": perigeedelta / 3600,
105
+ "angular diameter": str(diameter),
106
+ "angular diameter raw": diameter.degrees,
107
+ }
108
+
109
+
110
+ def next_supermoons(count=1, dt=None):
111
+ """
112
+ the next count supermoons on or after dt
113
+ :param count: number of supermoons to return
114
+ :param dt: timezone aware datetime, defaults to current time (UTC)
115
+ :return: list of dictionaries, as returned by next_supermoon
116
+ """
117
+ results = []
118
+ for i in range(count):
119
+ result = next_supermoon(dt=dt)
120
+ results.append(result)
121
+ dt = result["fullmoon"]["date"] + timedelta(days=1)
122
+ return results
123
+
124
+
125
+ def supermoons(year):
126
+ """
127
+ all supermoons during a calendar year (UTC)
128
+ :param year: year between 1900 and 2050
129
+ :return: list of dictionaries, as returned by next_supermoon
130
+ """
131
+ if not MIN_YEAR <= year <= MAX_YEAR:
132
+ raise ValueError(
133
+ f"year must be between {MIN_YEAR} and {MAX_YEAR} (per JPL DE421), got {year}"
134
+ )
135
+ results = []
136
+ dt = datetime(year=year, month=1, day=1, tzinfo=timezone.utc)
137
+ while True:
138
+ result = next_supermoon(dt=dt)
139
+ if result["fullmoon"]["date"].year != year:
140
+ break
141
+ results.append(result)
142
+ dt = result["fullmoon"]["date"] + timedelta(days=1)
143
+ return results
144
+
145
+
146
+ def describe(result, perigee=False, distance=False, angulardiameter=False):
147
+ """
148
+ human readable description of a supermoon
149
+ :param result: dictionary, as returned by next_supermoon
150
+ :return: list of lines
151
+ """
152
+ lines = []
153
+ msgstr = f"{result['fullmoon']['localdate'].strftime('%a %m/%d/%Y %I:%M %p %Z')} ({result['fullmoon']['date'].strftime('%H:%M %Z')})"
154
+ if distance:
155
+ msgstr += f" {result['fullmoon']['distance']:,} km ({result['fullmoon']['distance'] * 0.621371:,.1f} mi)"
156
+ thelist = []
157
+ for definition, meets in result["definitions"].items():
158
+ if meets:
159
+ thelist.append(definition)
160
+ if len(thelist) == len(result["definitions"].values()):
161
+ thelist = ["all known definitions"]
162
+ elif len(thelist) > 1:
163
+ thelist.insert(-1, "and")
164
+ lines.append(f" {msgstr} according to {', '.join(thelist)}")
165
+ if angulardiameter:
166
+ lines.append(f" angular diameter: {result['angular diameter']}")
167
+ if perigee:
168
+ distmsgstr = f" perigee: {result['perigee']['localdate'].strftime('%m/%d/%Y %H:%M %Z')} ({result['full perigee delta hours']:.2f} hours from full moon)"
169
+ if distance:
170
+ distmsgstr += f" {result['perigee']['distance']:,} km ({result['perigee']['distance'] * 0.621371:,.1f} mi)"
171
+ lines.append(distmsgstr)
172
+ return lines
173
+
174
+
175
+ def csv_row(result):
176
+ """
177
+ flattened summary of a supermoon, suitable for CSV output
178
+ :param result: dictionary, as returned by next_supermoon
179
+ :return: dictionary
180
+ """
181
+ return {
182
+ "fullmoon_local_date": result["fullmoon"]["localdate"].strftime(
183
+ "%Y-%m-%d %H:%M %Z"
184
+ ),
185
+ "perigee_local_date": result["perigee"]["localdate"].strftime(
186
+ "%Y-%m-%d %H:%M %Z"
187
+ ),
188
+ "perigee_distance_km": int(result["perigee"]["distance"]),
189
+ "perigee_distance_mi": round(result["perigee"]["distance"] * 0.621371),
190
+ "angular_diameter": result["angular diameter raw"],
191
+ }
192
+
193
+
194
+ def write_csv(results, filename="supermoons.csv"):
195
+ """
196
+ write supermoons to a CSV file
197
+ :param results: list of dictionaries, as returned by next_supermoon
198
+ :param filename: file to write
199
+ """
200
+ with open(filename, "w", newline="", encoding="utf-8") as f:
201
+ writer = csv.DictWriter(f, fieldnames=CSV_FIELDS)
202
+ writer.writeheader()
203
+ writer.writerows(csv_row(result) for result in results)
@@ -0,0 +1,31 @@
1
+ """
2
+ Shared Skyfield ephemeris and timescale, loaded lazily on first use.
3
+
4
+ The JPL DE421 ephemeris (~17 MB) is downloaded on first use to ~/.supermoon,
5
+ or to the directory named by the SUPERMOON_DATA environment variable.
6
+ """
7
+
8
+ import os
9
+ from functools import lru_cache
10
+
11
+ from skyfield.api import Loader
12
+
13
+ DATA_DIR = os.environ.get(
14
+ "SUPERMOON_DATA", os.path.join(os.path.expanduser("~"), ".supermoon")
15
+ )
16
+ EPHEMERIS = "de421.bsp" # covers 1900-2050
17
+
18
+
19
+ @lru_cache(maxsize=None)
20
+ def loader():
21
+ return Loader(DATA_DIR, verbose=False)
22
+
23
+
24
+ @lru_cache(maxsize=None)
25
+ def planets():
26
+ return loader()(EPHEMERIS)
27
+
28
+
29
+ @lru_cache(maxsize=None)
30
+ def timescale():
31
+ return loader().timescale()
@@ -0,0 +1,56 @@
1
+ from datetime import timedelta
2
+
3
+ import numpy as np
4
+ from skyfield import almanac
5
+ from skyfield.api import Angle
6
+
7
+ from .ephemeris import planets, timescale
8
+
9
+
10
+ def phases(dt, days=30, phases=range(5)):
11
+ """
12
+
13
+ :param dt: datetime to begin search
14
+ :param days: days to search, defaults to 30 to encompass a full lunation
15
+ :param phases: 0=new, 1=first quarter, 2=full, 3=last quarter
16
+ :return: dictionary (key is the datetime of the event), of dictionaries where
17
+ d is distance in km, phase_name is the title of the phase, phase_code is the integer representing the phase
18
+ """
19
+ e = planets()
20
+ earth, moon = e["earth"], e["moon"]
21
+ ts = timescale()
22
+ t0 = ts.utc(dt)
23
+ t1 = ts.utc(t0.utc_datetime() + timedelta(days=days))
24
+ t, y = almanac.find_discrete(t0, t1, almanac.moon_phases(e))
25
+ positions = (moon - earth).at(t)
26
+
27
+ results = []
28
+ for dd, phase_code, pos in zip(t, y, positions):
29
+ if phase_code in phases:
30
+ results.append(
31
+ {
32
+ "dt": dd.utc_datetime(),
33
+ "d": round(pos.distance().km, 1),
34
+ "dd": dd,
35
+ "phase_code": phase_code,
36
+ "phase_name": almanac.MOON_PHASES[phase_code],
37
+ }
38
+ )
39
+ return results
40
+
41
+
42
+ def next_full_moon(dt):
43
+ """
44
+
45
+ :param dt: datetime to begin search
46
+ :return:
47
+ """
48
+ r_moon = 1737.1 # in km
49
+
50
+ result = phases(dt, days=30, phases=[2])[0]
51
+ earth, moon = planets()["earth"], planets()["moon"]
52
+ moon_observation = earth.at(result["dd"]).observe(moon)
53
+ _, _, distance = moon_observation.apparent().radec()
54
+ result["diameter"] = Angle(radians=np.arcsin(r_moon / distance.km) * 2.0)
55
+
56
+ return result["dt"], result["d"], result["diameter"]
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: supermoon
3
+ Version: 0.1.0
4
+ Summary: Find supermoons according to each of the popular (and conflicting) definitions
5
+ Author-email: Tony Rice <tony@rtphokie.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rtphokie/supermoon
8
+ Project-URL: Issues, https://github.com/rtphokie/supermoon/issues
9
+ Keywords: supermoon,moon,perigee,full moon,astronomy
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: skyfield>=1.31
19
+ Requires-Dist: numpy
20
+ Requires-Dist: tzlocal
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest; extra == "test"
23
+ Requires-Dist: pytz; extra == "test"
24
+ Requires-Dist: requests; extra == "test"
25
+ Requires-Dist: beautifulsoup4; extra == "test"
26
+ Requires-Dist: lxml; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # supermoon
30
+
31
+ Finds supermoons, and shows which of the popular (and conflicting) definitions each one meets.
32
+ For background, see the [Wikipedia article on supermoons](https://en.wikipedia.org/wiki/Supermoon).
33
+
34
+ A supermoon, or perigean full moon, is a full Moon that happens near the point in the Moon's orbit
35
+ closest to Earth, so the Moon looks slightly larger. The term has no astronomical meaning. An
36
+ astrologer coined it, not an astronomer. It describes a timing coincidence in the Moon's synodic
37
+ month, and every popular definition of it is essentially arbitrary.
38
+
39
+ ## Installation
40
+
41
+ ```
42
+ pip install supermoon
43
+ ```
44
+
45
+ The first time you run it, supermoon downloads the JPL DE421 ephemeris (about 17 MB) to
46
+ `~/.supermoon`. To keep it somewhere else, or to reuse a copy you already have, set the
47
+ `SUPERMOON_DATA` environment variable to that directory. DE421 covers the years 1900 through 2050.
48
+
49
+ ## Command line usage
50
+
51
+ ```
52
+ usage: supermoon [-h] [--cnt CNT] [-B] [-P] [-D] [-A] [-C] [year] [endyear]
53
+
54
+ positional arguments:
55
+ year find supermoons for this year (optional, defaults to current date forward)
56
+ endyear stop finding supermoons (optional)
57
+
58
+ options:
59
+ -h, --help show this help message and exit
60
+ --cnt CNT moons to show
61
+ -B, --brief brief output
62
+ -P, --perigee include perigee time
63
+ -D, --distance include distances
64
+ -A, --angulardiameter
65
+ include angular diameter
66
+ -C, --csv also write results to supermoons.csv
67
+ ```
68
+
69
+ Examples:
70
+
71
+ ```
72
+ $ supermoon # the next supermoon
73
+ $ supermoon --cnt 3 -P -D -A # the next 3, with perigee, distances and angular diameter
74
+ $ supermoon 2029 # every supermoon in 2029
75
+ $ supermoon 2020 2035 -B # how many supermoons there are each year from 2020 to 2035
76
+ $ supermoon 2020 2035 -C # also save them to supermoons.csv
77
+ ```
78
+
79
+ `python -m supermoon` works the same way.
80
+
81
+ ## Python usage
82
+
83
+ ```python
84
+ from datetime import datetime, timezone
85
+ import supermoon
86
+
87
+ # the next supermoon after a date (defaults to now; naive datetimes are treated as UTC)
88
+ result = supermoon.next_supermoon(datetime(2025, 1, 1, tzinfo=timezone.utc))
89
+ result['fullmoon']['date'] # datetime.datetime(2025, 10, 7, 3, 47, 36, ..., tzinfo=UTC)
90
+ result['fullmoon']['distance'] # km
91
+ result['definitions'] # {'Sky & Telescope': False, 'Time & Date': False,
92
+ # 'Espenak': True, 'Nolle': True, 'within 1 day of perigee': False}
93
+
94
+ supermoon.supermoons(2029) # list of every supermoon in a year
95
+ supermoon.next_supermoons(count=3) # the next 3 supermoons
96
+ supermoon.describe(result, perigee=True, distance=True, angulardiameter=True) # printable lines
97
+ supermoon.write_csv(supermoon.supermoons(2029), "supermoons.csv")
98
+ ```
99
+
100
+ Each result is a dictionary with these keys:
101
+
102
+ | key | contents |
103
+ |---|---|
104
+ | `definitions` | each definition's name, mapped to whether this full Moon meets it |
105
+ | `fullmoon` | `date` (UTC), `localdate` (local time zone), `distance` (km) |
106
+ | `perigee` | `date`, `localdate` and `distance` of the closest perigee |
107
+ | `relative distance` | `thisorbit` (Espenak) and `thisyear` (Nolle) ratios |
108
+ | `full perigee delta hours` / `full perigee delta seconds` | time between the full Moon and perigee |
109
+ | `angular diameter` / `angular diameter raw` | the Moon's apparent size, as a string / in degrees |
110
+
111
+ ## Definitions used
112
+
113
+ * Richard Nolle, an astrologer, coined the term in 1979 in an article in _Dell Horoscope_ magazine.
114
+ He has refined his definition twice:
115
+ - rule 1 (1979): a full or new Moon at a distance 90% or greater than the perigee in a given orbit
116
+ - rule 2 (2000): a full or new Moon at a distance 90% or greater than mean perigee
117
+ [source](https://www.astropro.com/features/articles/supermoon/)
118
+ - rule 3 (2011): a full or new Moon at a distance 90% or greater than the closest perigee for
119
+ the calendar year. This package calculates this rule.
120
+ [source](https://www.astropro.com/features/tables/cen21ce/suprmoon.html)
121
+ * Fred Espenak (retired NASA astrophysicist, best known for lunar and solar eclipse predictions):
122
+ a full Moon at a distance 90% or greater of perigee during the current lunation. EarthSky also
123
+ [uses this definition](https://earthsky.org/astronomy-essentials/why-experts-disagree-on-what-makes-a-supermoon#nolle).
124
+ [source](http://astropixels.com/ephemeris/moon/fullperigee2001.html)
125
+ * Sky and Telescope magazine: a full Moon within 223,000 miles (358,884 km)
126
+ [source](https://skyandtelescope.org/observing/what-is-a-supermoon/)
127
+ * TimeandDate.com (a Norwegian company offering website and data services on time and astronomy):
128
+ a full Moon within 360,000 kilometers (223,694 mi)
129
+ [source](https://www.timeanddate.com/astronomy/moon/super-full-moon.html)
130
+ * Additionally, some sources have labeled full Moons within 24 hours of perigee as supermoons.
131
+
132
+ Nolle was presumably inspired by the real increase in tidal effects at a perigee
133
+ [syzygy](https://en.wikipedia.org/wiki/Syzygy_%28astronomy%29), so his tables include both new and
134
+ full Moons. Most mentions of supermoons in the popular media focus on full Moons near perigee,
135
+ because a new Moon is hard to see. This package only considers full Moons.
136
+
137
+ This collection of conflicting definitions is further described in
138
+ [this article I wrote on the subject](https://www.wral.com/weather/blogpost/11487264/).
139
+
140
+ ## Supermoons, 2020 through 2035
141
+
142
+ Times are US Eastern.
143
+
144
+ ```
145
+ 4 supermoons during 2020:
146
+ Sun 02/09/2020 02:33 AM EST (07:33 UTC) according to Espenak
147
+ Mon 03/09/2020 01:47 PM EDT (17:47 UTC) according to all known definitions
148
+ Tue 04/07/2020 10:35 PM EDT (02:35 UTC) according to all known definitions
149
+ Thu 05/07/2020 06:45 AM EDT (10:45 UTC) according to Espenak, and, Nolle
150
+ 4 supermoons during 2021:
151
+ Sun 03/28/2021 02:48 PM EDT (18:48 UTC) according to Espenak
152
+ Mon 04/26/2021 11:31 PM EDT (03:31 UTC) according to all known definitions
153
+ Wed 05/26/2021 07:13 AM EDT (11:13 UTC) according to all known definitions
154
+ Thu 06/24/2021 02:39 PM EDT (18:39 UTC) according to Espenak, and, Nolle
155
+ 4 supermoons during 2022:
156
+ Mon 05/16/2022 12:14 AM EDT (04:14 UTC) according to Espenak, and, Nolle
157
+ Tue 06/14/2022 07:51 AM EDT (11:51 UTC) according to all known definitions
158
+ Wed 07/13/2022 02:37 PM EDT (18:37 UTC) according to all known definitions
159
+ Thu 08/11/2022 09:35 PM EDT (01:35 UTC) according to Espenak, and, Nolle
160
+ 4 supermoons during 2023:
161
+ Mon 07/03/2023 07:38 AM EDT (11:38 UTC) according to Espenak
162
+ Tue 08/01/2023 02:31 PM EDT (18:31 UTC) according to all known definitions
163
+ Wed 08/30/2023 09:35 PM EDT (01:35 UTC) according to all known definitions
164
+ Fri 09/29/2023 05:57 AM EDT (09:57 UTC) according to Espenak, and, Nolle
165
+ 4 supermoons during 2024:
166
+ Mon 08/19/2024 02:25 PM EDT (18:25 UTC) according to Espenak
167
+ Tue 09/17/2024 10:34 PM EDT (02:34 UTC) according to all known definitions
168
+ Thu 10/17/2024 07:26 AM EDT (11:26 UTC) according to all known definitions
169
+ Fri 11/15/2024 04:28 PM EST (21:28 UTC) according to Espenak
170
+ 3 supermoons during 2025:
171
+ Mon 10/06/2025 11:47 PM EDT (03:47 UTC) according to Espenak, and, Nolle
172
+ Wed 11/05/2025 08:19 AM EST (13:19 UTC) according to all known definitions
173
+ Thu 12/04/2025 06:14 PM EST (23:14 UTC) according to all known definitions
174
+ 3 supermoons during 2026:
175
+ Sat 01/03/2026 05:02 AM EST (10:02 UTC) according to Espenak
176
+ Tue 11/24/2026 09:53 AM EST (14:53 UTC) according to Espenak, and, Nolle
177
+ Wed 12/23/2026 08:28 PM EST (01:28 UTC) according to all known definitions
178
+ 3 supermoons during 2027:
179
+ Fri 01/22/2027 07:17 AM EST (12:17 UTC) according to all known definitions
180
+ Sat 02/20/2027 06:23 PM EST (23:23 UTC) according to Espenak
181
+ Mon 12/13/2027 11:08 AM EST (16:08 UTC) according to Espenak
182
+ 4 supermoons during 2028:
183
+ Tue 01/11/2028 11:03 PM EST (04:03 UTC) according to Espenak, and, Nolle
184
+ Thu 02/10/2028 10:03 AM EST (15:03 UTC) according to all known definitions
185
+ Fri 03/10/2028 08:06 PM EST (01:06 UTC) according to all known definitions
186
+ Sun 04/09/2028 06:26 AM EDT (10:26 UTC) according to Espenak
187
+ 5 supermoons during 2029:
188
+ Tue 01/30/2029 01:03 AM EST (06:03 UTC) according to Espenak
189
+ Wed 02/28/2029 12:10 PM EST (17:10 UTC) according to Time & Date, Espenak, and, Nolle
190
+ Thu 03/29/2029 10:26 PM EDT (02:26 UTC) according to all known definitions
191
+ Sat 04/28/2029 06:36 AM EDT (10:36 UTC) according to all known definitions
192
+ Sun 05/27/2029 02:37 PM EDT (18:37 UTC) according to Espenak
193
+ 5 supermoons during 2030:
194
+ Tue 03/19/2030 01:56 PM EDT (17:56 UTC) according to Espenak
195
+ Wed 04/17/2030 11:20 PM EDT (03:20 UTC) according to Time & Date, Espenak, and, Nolle
196
+ Fri 05/17/2030 07:19 AM EDT (11:19 UTC) according to all known definitions
197
+ Sat 06/15/2030 02:41 PM EDT (18:41 UTC) according to all known definitions
198
+ Sun 07/14/2030 10:12 PM EDT (02:12 UTC) according to Espenak
199
+ 5 supermoons during 2031:
200
+ Tue 05/06/2031 11:39 PM EDT (03:39 UTC) according to Espenak
201
+ Thu 06/05/2031 07:58 AM EDT (11:58 UTC) according to Time & Date, Espenak, and, Nolle
202
+ Fri 07/04/2031 03:01 PM EDT (19:01 UTC) according to all known definitions
203
+ Sat 08/02/2031 09:45 PM EDT (01:45 UTC) according to all known definitions
204
+ Mon 09/01/2031 05:20 AM EDT (09:20 UTC) according to Espenak
205
+ 5 supermoons during 2032:
206
+ Wed 06/23/2032 07:32 AM EDT (11:32 UTC) according to Espenak
207
+ Thu 07/22/2032 02:51 PM EDT (18:51 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
208
+ Fri 08/20/2032 09:46 PM EDT (01:46 UTC) according to all known definitions
209
+ Sun 09/19/2032 05:30 AM EDT (09:30 UTC) according to all known definitions
210
+ Mon 10/18/2032 02:58 PM EDT (18:58 UTC) according to Espenak
211
+ 5 supermoons during 2033:
212
+ Wed 08/10/2033 02:07 PM EDT (18:07 UTC) according to Espenak
213
+ Thu 09/08/2033 10:20 PM EDT (02:20 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
214
+ Sat 10/08/2033 06:58 AM EDT (10:58 UTC) according to all known definitions
215
+ Sun 11/06/2033 03:32 PM EST (20:32 UTC) according to all known definitions
216
+ Tue 12/06/2033 02:22 AM EST (07:22 UTC) according to Espenak
217
+ 4 supermoons during 2034:
218
+ Wed 09/27/2034 10:56 PM EDT (02:56 UTC) according to Espenak
219
+ Fri 10/27/2034 08:42 AM EDT (12:42 UTC) according to all known definitions
220
+ Sat 11/25/2034 05:32 PM EST (22:32 UTC) according to all known definitions
221
+ Mon 12/25/2034 03:54 AM EST (08:54 UTC) according to Time & Date, Espenak, Nolle, and, within 1 day of perigee
222
+ 3 supermoons during 2035:
223
+ Tue 01/23/2035 03:16 PM EST (20:16 UTC) according to Espenak
224
+ Thu 11/15/2035 08:48 AM EST (13:48 UTC) according to Espenak
225
+ Fri 12/14/2035 07:33 PM EST (00:33 UTC) according to all known definitions
226
+ ```
227
+
228
+ ## Development
229
+
230
+ The project uses [uv](https://docs.astral.sh/uv/):
231
+
232
+ ```
233
+ uv sync --extra test
234
+ uv run pytest tests/supermoon_tests.py
235
+ uv run supermoon 2025
236
+ ```
237
+
238
+ `tests/basic.py` checks the perigee and apogee calculations against Fred Espenak's published
239
+ tables. It downloads those tables from astropixels.com, so it needs network access.
240
+
241
+ ## Releasing to PyPI
242
+
243
+ ```
244
+ uv build
245
+ uv publish
246
+ ```
247
+
248
+ Before each release, bump `__version__` in `supermoon/__init__.py`.
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ supermoon/__init__.py
5
+ supermoon/__main__.py
6
+ supermoon/apsis.py
7
+ supermoon/cli.py
8
+ supermoon/core.py
9
+ supermoon/ephemeris.py
10
+ supermoon/lunarphases.py
11
+ supermoon.egg-info/PKG-INFO
12
+ supermoon.egg-info/SOURCES.txt
13
+ supermoon.egg-info/dependency_links.txt
14
+ supermoon.egg-info/entry_points.txt
15
+ supermoon.egg-info/requires.txt
16
+ supermoon.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ supermoon = supermoon.cli:main
@@ -0,0 +1,10 @@
1
+ skyfield>=1.31
2
+ numpy
3
+ tzlocal
4
+
5
+ [test]
6
+ pytest
7
+ pytz
8
+ requests
9
+ beautifulsoup4
10
+ lxml
@@ -0,0 +1 @@
1
+ supermoon