Skip to content

Commit 9c4136b

Browse files
committed
Initial work on CLI tool -> implemented date/time functions.
1 parent 9b98221 commit 9c4136b

File tree

3 files changed

+352
-0
lines changed

3 files changed

+352
-0
lines changed

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,70 @@
33
Algorithms from "[Practical Astronomy with your Calculator or Spreadsheet](https://www.amazon.com/Practical-Astronomy-your-Calculator-Spreadsheet/dp/1108436072)" by Peter Duffett-Smith, implemented in Python 3. API documentation is published [here](https://jfcarr-astronomy.github.io/practical-astronomy-python/).
44

55
If you're interested in this topic, please buy the book! It provides far more detail and context.
6+
7+
## CLI
8+
9+
Work is started on a command-line interface, but only the date/time functions are implemented, so far. Results are formatted as JSON.
10+
11+
Invoke as `./pa-cli.py -h` to see all of the options:
12+
13+
```
14+
$ ./pa-cli.py -h
15+
16+
usage: pa-cli.py [-h] [--cd CD] [--ct CT] [--dh DH] [--gd GD] [--gl GL] [--gst GST] [--lst LST] [--jd JD] [--ut UT] [--year YEAR] [--dst] [--st] [--zc ZC] [--doe] [--cd_to_dn] [--gd_to_jd] [--jd_to_gd] [--ct_to_dh] [--dh_to_ct] [--lct_to_ut] [--ut_to_lct] [--ut_to_gst] [--gst_to_ut] [--gst_to_lst] [--lst_to_gst]
17+
18+
Practical Astronomy CLI.
19+
20+
optional arguments:
21+
-h, --help show this help message and exit
22+
23+
Actions:
24+
--doe Calculate date of Easter, for a given year.
25+
--cd_to_dn Convert civil date to day number.
26+
--gd_to_jd Convert Greenwich date to Julian date.
27+
--jd_to_gd Convert Julian date to Greenwich date.
28+
--ct_to_dh Convert civil time to decimal hours.
29+
--dh_to_ct Convert decimal hours to civil time.
30+
--lct_to_ut Convert local civil time to universal time.
31+
--ut_to_lct Convert universal time to local civil time.
32+
--ut_to_gst Convert universal time to Greenwich sidereal time.
33+
--gst_to_ut Convert Greenwich sidereal time to universal time.
34+
--gst_to_lst Convert Greenwich sidereal time to local sidereal time.
35+
--lst_to_gst Convert local sidereal time to Greenwich sidereal time.
36+
37+
Inputs (used by Actions):
38+
--cd CD Civil date. Input format: 'mm/dd/yyyy'
39+
--ct CT Civil time. Input format: 'hh:mm:ss'
40+
--dh DH Decimal hours. Input format: floating point number, e.g., 18.52416667
41+
--gd GD Greenwich date. Input format: 'mm/dd/yyyy'. Fractional day is allowed, e.g., '6/19.75/2009'
42+
--gl GL Geographical longitude. Input format: (+/-)##.##, e.g., -64.00
43+
--gst GST Greenwich sidereal time. Input format: 'hh:mm:ss'
44+
--lst LST Local sidereal time. Input format: 'hh:mm:ss'
45+
--jd JD Julian date. Input format: floating point number, e.g., 2455002.25
46+
--ut UT Universal time. Input format: 'hh:mm:ss'
47+
--year YEAR Calendar year, e.g. 2019.
48+
49+
Inputs (time zone management):
50+
--dst Observe daylight savings time.
51+
--st Observe standard time (default)
52+
--zc ZC Offset, in hours, for time zone correction.
53+
54+
```
55+
56+
### Example
57+
58+
Convert universal time to Greenwich sidereal time:
59+
60+
```
61+
$./pa-cli.py --ut_to_gst --ut "14:36:51.67" --gd "4/22/1980"
62+
63+
{"greenwichSiderealTimeHours": 4, "greenwichSiderealTimeMinutes": 40, "greenwichSiderealTimeSeconds": 5.23}
64+
```
65+
66+
## Unit Tests
67+
68+
Unit test run can be invoked via the Make utility:
69+
70+
```
71+
make all
72+
```

cli-test.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
./pa-cli.py --doe --year 2019
4+
./pa-cli.py --cd_to_dn --cd "2/1/2019"
5+
./pa-cli.py --gd_to_jd --gd "6/19.75/2009"
6+
./pa-cli.py --jd_to_gd --jd 2455002.25
7+
./pa-cli.py --ct_to_dh --ct "18:31:27"
8+
./pa-cli.py --dh_to_ct --dh 18.52416667
9+
./pa-cli.py --lct_to_ut --cd "7/1/2013" --ct "3:37:00" --zc 4 --dst
10+
./pa-cli.py --ut_to_lct --cd "6/30/2013" --ut "22:37:00" --zc 4 --dst
11+
./pa-cli.py --ut_to_gst --ut "14:36:51.67" --gd "4/22/1980"
12+
./pa-cli.py --gst_to_ut --gst "4:40:5.23" --gd "4/22/1980"
13+
./pa-cli.py --gst_to_lst --gst "4:40:5.23" --gl -64
14+
./pa-cli.py --lst_to_gst --lst "0:24:5.23" --gl -64

pa-cli.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/python3
2+
3+
import argparse
4+
import json
5+
import lib.pa_datetime as PA_DT
6+
from dateutil.parser import parse
7+
8+
def display_error(error_description):
9+
'''
10+
Return error details for a failed call.
11+
'''
12+
error_dict = dict(errorDescription=error_description)
13+
14+
print(json.dumps(error_dict))
15+
16+
def doe_year(year):
17+
'''
18+
Calculate date of Easter for a given year.
19+
'''
20+
easter_month,easter_day,easter_year = PA_DT.get_date_of_easter(year)
21+
easter_dict = dict(easterMonth=easter_month, easterDay=easter_day, easterYear=easter_year)
22+
23+
print(json.dumps(easter_dict))
24+
25+
def cd_to_dn(date_string):
26+
'''
27+
Convert civil date to day number.
28+
'''
29+
dt = parse(date_string)
30+
day_number = PA_DT.civil_date_to_day_number(dt.month, dt.day, dt.year)
31+
day_num_dict = dict(dayNumber=day_number)
32+
33+
print(json.dumps(day_num_dict))
34+
35+
def gd_to_jd(greenwich_date):
36+
'''
37+
Convert Greenwich date to Julian date.
38+
'''
39+
dt_split = greenwich_date.split("/")
40+
41+
julian_date = PA_DT.greenwich_date_to_julian_date(float(dt_split[1]), int(dt_split[0]), int(dt_split[2]))
42+
julian_date_dict = dict(julianDate=julian_date)
43+
44+
print(json.dumps(julian_date_dict))
45+
46+
def jd_to_gd(julian_date):
47+
'''
48+
Convert Julian date to Greenwich date.
49+
'''
50+
day,month,year = PA_DT.julian_date_to_greenwich_date(julian_date)
51+
greenwich_date_dict = dict(greenwichDay=day, greenwichMonth=month, greenwichYear=year)
52+
53+
print(json.dumps(greenwich_date_dict))
54+
55+
def ct_to_dh(civil_time):
56+
'''
57+
Convert civil time to decimal hours.
58+
'''
59+
ct_split = civil_time.split(":")
60+
61+
decimal_hours = PA_DT.civil_time_to_decimal_hours(int(ct_split[0]), int(ct_split[1]), int(ct_split[2]))
62+
decimal_hours = round(decimal_hours,8)
63+
decimal_hours_dict = dict(decimalHours=decimal_hours)
64+
65+
print(json.dumps(decimal_hours_dict))
66+
67+
def dh_to_ct(decimal_hours):
68+
'''
69+
Convert decimal hours to civil time.
70+
'''
71+
hours,minutes,seconds = PA_DT.decimal_hours_to_civil_time(decimal_hours)
72+
civil_time_dict = dict(civilTimeHours=hours, civilTimeMinutes=minutes, civilTimeSeconds=seconds)
73+
74+
print(json.dumps(civil_time_dict))
75+
76+
def lct_to_ut(civil_date, civil_time, is_dst, zone_correction):
77+
'''
78+
Convert local civil time to universal time.
79+
'''
80+
cd_split = civil_date.split("/")
81+
ct_split = civil_time.split(":")
82+
ut_hours,ut_minutes,ut_seconds,gw_day,gw_month,gw_year = PA_DT.local_civil_time_to_universal_time(int(ct_split[0]), int(ct_split[1]), int(ct_split[2]), is_dst, zone_correction, int(cd_split[1]), int(cd_split[0]), int(cd_split[2]))
83+
ut_dict = dict(utHours=ut_hours,utMinutes=ut_minutes,utSeconds=ut_seconds,greenwichDay=gw_day,greenwichMonth=gw_month,greenwichYear=gw_year)
84+
85+
print(json.dumps(ut_dict))
86+
87+
def ut_to_lct(civil_date, universal_time, is_dst, zone_correction):
88+
'''
89+
Convert universal time to local civil time.
90+
'''
91+
cd_split = civil_date.split("/")
92+
ut_split = universal_time.split(":")
93+
lct_hours,lct_minutes,lct_seconds,gw_day,gw_month,gw_year = PA_DT.universal_time_to_local_civil_time(int(ut_split[0]), int(ut_split[1]), int(ut_split[2]), is_dst, zone_correction, int(cd_split[1]), int(cd_split[0]), int(cd_split[2]))
94+
lct_dict = dict(lctHours=lct_hours,lctMinutes=lct_minutes,lctSeconds=lct_seconds,greenwichDay=gw_day,greenwichMonth=gw_month,greenwichYear=gw_year)
95+
96+
print(json.dumps(lct_dict))
97+
98+
def ut_to_gst(universal_time, greenwich_date):
99+
'''
100+
Convert Universal time to Greenwich sidereal time
101+
'''
102+
ut_split = universal_time.split(":")
103+
gd_split = greenwich_date.split("/")
104+
gst_hours,gst_minutes,gst_seconds = PA_DT.universal_time_to_greenwich_sidereal_time(int(ut_split[0]), int(ut_split[1]), float(ut_split[2]), int(gd_split[1]), int(gd_split[0]), int(gd_split[2]))
105+
gst_dict = dict(greenwichSiderealTimeHours=gst_hours, greenwichSiderealTimeMinutes=gst_minutes, greenwichSiderealTimeSeconds=gst_seconds)
106+
107+
print(json.dumps(gst_dict))
108+
109+
def gst_to_ut(greenwich_sidereal_time, greenwich_date):
110+
'''
111+
Convert Greenwich sidereal time to universal time
112+
'''
113+
gst_split = greenwich_sidereal_time.split(":")
114+
gd_split = greenwich_date.split("/")
115+
ut_hours,ut_minutes,ut_seconds,status_message = PA_DT.greenwich_sidereal_time_to_universal_time(int(gst_split[0]),int(gst_split[1]),float(gst_split[2]),int(gd_split[1]),int(gd_split[0]),int(gd_split[2]))
116+
ut_dict = dict(utHours=ut_hours,utMinutes=ut_minutes,utSeconds=ut_seconds,statusMessage=status_message)
117+
118+
print(json.dumps(ut_dict))
119+
120+
def gst_to_lst(greenwich_sidereal_time, geographical_longitude):
121+
'''
122+
Convert Greenwich sidereal time to local sidereal time
123+
'''
124+
gst_split = greenwich_sidereal_time.split(":")
125+
lst_hours,lst_minutes,lst_seconds = PA_DT.greenwich_sidereal_time_to_local_sidereal_time(int(gst_split[0]), int(gst_split[1]), float(gst_split[2]), geographical_longitude)
126+
lst_dict = dict(lstHours=lst_hours,lstMinutes=lst_minutes,lstSeconds=lst_seconds)
127+
128+
print(json.dumps(lst_dict))
129+
130+
def lst_to_gst(local_sidereal_time, geographical_longitude):
131+
'''
132+
Convert local sidereal time to Greenwich sidereal time
133+
'''
134+
lst_split = local_sidereal_time.split(":")
135+
gst_hours,gst_minutes,gst_seconds = PA_DT.local_sidereal_time_to_greenwich_sidereal_time(int(lst_split[0]),int(lst_split[1]),float(lst_split[2]),geographical_longitude)
136+
gst_dict = dict(gstHours=gst_hours,gstMinutes=gst_minutes,gstSeconds=gst_seconds)
137+
138+
print(json.dumps(gst_dict))
139+
140+
def main():
141+
parser = argparse.ArgumentParser(description='Practical Astronomy CLI.')
142+
143+
# Add argument groups
144+
actions_group = parser.add_argument_group(title="Actions")
145+
inputs_group = parser.add_argument_group(title="Inputs (used by Actions)")
146+
inputs_tz_group = parser.add_argument_group(title="Inputs (time zone management)")
147+
148+
# Inputs
149+
inputs_group.add_argument("--cd", type=str, help="Civil date. Input format: 'mm/dd/yyyy'")
150+
inputs_group.add_argument("--ct", type=str, help="Civil time. Input format: 'hh:mm:ss'")
151+
inputs_group.add_argument("--dh", type=float, help="Decimal hours. Input format: floating point number, e.g., 18.52416667")
152+
inputs_group.add_argument("--gd", type=str, help="Greenwich date. Input format: 'mm/dd/yyyy'. Fractional day is allowed, e.g., '6/19.75/2009'")
153+
inputs_group.add_argument("--gl", type=float, help="Geographical longitude. Input format: (+/-)##.##, e.g., -64.00")
154+
inputs_group.add_argument("--gst", type=str, help="Greenwich sidereal time. Input format: 'hh:mm:ss'")
155+
inputs_group.add_argument("--lst", type=str, help="Local sidereal time. Input format: 'hh:mm:ss'")
156+
inputs_group.add_argument("--jd", type=float, help="Julian date. Input format: floating point number, e.g., 2455002.25")
157+
inputs_group.add_argument("--ut", type=str, help="Universal time. Input format: 'hh:mm:ss'")
158+
inputs_group.add_argument("--year", type=int, help="Calendar year, e.g. 2019.")
159+
160+
# Inputs (time zone management)
161+
inputs_tz_group.add_argument('--dst', dest='is_daylight_savings', action='store_true', help="Observe daylight savings time.")
162+
inputs_tz_group.add_argument('--st', dest='is_daylight_savings', action='store_false', help="Observe standard time (default)")
163+
inputs_tz_group.set_defaults(is_daylight_savings=False)
164+
inputs_tz_group.add_argument("--zc", type=int, help="Offset, in hours, for time zone correction.")
165+
166+
# Actions
167+
actions_group.add_argument("--doe", action='store_true', help="Calculate date of Easter, for a given year.")
168+
actions_group.add_argument("--cd_to_dn", action='store_true', help="Convert civil date to day number.")
169+
actions_group.add_argument("--gd_to_jd", action='store_true', help="Convert Greenwich date to Julian date.")
170+
actions_group.add_argument("--jd_to_gd", action='store_true', help="Convert Julian date to Greenwich date.")
171+
actions_group.add_argument("--ct_to_dh", action='store_true', help="Convert civil time to decimal hours.")
172+
actions_group.add_argument("--dh_to_ct", action='store_true', help="Convert decimal hours to civil time.")
173+
actions_group.add_argument("--lct_to_ut", action='store_true', help="Convert local civil time to universal time.")
174+
actions_group.add_argument("--ut_to_lct", action='store_true', help="Convert universal time to local civil time.")
175+
actions_group.add_argument("--ut_to_gst", action='store_true', help="Convert universal time to Greenwich sidereal time.")
176+
actions_group.add_argument("--gst_to_ut", action='store_true', help="Convert Greenwich sidereal time to universal time.")
177+
actions_group.add_argument("--gst_to_lst", action='store_true', help="Convert Greenwich sidereal time to local sidereal time.")
178+
actions_group.add_argument("--lst_to_gst", action='store_true', help="Convert local sidereal time to Greenwich sidereal time.")
179+
180+
args=parser.parse_args()
181+
182+
if args.doe:
183+
if not args.year:
184+
display_error("'year' argument is required.")
185+
else:
186+
doe_year(args.year)
187+
188+
if args.cd_to_dn:
189+
if not args.cd:
190+
display_error("'cd' argument is required.")
191+
else:
192+
cd_to_dn(args.cd)
193+
194+
if args.gd_to_jd:
195+
if not args.gd:
196+
display_error("'gd' argument is required.")
197+
else:
198+
gd_to_jd(args.gd)
199+
200+
if args.jd_to_gd:
201+
if not args.jd:
202+
display_error("'jd' argument is required.")
203+
else:
204+
jd_to_gd(args.jd)
205+
206+
if args.ct_to_dh:
207+
if not args.ct:
208+
display_error("'ct' argument is required.")
209+
else:
210+
ct_to_dh(args.ct)
211+
212+
if args.dh_to_ct:
213+
if not args.dh:
214+
display_error("'dh' argument is required.")
215+
else:
216+
dh_to_ct(args.dh)
217+
218+
if args.lct_to_ut:
219+
if not args.cd:
220+
display_error("'cd', argument is required.")
221+
elif not args.ct:
222+
display_error("'ct', argument is required.")
223+
elif not args.zc:
224+
display_error("'zc', argument is required.")
225+
else:
226+
lct_to_ut(args.cd, args.ct, args.is_daylight_savings, args.zc)
227+
228+
if args.ut_to_lct:
229+
if not args.cd:
230+
display_error("'cd', argument is required.")
231+
elif not args.ut:
232+
display_error("'ut', argument is required.")
233+
elif not args.zc:
234+
display_error("'zc', argument is required.")
235+
else:
236+
ut_to_lct(args.cd, args.ut, args.is_daylight_savings, args.zc)
237+
238+
if args.ut_to_gst:
239+
if not args.ut:
240+
display_error("'ut' argument is required.")
241+
elif not args.gd:
242+
display_error("'gd' argument is required.")
243+
else:
244+
ut_to_gst(args.ut, args.gd)
245+
246+
if args.gst_to_ut:
247+
if not args.gst:
248+
display_error("'gst' argument is required.")
249+
elif not args.gd:
250+
display_error("'gd' argument is required.")
251+
else:
252+
gst_to_ut(args.gst, args.gd)
253+
254+
if args.gst_to_lst:
255+
if not args.gst:
256+
display_error("'gst' argument is required.")
257+
elif not args.gl:
258+
display_error("'gl' argument is required.")
259+
else:
260+
gst_to_lst(args.gst, args.gl)
261+
262+
if args.lst_to_gst:
263+
if not args.lst:
264+
display_error("'lst' argument is required.")
265+
elif not args.gl:
266+
display_error("'gl' argument is required.")
267+
else:
268+
lst_to_gst(args.lst, args.gl)
269+
270+
if __name__ == "__main__":
271+
main()

0 commit comments

Comments
 (0)