import datetime import math import doctest import typing from typing import Union try: from .durations import Duration except: pass # ======================================================== # ======================================================== # === DATE # ======================================================== # ======================================================== class Date: """ Class to convert dates for astronomy Date formats are: ----------------- now = Now. e.g. "now" jd = Julian day. e.g. 24504527.45678 iso = ISO 8601. e.g. 2018-02-28T12:34:55.23 sql = ISO 8601. e.g. 2018-02-28 12:34:55.23 ymdhms = Calendar. e.g. 2018 2 28 12 34 55.23 equinox = Equinox. e.g. J2000,0 digits = Pure digits e.g. 20180228123455.23 Usage: ------ First, instanciate an object from the class: date = Date() Second, assign a date in any date format: date.date("2018-02-28T12:34:55") Third, get the converted date: jd = date.jd() date = date.date() iso = date.iso() ymdhms = date.ymdhms() equinox = date.equinox() Informations: ------------- All dates are in UTC. help(Date) Date().infos("doctest") Date().infos("doc_methods") """ # ======================================================== # === attributs # ======================================================== _B1850 = 2396758.203 _B1900 = 2415020.3135 _B1950 = 2433282.4235 _B1975 = 2442413.478 _B2000 = 2451544.533 _B2025 = 2460675.588 _B2050 = 2469806.643 _B2100 = 2488068.753 _J1900 = 2415020.0000 _J1950 = 2433282.5000 _J2000 = 2451545.0000 _J2050 = 2469807.5000 _J2100 = 2488070.0000 # ======================================================== # === internal methods # ======================================================== def _init(self,date: Union[str, float] = "") -> None: """ Initialize internal attributes. :param date: date is a date in any supported format (cf. help(Date)) :type date: any :return: None :rtype: None :Example: >>> objdate = Date() >>> objdate._init() """ self._init_date = date self._init_dateformat = 0 self._computed_jd = 0 self._computed_mjd = 0 self._computed_iso = 0 self._computed_iso_nb_subdigit = 3 self._computed_iso_letter = 'T' self._computed_ymdhms = 0 self._computed_digits = 0 self._computed_digits_nb_subdigit = 3 self._computed_equinox = 0 self._computed_equinox_year_type = "J" self._computed_equinox_nb_subdigit = 1 self._jd = 0 self._mjd = 0 self._iso = 0 self._ymdhms = 0 self._equinox = 0 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 :return: 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 _duration2day(self, duration) -> float: """ Return a duration in day unit from a duration in dhms format. :param duration: A string formated as "2d7h23m12.5s" :type duration: string :return: duration expressed in day and fraction of day :rtype: float :Example: >>> objdate = Date() >>> objdate._duration2day("2d7h23m12.5s") 2.3077835648148146 """ duration = str(duration) duration = duration.upper() cars = "+-.E0123456789" units = "DHMS" dur = 0 k1 = -1 k2 = -1 k = 0 div = 0 for car in duration: if car in cars: if k1 == -1: k1 = k k2 = k elif car in units: if (k1>=0 and k2>=0): div = 0 if car=='D': div = 1. elif car=='H': div = 24. elif car=='M': div = 1440. elif car=='S': div = 86400. if div>0: n = float(duration[k1:k2+1]) dur += n/div k1 = -1 k2 = -1 k += 1 if (div==0 and dur==0 and k1>=0 and k2>=k1): n = float(duration[k1:k2+1]) dur += n return dur def _date_compare(self, date, operator): """ Comparaison of dates for various operators. :param date: A date in any supported format (cf. help(Date)) :type date: Date() :param operator : Operator such as == != > >= < <= :type operator : string :return: The logic result of the comparison. :rtype: bool :Example: >>> objdate1 = Date() >>> objdate2 = Date() >>> objdate1.date("2018 02 28"); objdate2.date("2018 02 27"); objdate1._date_compare(objdate2,">") '2018 02 28' '2018 02 27' True .. note:: Does not account for the modulo. """ if self._computed_jd == 0: self.jd() if date._computed_jd == 0: date.jd() res = False if (self._computed_jd == 1) and (date._computed_jd == 1): toeval = str(self._jd)+" "+operator+" "+str(date._jd) res = eval(toeval) return res # ======================================================== # === date methods # ======================================================== def date_date2jd(self,date) -> typing.Tuple[int, float]: """ Compute a julian day from any date format :param date: A string formated as "2d7h23m12.5s" :type date: string :return : A tuple of init_dateformat, julian day. init_dateformat : The identified format of the input: 0 = Error, format not known 1 = Now 2 = Equinox 3 = Sql 4 = Calendar 5 = ISO 8601 6 = Julian day 7 = Modified Julian day 8 = Digits :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_date2jd("2018-02-28T12:34:55.234") (5, 2458178.0242503937) .. note:: Prefer using objdate.date() followed by objdate.jd(). """ # --- First we do not process if date is ever a Date object if (isinstance(date, Date)): return date # --- Decode the date jd = 0 init_dateformat = 0 str_date = str(date).upper() str_split = str_date.split(" ") str_nsplit = len(str_split) if str_date == "NOW": # style NOW utc_datetime = datetime.datetime.utcnow() day = utc_datetime.day + ((((utc_datetime.microsecond*1e-6) + utc_datetime.second)/60. + utc_datetime.minute)/60. + utc_datetime.hour)/24. error, jd = self.date_ymd2jd(utc_datetime.year, utc_datetime.month, day) init_dateformat = 1 elif (str_date[0] is "B") or (str_date[0] is "J"): # style J2000,0 error, jd = self.date_equinox2jd(str_date) init_dateformat = 2 elif (str_nsplit is 2): # style SQL 2018-01-02 23:02:45.456 iso_date = str_split[0] + "T" + str_split[1] error, jd = self.date_iso2jd(iso_date) init_dateformat = 3 elif (str_nsplit is 3): # style calenday 2018 01 02.4567 str_split = [ float(x) for x in str_split] error, jd = self.date_ymd2jd(str_split[0],str_split[1],str_split[2]) init_dateformat = 4 elif (str_nsplit is 4): # style calenday 2018 01 02 05.324 str_split = [ float(x) for x in str_split] error, jd = self.date_ymdhms2jd(str_split[0],str_split[1],str_split[2],str_split[3]) init_dateformat = 4 elif (str_nsplit is 5): # style calenday 2018 01 02 05 12.1809 str_split = [ float(x) for x in str_split] error, jd = self.date_ymdhms2jd(str_split[0],str_split[1],str_split[2],str_split[3],str_split[4]) init_dateformat = 4 elif (str_nsplit is 6): # style calenday 2018 01 02 05 12 56.789 str_split = [ float(x) for x in str_split] error, jd = self.date_ymdhms2jd(str_split[0],str_split[1],str_split[2],str_split[3],str_split[4],str_split[5]) init_dateformat = 4 elif (str_nsplit is 1) and (str_date.find("T") > 6): # style ISO 2018-01-02T23:02:45.456 error, jd = self.date_iso2jd(str_date) init_dateformat = 5 elif (self._is_number(str_date) is True): # style is julian day or MJD # MJD = JD - 2400000.5 (SAO in 1957) # digits as 20180316 jd = float(str_date) init_dateformat = 6 if (math.log10(jd) > 7.2): # --- digits error, jd = self.date_digits2jd(str_date) init_dateformat = 8 elif (math.log10(jd) < 5): # --- MJD jd += 2400000.5 init_dateformat = 7 return init_dateformat, jd def date_iso2jd(self,string): """ Compute a julian day from a ISO8601 date :param string: A string formated in ISO 8601 :type string: string :return : A tuple of error, julian day. error = 0 means no error :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_iso2jd("2018-02-28T12:34:55.234") (0, 2458178.0242503937) First integer is an error code. 0 = no problem. .. note:: Prefer using objdate.date() followed by objdate.jd(). """ error = 0 k1 = string.find("-",1) k2 = string.find("-",k1+1) kt = string.find("T",k2+1) if (kt==-1): kt = string.find("T",k2+1) k3 = string.find(":",kt+1) k4 = string.find(":",k3+1) d=1 hh=0 mm=0 ss=0.0 if (k2 > -1): y = int(string[0:k1]) m = int(string[k1+1:k2]) if (kt == -1): d = float(string[k2+1:]) else: d = int(string[k2+1:kt]) if (k3 > -1): hh = int(string[kt+1:k3]) d += hh/24. if (k4 == -1): mm = float(string[k3+1:]) d += mm/1440. else: mm = int(string[k3+1:k4]) d += mm/1440. ss = float(string[k4+1:]) d += ss/86400. error, jd = self.date_ymd2jd(y,m,d) else: # pb format error = 1 return error, jd def date_jd2mjd(self, jd): """ Compute modified julian day from julian day :param jd: A julian day :return : A tuple of error, string of a date formatted into ISO8601. error = 0 means no error. :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_jd2mjd(2458178.0242503937) .. note:: Prefer using objdate.date() followed by objdate.mjd() """ error = 0 res = jd - 2400000.5 return error, res def date_jd2iso(self, jd, nb_subdigit=3, letter='T'): """ Compute a ISO8601 date from a julian day :param jd: A julian day :type jd: float :param nb_subdigit : The number of digit returned after the seconds. :type nb_subdigit: int :param letter : The letter to separe date end time. If letter is "" then the output format sticks all digits without any characters :-T. So the format is no longer ISO but useful for a pure digit code. :type letter: int :return : A tuple of error, string of a date formatted into ISO8601. error = 0 means no error. :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_jd2iso(2458178.0242503937) (0, '2018-02-28T12:34:55.234') .. note:: Prefer using objdate.date() followed by objdate.iso() """ error, y, m, d, hh, mm, ss = self.date_jd2ymdhms(jd) nb_prefixdigit = nb_subdigit+3 if (nb_subdigit < 0): nb_subdigit = 0 if (nb_subdigit == 0): nb_prefixdigit -= 1 if (letter==''): fstring = "{:04d}{:02d}{:02d}{}{:02d}{:02d}{:0"+str(nb_prefixdigit)+"."+str(nb_subdigit)+"f}" else: fstring = "{:04d}-{:02d}-{:02d}{}{:02d}:{:02d}:{:0"+str(nb_prefixdigit)+"."+str(nb_subdigit)+"f}" res = fstring.format(y, m, d, letter, hh, mm, ss) return error, res def date_equinox2jd(self,string): """ Compute a julian day from a equinoxal date :param string: date string is a string in Equinox format (cf. help(Date)) :type string: string :return : A tuple of error, julian day. error = 0 means no error :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_equinox2jd("J2025,0") (0, 2460676.25) .. note:: Prefer using objdate.date() followed by objdate.jd() """ error = 0 t = string[0] s = string[1:].replace(',','.') a = float(s) jd = 0 if t is "J": if (a == 1900): jd = self._J1900 elif (a == 1950): jd = self._J1950 elif (a == 2000): jd = self._J2000 elif (a == 2050): jd = self._J2050 elif (a == 2100): jd = self._J2100 else: jd = 2451545.0+(a-2000.0)*365.25; elif t is "B": if (a == 1850): jd = self._B1850 elif (a == 1900): jd = self._B1900 elif (a == 1950): jd = self._B1950 elif (a == 1975): jd = self._B1975 elif (a == 2000): jd = self._B2000 elif (a == 2025): jd = self._B2025 elif (a == 2050): jd = self._B2050 elif (a == 2100): jd = self._B2100 else: error = 2 return error,jd def date_digits2jd(self,string): """ Compute a julian day from a date with only digits :param string: A string formated in digits :type string: string :return : A tuple of error, julian day. error = 0 means no error :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_iso2jd("2018-02-28T12:34:55.234") (0, 2458178.0242503937) First integer is an error code. 0 = no problem. .. note:: Prefer using objdate.date() followed by objdate.digits(). """ error = 0 d=1 hh=0 mm=0 ss=0.0 if len(string)>=4: y = int(string[0:4]) else: y = int(string) if len(string)>=6: m = int(string[4:6]) else: m = 1 if len(string)>=8: d = int(string[6:8]) else: d = 1 if len(string)>=10: hh = int(string[8:10]) else: hh = 0 if len(string)>=12: mm = int(string[10:12]) else: mm = 0 if len(string)>12: ss = float(string[12:]) else: ss = 0 d += hh/24. d += mm/1440. d += ss/86400. error, jd = self.date_ymd2jd(y,m,d) return error, jd def date_jd2digits(self, jd, nb_subdigit=3): """ Compute a date only with digits from a julian day :param jd: A julian day :type jd: float :param nb_subdigit : The number of digit returned after the seconds. :type nb_subdigit: int :return : A tuple of error, string of a date formatted into ISO8601. error = 0 means no error. :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_jd2iso(2458178.0242503937) (0, '2018-02-28T12:34:55.234') .. note:: Prefer using objdate.date() followed by objdate.digits() """ return self.date_jd2iso(jd,nb_subdigit,"") def date_jd2equinox(self,jd,year_type="",nb_subdigit=1): """ Compute an equinoxal date from a julian day :param jd: A julian day :type jd: float :param year_type: "B" (Bessel) or "J" (Julian) or "" for automatic choice :type year_type: string :param nb_subdigit : The number of digits returned after the year. :type nb_subdigit: int :return : A tuple of error, string of a date formatted into ISO8601. error = 0 means no error. :rtype: tuple(int, float) :Example: >>> objdate = Date() >>> objdate.date_jd2equinox(2458178.0242503937) (0, 'J2018.2') .. note:: Prefer using objdate.date() followed by objdate.equinox() """ error = 0 eps=1e-3 if (year_type==""): year_type="J" if ((math.fabs(jd-2415020.3135)0): fstring = "{:."+str(nb_subdigit)+"f}" equinox = chaine0 + fstring.format(a) else: equinox = chaine0 + "{:.0f}".format(a) return error, equinox def date_ymdhms2jd(self, y, m, d, hh=0, mm=0, ss=0): """ Compute a julian day from a calendar date Inputs: ------- y = year m = month d = day hh = hour mm = minutes ss = seconds N.B. d can include fraction of day of hh, mm, ss are not used. Usage: ------ >>> objdate = Date() >>> objdate.date_ymdhms2jd(2017,3,12,0,23,12.34) (0, 2457824.516115046) First integer is an error code. 0 = no problem. Related topics: --------------- Prefer using objdate.date() followed by objdate.jd() """ d += ( hh + ( mm + ss/60.) /60.) /24.; error, jd = self.date_ymd2jd(y, m, d) return error, jd def date_ymd2jd(self,year, month, day): """ Compute a julian day from a calendar date Inputs: ------- year = year month = month day = day and fraction of day Usage: ------ >>> objdate = Date() >>> objdate.date_ymd2jd(2017,3,12.64232) (0, 2457825.14232) First integer is an error code. 0 = no problem. Related topics: --------------- Prefer using objdate.date() followed by objdate.jd() """ error = 0 a=year; m=month; j=day; if m <= 2: a=a-1 m=m+12 aa=math.floor(a/100) bb=2-aa+math.floor(aa/4) jd=math.floor(365.25*(a+4716))+math.floor(30.6001*(m+1))+bb-1524.5 jd=jd+j; if (jd<2299160.5) : jd=math.floor(365.25*(a+4716))+math.floor(30.6001*(m+1))-1524.5; jd=jd+j return error,jd def date_jd2ymd(self,jd): """ Compute a calendar date from a julian day Inputs: ------- jd is a julian day Usage: ------ >>> objdate = Date() >>> objdate.date_jd2ymd(2457825.14232) (0, 2017, 3, 12.64232000010088) First integer is an error code. 0 = no problem. Following items are: year = year month = month day = day and fraction of day Related topics: --------------- Prefer using objdate.date() followed by objdate.ymdhms() """ error=0; jd+=.5 z=math.floor(jd) f=jd-z if (z<2299161.): a=z else: alpha=math.floor((z-1867216.25)/36524.25) a=z+1+alpha-math.floor(alpha/4) b=a+1524 c=math.floor(((b-122.1)/365.25)) d=math.floor(365.25*c) e=math.floor((b-d)/30.6001) d=b-d-math.floor(30.6001*e)+f if e<14: m = (int)(e-1) else: m = (int)(e-13) if m>2: y = (int)(c-4716) else: y = (int)(c-4715) return error, y, m, d def date_jd2ymdhms(self,jd): """ Compute a calendar date from a julian day Inputs: ------- jd is a julian day Usage: ------ >>> objdate = Date() >>> objdate.date_jd2ymdhms(2457824.516115046) (0, 2017, 3, 12, 0, 23, 12.339983582496643) First integer is an error code. 0 = no problem. Following items are: y = year m = month d = day hh = hour mm = minutes ss = seconds Related topics: --------------- Prefer using objdate.date() followed by objdate.ymdhms() """ error, y, m, day = self.date_jd2ymd(jd) d = int(math.floor(day)) hh = 0 mm = 0 ss = 0 if (error==0): r = (day-d)*24 hh = int(math.floor(r)) r = (r-hh)*60 mm = int(math.floor(r)) ss = (r-mm)*60 return error, y, m, d, hh, mm, ss # ======================================================== # === get/set methods # ======================================================== def date(self, date=""): """ Set the input date in any format :param date: date is a date in any supported format (cf. help(Date)) :type date: any :return : The input date. :rtype: string :Example: >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' .. note:: After using objdate.date() get conversions with methods as objdate.jd() or objdate.iso(). """ if isinstance(date, Date) == False: if date != "": if isinstance(date, str) == True: dt = date.upper() else: dt = "" if (date != self._init_date) or (dt == "NOW"): self._init(date) else: self = date return self._init_date def jd(self): """ Get the date in julian day format :return : The julian day. :rtype: float :Example: >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.jd() 2458178.0242503937 .. note:: Before use objdate.date() to set the input date. """ if (self._computed_jd == 0): init_dateformat, jd = self.date_date2jd(self._init_date) if (init_dateformat > 0): self._init_dateformat = init_dateformat self._computed_jd = 1 self._jd = jd return self._jd return -1 return self._jd def iso(self, nb_subdigit=3,letter='T'): """ Get the date in ISO 8601 format Inputs: ------- nb_subdigit is the number of digit returned after the seconds. Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.iso(2) '2018-02-28T12:34:55.23' Related topics: --------------- Before use objdate.date() to set the input date. """ if (self._computed_iso == 0) or (self._computed_iso_nb_subdigit != nb_subdigit) or (self._computed_iso_letter != letter): if (self._computed_jd == 0): self.jd() if (self._init_dateformat > 0): error, iso = self.date_jd2iso(self._jd, nb_subdigit, letter) if error==0: self._computed_iso = 1 self._iso = iso self._computed_iso_nb_subdigit = nb_subdigit self._computed_iso_letter = letter return self._iso return -1 return self._iso def mjd(self): """ Get the date in modified julian day format :return : The modified julian day. :rtype: float :Example: >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.mjd() 2458178.0242503937 .. note:: Before use objdate.date() to set the input date. """ if (self._computed_mjd == 0): if (self._computed_jd == 0): self.jd() error, mjd = self.date_jd2mjd(self._jd) if error==0: self._computed_mjd = 1 self._mjd = mjd return self._mjd return -1 return self._mjd def ymdhms(self): """ Get the date in ymdhms format Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.ymdhms() [2018, 2, 28, 12, 34, 55.23401856422424] Related topics: --------------- Before use objdate.date() to set the input date. """ if (self._computed_ymdhms == 0): if (self._computed_jd == 0): self.jd() if (self._init_dateformat > 0): error, *ymdhms = self.date_jd2ymdhms(self._jd) if error==0: self._computed_ymdhms = 1 self._ymdhms = ymdhms return self._ymdhms return -1 return self._ymdhms def digits(self, nb_subdigit=3): """ Get the date in digits format Inputs: ------- nb_subdigit is the number of digit returned after the seconds. Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.iso(2) '2018-02-28T12:34:55.23' Related topics: --------------- Before use objdate.date() to set the input date. """ if (self._computed_digits == 0) or (self._computed_digits_nb_subdigit != nb_subdigit): if (self._computed_jd == 0): self.jd() if (self._init_dateformat > 0): error, digits = self.date_jd2digits(self._jd, nb_subdigit) if error==0: self._computed_digits = 1 self._digits = digits self._computed_digits_nb_subdigit = nb_subdigit return self._digits return -1 return self._digits def equinox(self, year_type="J", nb_subdigit=1): """ Get the date in equinox format Inputs: ------- year_type = "B" (Bessel) or "J" (Julian) or "" for automatic choice nb_subdigit is the number of digit returned after the year. Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") '2018-02-28T12:34:55.234' >>> objdate.equinox() 'J2018.2' Related topics: --------------- Before use objdate.date() to set the input date. """ if (self._computed_equinox == 0) or (self._computed_equinox_year_type != year_type) or (self._computed_equinox_nb_subdigit != nb_subdigit): if (self._computed_jd == 0): self.jd() if (self._init_dateformat > 0): error, equinox = self.date_jd2equinox(self._jd,year_type,nb_subdigit) if error==0: self._computed_equinox = 1 self._equinox = equinox self._computed_equinox_year_type = year_type self._computed_equinox_nb_subdigit = nb_subdigit return self._equinox return -1 return self._equinox # ======================================================== # === debug methods # ======================================================== def infos(self, action): """ To get informations about this class :param action: A command to run a debug action (see examples). :type action: string :Example: Date().infos("doctest") Date().infos("doc_methods") Date().infos("internal_attributes") Date().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 = "Date()."+varname+".__doc__" tt =eval(t) print(tt) if (action == "doctest"): if __name__ == "__main__": print("\n{:~^40}".format("doctest")) #doctest.testmod(verbose=True, extraglobs={'objdate': Date()}) 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, date=""): """ Object initialization where date is the input in any format Inputs: ------- date is a date in any supported format (cf. help(Date)) """ self._init(date) def __add__(self, duration): """ Add a duration to a date Inputs: ------- duration in dhms format (e.g 3d20h5m3s) Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") ; date = objdate + "12m45s" ; date.iso() '2018-02-28T12:34:55.234' '2018-02-28T12:47:40.234' """ if self._computed_jd == 0: self.jd() if self._computed_jd == 1: jd = self._jd else: return Date() duration = Duration(duration) day = duration.day() jd += day return Date(jd) def __radd__(self, duration): """ Right addition a duration to a date """ return self + duration def __iadd__(self, duration): """ Add a duration to a date """ return self + duration def __sub__(self, object_or_duration: Union["Date", float]) -> "Date": """ Subtract a duration to a date (returns an object date) or Compute the duration between two date objects. Inputs: ------- object_or_duration: Duration in dhms format (e.g 3d20h5m3s) or object_or_duration: Date object Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") ; date = objdate - "12m45s" ; date.iso() '2018-02-28T12:34:55.234' '2018-02-28T12:22:10.234' >>> objdate.date("2018-02-28T12:34:55.234") ; objdate2 = Date("2018-02-25T12:34:55.234") ; days = objdate - objdate2 ; print(days) '2018-02-28T12:34:55.234' 3.0 """ res = 0 if self._computed_jd == 0: self.jd() if isinstance(object_or_duration, Date) == True: date = object_or_duration if date._computed_jd == 0: date.jd() if (self._computed_jd == 1) and (date._computed_jd == 1): res = self._jd - date._jd else: duration = object_or_duration if self._computed_jd == 1: jd = self._jd else: return Date() duration = Duration(duration) day = duration.day() jd -= day return Date(jd) return res def __rsub__(self, date): """ Right subtraction only for a date to another date """ if isinstance(date, Date) == True: return self - date else: return date def __isub__(self, duration): """ Subtract a duration to a date Inputs: ------- duration in dhms format (e.g 3d20h5m3s) Usage: ------ >>> objdate = Date() >>> objdate.date("2018-02-28T12:34:55.234") ; objdate -= "12m45s" ; objdate.iso() '2018-02-28T12:34:55.234' '2018-02-28T12:22:10.234' """ return self - duration def __eq__(self, date): """ Comparaison of dates. Return True if dates are defined and equals. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 28"); objdate2 = Date("2018 02 28"); objdate == objdate2 '2018 02 28' True """ return self._date_compare( date, "==") def __ne__(self, date): """ Comparaison of dates. Return True if dates are defined and not equals. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 28"); objdate2 = Date("2018 02 28"); objdate != objdate2 '2018 02 28' False """ return self._date_compare( date, "!=") def __gt__(self, date): """ Comparaison of dates: date1 > date 2. Return True if dates are defined and date1 > date 2. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 28"); objdate2 = Date("2018 02 27"); objdate > objdate2 '2018 02 28' True """ return self._date_compare( date, ">") def __ge__(self, date): """ Comparaison of dates: date1 >= date 2. Return True if dates are defined and date1 >= date 2. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 28"); objdate2 = Date("2018 02 27"); objdate >= objdate2 '2018 02 28' True """ return self._date_compare( date, ">=") def __lt__(self, date): """ Comparaison of dates: date1 < date 2. Return True if dates are defined and date1 < date 2. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 26"); objdate2 = Date("2018 02 27"); objdate < objdate2 '2018 02 26' True """ return self._date_compare( date, "<") def __le__(self, date): """ Comparaison of dates: date1 <= date 2. Return True if dates are defined and date1 <= date 2. Inputs: ------- date: An object instancied on Date Usage: ------ >>> objdate = Date() >>> objdate.date("2018 02 26"); objdate2 = Date("2018 02 27"); objdate <= objdate2 '2018 02 26' True """ return self._date_compare( date, "<=") # ======================================================== # ======================================================== # ======================================================== # Examples of execution if __name__ == "__main__": kex = 0 print("==========================") print("=== Self documentation ===") print("==========================") kex+=1; print("\n=== Example {}: Simple conversion ISO 8601".format(kex)) date_inp = "now" date = Date(date_inp) print("{} = {} (ISO8601 UTC at milliseconds)".format(date_inp,date.iso())) print("{} = {} (ISO8601 UTC at seconds)".format(date_inp,date.iso(0))) kex+=1; print("\n=== Example {}: Simple conversion to Julian day".format(kex)) date_inp = "now" date = Date(date_inp) print("{} = {}".format(date_inp,date.jd())) print("{} = {} (Modfied Julian day)".format(date_inp,date.mjd())) kex+=1; print("\n=== Example {}: Addition of date + duration".format(kex)) date_inp1 = "now" duration_inp2 = "2d28m12.4s" a = Date(date_inp1) b = Duration(duration_inp2) c = a + b print("{} + {} = {}".format(date_inp1,duration_inp2,c.iso(2))) cl = "Date" print("\n=== To perform unitary tests: {}().infos(\"doctest\")".format(cl)) print("=== To list docs of all methods: {}().infos(\"doc_methods\")".format(cl)) print("\n=== List of public methods using {}().infos(\"public_methods\"):".format(cl)) Date().infos("public_methods")