Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 294 lines (267 sloc) 9.62 KB
#!/usr/bin/python3
#+
# Conversion between Gregorian dates and Julian days/times. This
# script can be invoked in various ways (all dates/times in UTC):
#
# Julian -d|--days
# outputs the current integer Julian day number.
# Julian -f|--fracdays
# outputs the current Julian day number with fraction
# indicating time of day.
# Julian -s|--seconds
# outputs the current Julian seconds
# (Julian day number * 84600 plus seconds since midnight)
# Julian -d|--days n
# outputs the Gregorian year, month and day corresponding
# to Julian day n.
# Julian -f|--fracdays n
# outputs the Gregorian year, month and day and hours, minutes
# and seconds since midnight corresponding to (possibly
# fractional) Julian day n.
# Julian -s|--seconds n
# outputs the Gregorian year, month and day and hours, minutes
# and seconds since midnight corresponding to Julian seconds n.
# Julian -d|--days y m d
# outputs the Julian day corresponding to the specified
# Gregorian year, month and day.
# Julian -f|--fracdays y m d h m s
# outputs the Julian day (with fraction indicating time of day)
# corresponding to the specified Gregorian year, month and day
# and hours, minutes and seconds since midnight.
# Julian -s|--seconds y m d h m s
# outputs the Julian seconds corresponding to the specified
# Gregorian year, month and day and hours, minutes and seconds
# since midnight.
#
# Additional option:
#
# -a|--astro
# indicates that Julian days are to counted from noon UTC
# (astronomical convention) instead of midnight.
#
# Copyright 2007-2015 by Lawrence D’Oliveiro <ldo@geek-central.gen.nz>.
# Licensed under CC-BY-SA <http://creativecommons.org/licenses/by-sa/4.0/>.
#-
import sys
import time
import datetime
import getopt
#+
# Useful stuff
#-
seconds_per_day = 86400
julian_day_offset = 1721425 # add to datetime.datetime.toordinal() to get Julian day number
gregorian_years_repeat = 400
# calendar rules repeat every this number of years (interval contains 97 leap years)
gregorian_weeks_repeat = 20871 # exactly this number of weeks in 400 Gregorian calendar years
class Date :
"extension of datetime.date to handle a wider range of years."
def __init__(self, *args) :
if len(args) == 3 :
year, month, day = args
if year < datetime.MINYEAR or year > datetime.MAXYEAR :
adj_year = (year - datetime.MINYEAR) % gregorian_years_repeat + datetime.MINYEAR
self.cycle_adjust = (year - adj_year) // gregorian_years_repeat # will be exact division
year = adj_year
else :
self.cycle_adjust = 0
#end if
self._date = datetime.date(year, month, day)
elif len(args) == 2 : # internal shortcut use
self._date, self.cycle_adjust = args
else :
raise ValueError("usage: Date(y, m, d)")
#end if
#end __init__
@property
def year(self) :
return self._date.year + self.cycle_adjust * gregorian_years_repeat
#end year
@property
def month(self) :
return self._date.month
#end year
@property
def day(self) :
return self._date.day
#end day
@classmethod
def fromordinal(celf, n) :
if n < 1 or n > datetime.date.max.toordinal() :
adj_days = (n - 1) % (gregorian_weeks_repeat * 7) + 1
cycle_adjust = (n - adj_days) // (gregorian_weeks_repeat * 7) # will be exact division
n = adj_days
else :
cycle_adjust = 0
#end if
return Date(datetime.date.fromordinal(n), cycle_adjust)
#end fromordinal
def toordinal(self) :
return self._date.toordinal() + self.cycle_adjust * gregorian_weeks_repeat * 7
#end toordinal
def replace(self, year = None, month = None, day = None) :
"returns the same Date, except for replacement of whichever arguments are specified."
if year == None :
year = self.year
#end if
if month == None :
month = self.month
#end if
if day == None :
day = self.day
#end if
return Date(year, month, day)
#end replace
def __add__(self, delta) :
"adds a Date to a timedelta, giving a new Date."
return Date.fromordinal(self.toordinal() + delta.days)
#end __add__
def __sub__(self, other) :
"subtracts two Dates, returning a datetime.timedelta."
return \
datetime.timedelta \
(
days =
(self._date - other._date).days
+
(self.cycle_adjust - other.cycle_adjust) * gregorian_weeks_repeat * 7
)
#end __sub__
def weekday(self) :
"returns day of week, [Mon .. Sun] => [0 .. 6]."
return self._date.weekday()
#end weekday
def isoweekday(self) :
"returns day of week, [Mon .. Sun] => [1 .. 7]."
return self._date.isoweekday()
#end isoweekday
def isocalendar(self) :
result = self._date.isocalendar()
return (result[0] + self.cycle_adjust * gregorian_years_repeat, result[1], result[2])
#end isocalendar
#end Date
unix_base_julian_day = Date(1970, 1, 1).toordinal() + julian_day_offset
def now_julian() :
"returns the number of Julian-day seconds for the present moment (UTC)."
return \
(
unix_base_julian_day * seconds_per_day
+
int(time.time())
)
#end now_julian
def ymd_to_julian(y, m, d) :
"returns the Julian day number corresponding to the specified year, month and day of month."
return \
Date(y, m, d).toordinal() + julian_day_offset
#end ymd_to_julian
def julian_to_ymd(j) :
"returns the (year, month, day) triple corresponding to the specified Julian day number."
result = Date.fromordinal(j - julian_day_offset)
return \
(result.year, result.month, result.day)
#end julian_to_ymd
#+
# Mainline
#-
def mainline() :
astron_adjust = 0.0
(opts, args) = getopt.getopt \
(
sys.argv[1:],
"adfs",
["astro", "days", "fracdays", "seconds",]
)
do_what = None
for keyword, value in opts :
if keyword == "-a" or keyword == "--astro" :
astron_adjust = 0.5
elif keyword == "-d" or keyword == "--days" :
do_what = "days"
elif keyword == "-f" or keyword == "--fracdays" :
do_what = "fracdays"
elif keyword == "-s" or keyword == "--seconds" :
do_what = "seconds"
#end if
#end for
if do_what == None :
raise getopt.GetoptError("must specify either --days or --seconds")
#end if
if do_what == "seconds" : # conversions in seconds
if len(args) == 1 :
timeval = int(args[0]) + round(astron_adjust * seconds_per_day)
dateval = julian_to_ymd(timeval // seconds_per_day)
timeval %= seconds_per_day
hours = timeval // 3600
minutes = timeval // 60 % 60
seconds = timeval % 60
result = dateval + (hours, minutes, seconds)
elif len(args) == 6 :
result = \
(
ymd_to_julian(int(args[0]), int(args[1]), int(args[2]))
*
seconds_per_day
+
int(args[3]) * 3600
+
int(args[4]) * 60
+
int(args[5])
-
round(astron_adjust * seconds_per_day),
)
elif len(args) == 0 :
result = (now_julian(),)
else :
raise getopt.GetoptError("must specify 0, 1 or 6 args for --seconds")
#end if
elif do_what == "fracdays" :
if len(args) == 1 :
timeval = float(args[0]) + astron_adjust
dateval = julian_to_ymd(int(timeval))
time_of_day = int((timeval - int(timeval)) * seconds_per_day)
hours = int(time_of_day) // 3600
minutes = int(time_of_day) // 60 % 60
seconds = time_of_day % 60
result = dateval + (hours, minutes, seconds)
elif len(args) == 6 :
result = \
(
(
ymd_to_julian(int(args[0]), int(args[1]), int(args[2]))
*
seconds_per_day
+
int(args[3]) * 3600
+
int(args[4]) * 60
+
int(args[5])
)
/
seconds_per_day
-
astron_adjust,
)
elif len(args) == 0 :
result = (now_julian() / seconds_per_day - astron_adjust,)
else :
raise getopt.GetoptError("must specify 0, 1 or 6 args for --fracdays")
#end if
else : # conversions in whole days
if len(args) == 1 :
result = julian_to_ymd(int(args[0]))
elif len(args) == 3 :
result = (ymd_to_julian(int(args[0]), int(args[1]), int(args[2])),)
elif len(args) == 0 :
result = (now_julian() // seconds_per_day,)
else :
raise getopt.GetoptError("must specify 0, 1 or 3 args for --days")
#end if
#end if
sys.stdout.write(" ".join([str(i) for i in result]) + "\n")
#end mainline
if __name__ == "__main__" :
mainline()
#end if