import math
import doctest
import typing
# ========================================================
# ========================================================
# === DURATION
# ========================================================
# ========================================================
[docs]class Duration:
""" Class to convert dates for astronomy
Date formats are:
* day = days. e.g. 12.3457
* dhms = day, hour, minutes seconds. e.g. 2d12h34m55.23s
"""
# ========================================================
# === internal methods
# ========================================================
def _init(self,duration="") -> None:
""" Initialize internal attributes.
:param duration: Duration in any supported format (cf. help(Duration))
:type date: any
:returns: None
:rtype: None
:Example:
>>> objduration = Duration()
>>> objduration._init()
"""
self._init_duration = duration
self._init_durationformat = 0
self._computed_day = 0
self._day = 0
self._computed_dhms = 0
self._dhms = "0d00h00m00s"
self._dhms_format = "0.3"
def _is_number(self,s) -> bool:
""" Return True if the string is a number else return False.
:param s: A string to test
:type s: string
:returns: True is the string can be concerted into a float
:rtype: bool
:Example:
>>> objdate = Date()
>>> objdate._is_number("3e5")
True
>>> objdate._is_number("3a5")
False
"""
try:
float(s)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
def _day2dhms(self, day, dhms_format) -> str:
if (self._computed_day == 0):
return ""
symbols = str(dhms_format)
digits = ""
separator=0; # 0=dhms 1=space 2=:
sign=0; #
zeros=0; # 0=spaces 1=leading zeros
k=0
for car in symbols:
if (car==' '):
separator=1
if (car=='_'):
separator=1
if (car==':'):
separator=2
if (car=='+'):
sign=1
if (self._is_number(car)) or car=='.':
digits = symbols[k:]
break
k += 1
# int unit=0; // 0=deg 1=hours
# int modulo=0; // 0=360 1=180
# int separator=0; // 0=hdms 1=space 2=:
# int sign=0; // 0=[0:modulo] 1=[-modulo/2:modulo/2]
# === trailing format (digits)
nb_decimalsec = 2
ld = len(digits)
if (ld>0):
# e.g. digits = ".3"
car = digits[0]
if car=='0':
zeros=1
digits = digits[1:]
# now digits = ".3" and zeros=1
kd = digits.find(".")
if kd >= 0:
if (self._is_number(digits[kd+1:])==True):
nb_decimalsec = int(digits[kd+1:])
# now digits_nb_decimal="3"
#print("ld="+str(ld))
#print("symbols="+symbols)
#print("digits="+digits)
#print("separator="+str(separator))
#print("sign="+str(sign))
#print("zeros="+str(zeros))
#print("nb_decimal="+str(nb_decimalsec))
# --- sign of the input angle
s = 1
if day<0:
s = -1
day = -day
# --- compute the trheee components of dd hh mm ss
r = day
dd = int(math.floor(r))
hh = 0
mm = 0
ss = 0
r = (r-dd)*24
hh = int(math.floor(r))
r = (r-hh)*60
mm = int(math.floor(r))
ss = (r-mm)*60
#print("hh ="+str(hh))
#print("mm ="+str(mm))
#print("ss ="+str(ss))
# === Compute the result
result = ""
# --- sign
if sign==1 and s>=0:
result += "+"
elif s<0:
result += "-"
# --- dd
fstring = "{:d}"
result += fstring.format(dd)
# --- separator dd hh
if separator==1:
result += " "
elif separator==2:
result += ":"
else:
result += "d"
# --- hh
if zeros==0:
fstring = "{:d}"
else:
fstring = "{:02d}"
result += fstring.format(hh)
# --- separator hh mm
if separator==1:
result += " "
elif separator==2:
result += ":"
else:
result += "h"
# --- mm
if zeros==0:
fstring = "{:d}"
else:
fstring = "{:02d}"
result += fstring.format(mm)
# --- separator mm ss
if separator==1:
result += " "
elif separator==2:
result += ":"
else:
result += "m"
# --- ss
if zeros==0:
fstring = "{:."+str(nb_decimalsec)+"f}"
else:
if nb_decimalsec==0:
fstring = "{:0"+str(nb_decimalsec+2)+".0f}"
else:
fstring = "{:0"+str(nb_decimalsec+3)+"."+str(nb_decimalsec)+"f}"
#print("fstring="+fstring)
result += fstring.format(ss)
# --- separator ss
if separator==0:
result += "s"
# -- end
return 0, result
def _duration2day(self, duration) -> float:
""" Return a duration in day unit from a duration in ymdhms format.
:param duration: A string formated as "2d7h23m12.5s"
:type duration: string
:returns: duration expressed in day and fraction of day
:rtype: float
:Example:
>>> objduration = Date()
>>> objduration._duration2day("-0d7h23m12.5s")
0.30778356481481484
"""
str_duration = str(duration)
lst_durations = str_duration.split()
str_duration = " ".join(lst_durations)
numbs = "+-.E0123456789"
kd = -1
kh = -1
km = -1
ks = -1
da = 0
ho = 0
mi = 0
se = 0
sign = 1
k = 0
knum = -1
dur = 0
#print("km={} ks={}".format(km,ks))
for car in str_duration:
if (car == '-') and (knum == -1):
sign = -1
if (car in numbs) and (knum == -1):
knum = k
#print("k={} sign={} car={} knum=={} kd={} kh={} km={} ks={}".format(k,sign,car,knum,kd,kh,km,ks))
if (car=='d' or car=='D') and (kd == -1) and (knum >= 0) :
k1 = knum
k2 = k
str_val = str_duration[k1:k2]
if (self._is_number(str_val) == True):
kd = k1
da = math.fabs(float(str_val))
knum = -1
dur += da
if (car=='h' or car=='H') and (kh == -1) and (knum >= 0) :
k1 = knum
k2 = k
str_val = str_duration[k1:k2]
#print("str_val={}".format(str_val))
if (self._is_number(str_val) == True):
kh = k1
ho = math.fabs(float(str_val))
knum = -1
dur += ho/24.
if ( (car=='m' or car=='M') and (km == -1) ) and (knum >= 0) :
k1 = knum
k2 = k
str_val = str_duration[k1:k2]
#print("str_val={}".format(str_val))
if (self._is_number(str_val) == True):
km = k1
mi = math.fabs(float(str_val))
knum = -1
dur += mi/1440.
if (car=='s' or car=='S') and (ks == -1) and (knum >= 0) :
k1 = knum
k2 = k
str_val = str_duration[k1:k2]
#print("str_val={}".format(str_val))
if (self._is_number(str_val) == True):
ks = k1
se = math.fabs(float(str_val))
knum = -1
dur += se/86400.
k += 1
if (car not in numbs):
knum = -1
#print("END k={} car={} knum=={} numbs={}".format(k,car,knum,numbs))
if (dur==0):
lst_durations = str_duration.split()
#print("(1) lst_durations={}".format(lst_durations))
lst_durations = [ math.fabs(float(x)) for x in lst_durations if (self._is_number(x) == True) ]
#print("(2) lst_durations={}".format(lst_durations))
n_duration = len(lst_durations)
if (n_duration==1):
da = lst_durations[0]
dur = da
elif (n_duration>=2):
se = lst_durations[-1]
dur += se/86400.
mi = lst_durations[-2]
dur += mi/1440.
if (n_duration>=3):
ho = lst_durations[-3]
dur += ho/24.
if (n_duration>=3):
da = lst_durations[-4]
dur += da
dur *= sign
decode = (sign, da, ho, mi, se)
return decode, dur
def _duration_compare(self, duration, operator):
""" Comparaison of durations for various operators.
:param duration: An duration in any supported format (cf. help(Duration))
:type duration: Duration()
:param operator : Operator such as == != > >= < <=
:type operator : string
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(6.12) ; objduration2.duration("5d2m") ; objduration1._duration_compare(objduration2,">")
6.12
'5d2m'
True
.. note:: Does not account for the modulo.
"""
if isinstance(duration, Duration) == False:
duration = Duration(duration)
if self._computed_day == 0:
self.day()
if duration._computed_day == 0:
duration.day()
res = False
if (self._computed_day == 1) and (duration._computed_day == 1):
toeval = str(self._day)+" "+operator+" "+str(duration._day)
res = eval(toeval)
return res
# ========================================================
# === duration methods
# ========================================================
[docs] def duration_duration2day(self,duration) -> typing.Tuple[int, float]:
""" Compute day number from any duration format
:param date: A string formated as "2d7h23m12.5s" or "2 7 23 12.5"
:type date: string
:returns: A tuple of decode, julian day. decode : (sign, da, ho, mi, se)
:rtype: tuple(tuple, float)
:Example:
>>> objduration = Duration()
>>> objduration.duration_duration2day("2d7h23m12.5s")
((1, 2.0, 7.0, 23.0, 12.5), 2.3077835648148146)
.. note:: Prefer using objduration.duration() followed by objduration.day().
"""
decode, day = self._duration2day(duration)
self._computed_day = 1
return decode, day
# ========================================================
# === get/set methods
# ========================================================
[docs] def duration(self, duration=""):
""" Set the input duration in any format
:param duration: duration is a duration in any supported format (cf. help(Duration))
:type duration: any
:returns: The input duration.
:rtype: string
:Example:
>>> objduration = Duration()
>>> objduration.duration("2d7h23m12.5s")
'2d7h23m12.5s'
.. note:: After using objdate.duration() get conversions with methods as objdate.day() or objdate.dhms().
"""
if duration != "":
if (duration != self._init_duration):
self._init(duration)
return self._init_duration
[docs] def day(self):
""" Get the date in julian day format
:returns: The julian day.
:rtype: float
:Example:
>>> objduration = Duration()
>>> objduration.duration("2d7h23m12.5s")
'2d7h23m12.5s'
>>> objduration.day()
2.3077835648148146
.. note:: After using objdate.duration() get conversion with objdate.day().
"""
if (self._computed_day == 0):
init_durationformat, day = self.duration_duration2day(self._init_duration)
self._init_durationformat = init_durationformat
self._computed_day = 1
self._day = day
return self._day
[docs] def dhms(self, dhms_format):
""" Get the date in dhms format
:param dhms_format: dhms format (cf. help(Duration))
:type dhms_format: string
:returns: The formated string.
:rtype: string
:Example:
>>> objduration = Duration()
>>> objduration.duration("2d7h3m12.5s")
'2d7h3m12.5s'
>>> objduration.dhms("0")
'2d07h03m12.50s'
.. note:: After using objdate.duration() get conversion with objdate.dhms().
"""
if (self._computed_day == 0):
self.day()
if (self._computed_dhms == 0) or (self._dhms_format != dhms_format) :
error, dhms = self._day2dhms(self._day,dhms_format)
self._dhms_format = dhms_format
self._dhms = dhms
self._computed_dhms = 1
return self._dhms
# ========================================================
# === debug methods
# ========================================================
[docs] def infos(self, action) -> None:
""" To get informations about this class
:param action: A command to run a debug action (see examples).
:type action: string
:Example:
Duration().infos("doctest")
Duration().infos("doc_methods")
Duration().infos("internal_attributes")
Duration().infos("public_methods")
"""
if (action == "doc_methods"):
publics = [x for x in dir(self) if x[0]!="_"]
for public in publics:
varname = "{}".format(public)
if (callable(getattr(self,varname))==True):
print("\n{:=^40}".format(" method "+varname+" "))
t = "Duration()."+varname+".__doc__"
tt =eval(t)
print(tt)
if (action == "doctest"):
if __name__ == "__main__":
print("\n{:~^40}".format("doctest"))
#doctest.testmod(verbose=True, extraglobs={'objangle': Duration()})
doctest.testmod(verbose=True)
if (action == "internal_attributes"):
internals = [x for x in dir(self) if x[0]=="_" and x[1]!="_"]
for internal in internals:
varname = "{}".format(internal)
#if (hasattr(self,varname)==True):
if (callable(getattr(self,varname))==False):
print(varname + "=" + str(getattr(self,varname)))
if (action == "public_methods"):
publics = [x for x in dir(self) if x[0]!="_"]
for public in publics:
varname = "{}".format(public)
if (callable(getattr(self,varname))==True):
print(varname)
# ========================================================
# === special methods
# ========================================================
def __init__(self, duration=""):
""" Object initialization where duration is the input in any format.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: any
"""
self._init(duration)
[docs] def __add__(self, duration):
""" Add a duration to a duration.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The result of the addition
:rtype: Duration()
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.25) ; objduration2.duration("56d28m") ; objduration = objduration1 + objduration2 ; objduration.dhms("+0.3")
12.25
'56d28m'
'+68d06h28m00.000s'
"""
if isinstance(duration, Duration) == False:
duration = Duration(duration)
res = Duration()
if self._computed_day == 0:
self.day()
if duration._computed_day == 0:
duration.day()
if (self._computed_day == 1) and (duration._computed_day == 1):
day = self._day + duration._day
res = Duration(day)
return res
[docs] def __radd__(self, duration):
""" Right addition a duration to a duration.
"""
return self + duration
[docs] def __iadd__(self, duration):
""" Add a duration to a duration.
"""
return self + duration
[docs] def __sub__(self, duration):
""" Subtract a duration to a duration.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The result of the addition
:rtype: Duration()
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.25) ; objduration2.duration("56d28m") ; objduration = objduration1 - objduration2 ; objduration.dhms("+0.3")
12.25
'56d28m'
'-43d18h28m00.000s'
"""
if isinstance(duration, Duration) == False:
duration = Duration(duration)
res = Duration()
if self._computed_day == 0:
self.day()
if duration._computed_day == 0:
duration.day()
if (self._computed_day == 1) and (duration._computed_day == 1):
day = self._day - duration._day
res = Duration(day)
return res
[docs] def __rsub__(self, duration):
""" Right subtraction a duration to a duration.
"""
if isinstance(duration, Duration) == True:
return self - duration
else:
return duration
[docs] def __isub__(self, duration):
""" Subtract a duration to a duration.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The result of the addition
:rtype: Duration()
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.25) ; objduration2.duration("56d28m") ; objduration1 -= objduration2 ; objduration1.dhms("+0.3")
12.25
'56d28m'
'-43d18h28m00.000s'
"""
return self - duration
[docs] def __eq__(self, duration):
""" Comparaison of durations. Return True if durations are defined and equals.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 == objduration2
12.345
'56d28m'
False
"""
return self._duration_compare(duration, "==")
[docs] def __ne__(self, duration):
""" Comparaison of durations. Return True if durations are defined and not equals.
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 != objduration2
12.345
'56d28m'
True
"""
return self._duration_compare(duration, "!=")
[docs] def __gt__(self, duration):
""" Comparaison of durations. Return True if self > duration
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 > objduration2
12.345
'56d28m'
False
"""
return self._duration_compare(duration, ">")
[docs] def __ge__(self, duration):
""" Comparaison of durations. Return True if self >= duration
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 >= objduration2
12.345
'56d28m'
False
"""
return self._duration_compare(duration, ">=")
[docs] def __lt__(self, duration):
""" Comparaison of durations. Return True if self < duration
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 < objduration2
12.345
'56d28m'
True
"""
return self._duration_compare(duration, "<")
[docs] def __le__(self, duration):
""" Comparaison of durations. Return True if self <= duration
:param duration: A duration in any supported format (cf. help(Duration))
:type duration: Duration()
:returns: The logic result of the comparison.
:rtype: bool
:Example:
>>> objduration1 = Duration()
>>> objduration2 = Duration()
>>> objduration1.duration(12.345) ; objduration2.duration("56d28m") ; objduration1 <= objduration2
12.345
'56d28m'
True
"""
return self._duration_compare(duration, "<=")
[docs] def __mul__(self, multiplier):
""" multiplication of duration by a float or int. Return a duration
:param multiplier: A real number
:type multiplier: float
:returns: A duration
:rtype: Duration()
:Example:
>>> objduration = Duration()
>>> objduration.duration(30.43) ; (objduration*2).day()
30.43
60.86
"""
if isinstance(multiplier, (int, float)) == False:
raise TypeError
return ""
if self._computed_day == 0:
self.day()
durationmult = Duration()
if (self._computed_day == 1):
day = self._day * multiplier
durationmult.duration(day)
return durationmult
[docs] def __rmul__(self, multiplier):
""" Right multiplication of a duration by a float or int.
"""
return self * multiplier
[docs] def __truediv__(self, divisor):
""" division of an angle by a float or int. Return a duration
:param divisor: A real number
:type divisor: float
:returns: A duration
:rtype: Duration()
:Example:
>>> objduration = Duration()
>>> objduration.duration(30.43) ; (objduration/2).day()
30.43
15.215
"""
if isinstance(divisor, (int, float)) == False:
raise TypeError
return ""
if (divisor==0):
raise ZeroDivisionError
if self._computed_day == 0:
self.day()
durationdiv = Duration()
if (self._computed_day == 1):
day = self._day / divisor
durationdiv.duration(day)
return durationdiv