Source code for pymeeus.Epoch

# -*- coding: utf-8 -*-


# PyMeeus: Python module implementing astronomical algorithms.
# Copyright (C) 2018  Dagoberto Salazar
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


import calendar
import datetime
from base import TOL, get_ordinal_suffix, INT


"""
.. module:: Epoch
   :synopsis: Class to handle time
   :license: GNU Lesser General Public License v3 (LGPLv3)

.. moduleauthor:: Dagoberto Salazar
"""


DAY2SEC = 86400.0
"""Number of seconds per day"""

DAY2MIN = 1440.0
"""Number of minutes per day"""

DAY2HOURS = 24.0
"""Number of hours per day"""

LEAP_TABLE = {1972.5: 1, 1973.0: 2, 1974.0: 3, 1975.0: 4, 1976.0: 5,
              1977.0: 6, 1978.0: 7, 1979.0: 8, 1980.0: 9, 1981.5: 10,
              1982.5: 11, 1983.5: 12, 1985.5: 13, 1988.0: 14, 1990.0: 15,
              1991.0: 16, 1992.5: 17, 1993.5: 18, 1994.5: 19, 1996.0: 20,
              1997.5: 21, 1999.0: 22, 2006.0: 23, 2009.0: 24, 2012.5: 25,
              2015.5: 26, 2017.0: 27}
"""This table represents the point in time FROM WHERE the given number of leap
seconds is valid. Given that leap seconds are (so far) always added at
June 30th or December 31st, a leap second added in 1997/06/30 is represented
here as '1997.5', while a leap second added in 2005/12/31 appears here as
'2006.0'."""


[docs]class Epoch(object): """ Class Epoch deals with the tasks related to time handling. The constructor takes either a single JDE value, or a series of values representing year, month, day, hours, minutes, seconds. This series of values is supposed to be in UTC time (civil time). It is also possible to provide another Epoch object as input of the constructor. When a UTC time is provided, it is converted to International Atomic Time (TAI) using an internal table of leap seconds, and from there, it is converted to (and stored as) Terrestrial Time (TT). Given that leap seconds are added or subtracted in an irregular basis, it is not possible to predict them in advance, and the internal leap second table will become outdated at some point in time. To counter this, you have two options: - Download an updated version of this Pymeeus package. - Use the argument **leap_seconds** in the constructor or :meth:`set` method to provide the correct number of leap seconds (w.r.t. TAI) to be applied. For instance, if at some time in the future the TAI-UTC difference is 43 seconds, you should set **leap_seconds=43** if you don't have an updated version of this class. In order to know which is the most updated leap second value stored in this class, you may use the :meth:`get_last_leap_second()` method. The UTC to TT correction is done by default, but you may disable it by setting **leap_seconds=0**. In that case, it is supposed that the input data is already in TT scale. .. note:: As said above, UTC to TT correction is done by default, but the current version of UTC was implemented in January 1st, 1972. Therefore, for dates before the correction in NOT carried out, and it is supposed that the input data is already in TT scale. .. note:: For conversions between TT and Universal Time (UT), please use the method :meth:`tt2ut`. .. note:: Internally, time values are stored as a Julian Ephemeris Day (JDE), based on the uniform scale of Dynamical Time, or morei specifically, Terrestial Time (TT) (itself the redefinition of Terrestrial Dynamical Time, TDT). .. note:: The UTC-TT conversion is composed of three corrections: a. TT-TAI, composed of 32.184 s, b. TAI-UTC(1972), 10 s, and c. UTC(1972)-UTC(Now) which is the current amount of leap seconds. When you do, for instance, **leap_seconds=43**, you modify the c. part, while when you do *leap_seconds=0.0*, you disable the three corrections. .. note:: Given that this class stores the epoch as JDE, if the JDE value is in the order of millions of days then, for a computer with 15-digit accuracy, the final time resolution is about 10 milliseconds. That is considered enough for most applications of this class. """
[docs] def __init__(self, *args, **kwargs): """Epoch constructor. This constructor takes either a single JDE value, or a series of values representing year, month, day, hours, minutes, seconds. This series of values is supposed to be in UTC time (civil time). It is also possible to provide another Epoch object as input for the constructor, or the year, month, etc. arguments can be provided in a tuple or list. Moreover, it is also possible provide :class:`date` or :class:`datetime` objects for initialization. The **month** value can be provided as an integer (1 = January, 2 = February, etc), or it can be provided as short (Jan, Feb, ...) or long (January, February, ...) names. Also, hours, minutes, seconds can be provided separately, or as decimals of the day value. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. On the other hand, if it is set to zero, then the UTC to TT correction is disabled, and it is supposed that the input data is already in TT scale. :param \*args: Either JDE, Epoch, date, datetime or year, month, day, hours, minutes, seconds values, by themselves or inside a tuple or list :type \*args: int, float, :py:class:`Epoch`, tuple, list, date, datetime :param leap_seconds: If different from zero, this is the value to be used in the UTC->TAI conversion. If equals to zero, conversion is disabled. :type leap_seconds: int, float :returns: Epoch object. :rtype: :py:class:`Epoch` :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> e = Epoch(1987, 6, 19.5, leap_seconds=0.0) >>> print(e) 2446966.0 """ # Initialize field self._jde = 0.0 self.set(*args, **kwargs) # Use 'set()' method to handle the setup
[docs] def set(self, *args, **kwargs): """Method used to set the value of this object. This method takes either a single JDE value, or a series of values representing year, month, day, hours, minutes, seconds. This series of values is supposed to be in UTC time (civil time). It is also possible to provide another Epoch object as input for the :meth:`set` method, or the year, month, etc arguments can be provided in a tuple or list. Moreover, it is also possible provide :class:`date` or :class:`datetime` objects for initialization. The **month** value can be provided as an integer (1 = January, 2 = February, etc), or it can be provided as short (Jan, Feb, ...) or long (January, February, ...) names. Also, hours, minutes, seconds can be provided separately, or as decimals of the day value. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. On the other hand, if it is set to zero, then the UTC to TT correction is disabled, and it is supposed that the input data is already in TT scale. .. note:: The UTC to TT correction is only carried out for dates after January 1st, 1972. :param \*args: Either JDE, Epoch, date, datetime or year, month, day, hours, minutes, seconds values, by themselves or inside a tuple or list :type \*args: int, float, :py:class:`Epoch`, tuple, list, date, datetime :param leap_seconds: If different from zero, this is the value to be used in the UTC->TAI conversion. If equals to zero, conversion is disabled. If not given, UTC to TT conversion is carried out (default). :type leap_seconds: int, float :returns: None. :rtype: None :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> e = Epoch() >>> e.set(1987, 6, 19.5, leap_seconds=0.0) >>> print(e) 2446966.0 >>> e.set(1977, 'Apr', 26.4, leap_seconds=0.0) >>> print(e) 2443259.9 >>> e.set(1957, 'October', 4.81, leap_seconds=0.0) >>> print(e) 2436116.31 >>> e.set(333, 'Jan', 27, 12, leap_seconds=0.0) >>> print(e) 1842713.0 >>> e.set(1900, 'Jan', 1, leap_seconds=0.0) >>> print(e) 2415020.5 >>> e.set(-1001, 'august', 17.9, leap_seconds=0.0) >>> print(e) 1355671.4 >>> e.set(-4712, 1, 1.5, leap_seconds=0.0) >>> print(e) 0.0 >>> e.set((1600, 12, 31), leap_seconds=0.0) >>> print(e) 2305812.5 >>> e.set([1988, 'JUN', 19, 12], leap_seconds=0.0) >>> print(e) 2447332.0 >>> d = datetime.date(2000, 1, 1) >>> e.set(d, leap_seconds=0.0) >>> print(e) 2451544.5 >>> e.set(837, 'Apr', 10, 7, 12, leap_seconds=0.0) >>> print(e) 2026871.8 >>> d = datetime.datetime(837, 4, 10, 7, 12, 0, 0) >>> e.set(d, leap_seconds=0.0) >>> print(e) 2026871.8 """ # Clean up the internal parameters self._jde = 0.0 # If no arguments are given, return. Internal values are 0.0 if len(args) == 0: return # If we have only one argument, it can be a JDE or another Epoch object elif len(args) == 1: if isinstance(args[0], Epoch): self._jde = args[0]._jde return elif isinstance(args[0], (int, float)): self._jde = args[0] return elif isinstance(args[0], (tuple, list)): year, month, day, hours, minutes, sec = \ self._check_values(*args[0]) elif isinstance(args[0], datetime.datetime): d = args[0] year, month, day, hours, minutes, sec = \ self._check_values(d.year, d.month, d.day, d.hour, d.minute, d.second + d.microsecond/1e6) elif isinstance(args[0], datetime.date): d = args[0] year, month, day, hours, minutes, sec = \ self._check_values(d.year, d.month, d.day) else: raise TypeError("Invalid input type") elif len(args) == 2: # Insuficient data to set the Epoch raise ValueError("Invalid number of input values") elif len(args) >= 3: # Year, month, day year, month, day, hours, minutes, sec = self._check_values(*args) day += hours/DAY2HOURS + minutes/DAY2MIN + sec/DAY2SEC # Handle the 'leap_seconds' argument, if pressent if 'leap_seconds' in kwargs: # Compute JDE self._jde = self._compute_jde(year, month, day, utc2tt=False, leap_seconds=kwargs['leap_seconds']) else: self._jde = self._compute_jde(year, month, day)
def _compute_jde(self, y, m, d, utc2tt=True, leap_seconds=0.0): """Method to compute the Julian Ephemeris Day (JDE). .. note:: The UTC to TT correction is only carried out for dates after January 1st, 1972. :param y: Year :type y: int :param m: Month :type m: int :param d: Day :type d: float :param utc2tt: Whether correction UTC to TT is done automatically. :type utc2tt: bool :param leap_seconds: Number of leap seconds to apply :type leap_seconds: float :returns: Julian Ephemeris Day (JDE) :rtype: float """ # The best approach here is first convert to JDE, and then adjust secs if m <= 2: y -= 1 m += 12 a = INT(y/100.0) b = 0.0 if not Epoch.is_julian(y, m, INT(d)): b = 2.0 - a + INT(a/4.0) jde = INT(365.25*(y + 4716.0)) + INT(30.6001*(m + 1.0)) + \ d + b - 1524.5 # If enabled, let's convert from UTC to TT, adding the needed seconds deltasec = 0.0 # In this case, UTC to TT correction is applied automatically if utc2tt: if y >= 1972: deltasec = 32.184 # Difference between TT and TAI deltasec += 10.0 # Difference between UTC and TAI in 1972 deltasec += Epoch.leap_seconds(y, m) else: # Correction is NOT automatic if leap_seconds != 0.0: # We apply provided leap seconds if y >= 1972: deltasec = 32.184 # Difference between TT and TAI deltasec += 10.0 # Difference between UTC-TAI in 1972 deltasec += leap_seconds return jde + deltasec/DAY2SEC def _check_values(self, *args): """This method takes the input arguments to 'set()' method (year, month, day, etc) and carries out some sanity checks on them. It returns a tuple containing those values separately, assigning zeros to those arguments which were not provided. :param \*args: Year, month, day, hours, minutes, seconds values. :type \*args: int, float :returns: Tuple with year, month, day, hours, minutes, seconds values. :rtype: tuple :raises: ValueError if input values are in the wrong range, or too few arguments given as input. """ # This list holds the maximum amount of days a given month can have maxdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # Initialize some variables year = -9999 month = -9999 day = -9999 hours = 0.0 minutes = 0.0 sec = 0.0 # Carry out some basic checks if len(args) < 3: raise ValueError("Invalid number of input values") elif len(args) >= 3: # Year, month, day year = args[0] month = args[1] day = args[2] if len(args) >= 4: # Year, month, day, hour hours = args[3] if len(args) >= 5: # Year, month, day, hour, minutes minutes = args[4] if len(args) >= 6: # Year, month, day, hour, minutes, seconds sec = args[5] if year < -4712: # No negative JDE will be allowed raise ValueError("Invalid value for the input year") if day < 1 or day > 31: raise ValueError("Invalid value for the input day") if hours < 0 or hours > 23: raise ValueError("Invalid value for the input hours") if minutes < 0 or minutes > 59: raise ValueError("Invalid value for the input minutes") if sec < 0 or sec > 59: raise ValueError("Invalid value for the input seconds") # Test the days according to the month month = Epoch.get_month(month) limit_day = maxdays[month - 1] # We need extra tests if month is '2' (February) if month == 2: if Epoch.is_leap(year): limit_day = 29 if day > limit_day: raise ValueError("Invalid value for the input day") # We are ready to return the parameters return year, month, day, hours, minutes, sec
[docs] @staticmethod def is_julian(year, month, day): """This method returns True if given date is in the Julian calendar. :param year: Year :type y: int :param month: Month :type m: int :param day: Day :type day: int :returns: Whether the provided date belongs to Julian calendar or not. :rtype: bool >>> Epoch.is_julian(1997, 5, 27.1) False >>> Epoch.is_julian(1397, 7, 7.0) True """ if (year < 1582) or (year == 1582 and month < 10) or \ (year == 1582 and month == 10 and day < 5.0): return True else: return False
[docs] @staticmethod def get_month(month, as_string=False): """Method to get the month as a integer in the [1, 12] range, or as a full name. :param month: Month, in numeric, short name or long name format :type month: int, float, str :param as_string: Whether the output will be numeric, or a long name. :type as_string: bool :returns: Month as integer in the [1, 12] range, or as a long name. :rtype: int, str :raises: ValueError if input month value is invalid. >>> Epoch.get_month(4.0) 4 >>> Epoch.get_month('Oct') 10 >>> Epoch.get_month('FEB') 2 >>> Epoch.get_month('August') 8 >>> Epoch.get_month('NOVEMBER') 11 >>> Epoch.get_month(9.0, as_string=True) 'September' >>> Epoch.get_month('Feb', as_string=True) 'February' >>> Epoch.get_month('March', as_string=True) 'March' """ months_mmm = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] months_full = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] if isinstance(month, (int, float)): month = int(month) # Truncate if it has decimals if month >= 1 and month <= 12: if not as_string: return month else: return months_full[month - 1] else: raise ValueError("Invalid value for the input month") elif isinstance(month, str): month = month.strip().capitalize() if len(month) == 3: if month in months_mmm: if not as_string: return (months_mmm.index(month) + 1) else: return months_full[months_mmm.index(month)] else: raise ValueError("Invalid value for the input month") else: if month in months_full: if not as_string: return (months_full.index(month) + 1) else: return month else: raise ValueError("Invalid value for the input month")
[docs] @staticmethod def is_leap(year): """Method to check if a given year is a leap year. :param year: Year to be checked. :type year: int, float :returns: Whether or not year is a leap year. :rtype: bool :raises: ValueError if input year value is invalid. >>> Epoch.is_leap(2003) False >>> Epoch.is_leap(2012) True >>> Epoch.is_leap(1900) False >>> Epoch.is_leap(-1000) True >>> Epoch.is_leap(1000) True """ if isinstance(year, (int, float)): # Mind the difference between Julian and Gregorian calendars if year >= 1582: year = INT(year) return calendar.isleap(year) else: return (abs(year) % 4) == 0 else: raise ValueError("Invalid value for the input year")
[docs] @staticmethod def getDOY(YYYY, MM, DD): """This method returns the Day Of Year (DOY) for the given date. :param YYYY: Year, in four digits format :type YYYY: int, float :param MM: Month, in numeric format (1 = January, 2 = February, etc) :type MM: int, float :param DD: Day, in numeric format :type DD: int, float :returns: Day Of Year (DOY). :rtype: float :raises: ValueError if input values correspond to a wrong date. >>> Epoch.getDOY(1999, 1, 29) 29.0 >>> Epoch.getDOY(1978, 11, 14) 318.0 >>> Epoch.getDOY(2017, 12, 31.7) 365.7 >>> Epoch.getDOY(2012, 3, 3.1) 63.1 >>> Epoch.getDOY(-400, 2, 29.9) 60.9 """ # Let's carry out first some basic checks if DD < 1 or DD >= 32 or MM < 1 or MM > 12: raise ValueError("Invalid input data") day = int(DD) frac = DD % 1 if YYYY >= 1: # datetime's minimum year is 1 try: d = datetime.date(YYYY, MM, day) except ValueError: raise ValueError("Invalid input date") doy = d.timetuple().tm_yday else: k = 2 if Epoch.is_leap(YYYY) else 1 doy = INT((275.0*MM)/9.0) - k*INT((MM + 9.0)/12.0) + day - 30.0 return float(doy + frac)
[docs] @staticmethod def doy2date(year, doy): """This method takes a year and a Day Of Year values, and returns the corresponding date. :param year: Year, in four digits format :type year: int, float :param doy: Day of Year number :type doy: int, float :returns: Year, month, day. :rtype: tuple :raises: ValueError if either input year or doy values are invalid. >>> t = Epoch.doy2date(1999, 29) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) 1999/1/29.0 >>> t = Epoch.doy2date(2017, 365.7) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) 2017/12/31.7 >>> t = Epoch.doy2date(2012, 63.1) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) 2012/3/3.1 >>> t = Epoch.doy2date(-1004, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -1004/2/29.0 >>> t = Epoch.doy2date(0, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) 0/2/29.0 >>> t = Epoch.doy2date(1, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) 1/3/1.0 >>> t = Epoch.doy2date(-1, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -1/3/1.0 >>> t = Epoch.doy2date(-2, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -2/3/1.0 >>> t = Epoch.doy2date(-3, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -3/3/1.0 >>> t = Epoch.doy2date(-4, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -4/2/29.0 >>> t = Epoch.doy2date(-5, 60) >>> print("{}/{}/{}".format(t[0], t[1], round(t[2], 1))) -5/3/1.0 """ if isinstance(year, (int, float)) and isinstance(doy, (int, float)): frac = float(doy % 1) doy = int(doy) if year >= 1: # datetime's minimum year is 1 ref = datetime.date(year, 1, 1) mydate = datetime.date.fromordinal(ref.toordinal() + doy - 1) return year, mydate.month, mydate.day + frac else: # The algorithm provided by Meeus doesn't work for years below # +1. This little hack solves that problem (the 'if' result is # inverted here). k = 1 if Epoch.is_leap(year) else 2 if doy < 32: m = 1 else: m = INT((9.0*(k + doy))/275.0 + 0.98) d = doy - INT((275.0*m)/9.0) + k*INT((m + 9.0)/12.0) + 30 return year, int(m), d + frac else: raise ValueError("Invalid input values")
[docs] @staticmethod def leap_seconds(year, month): """Returns the leap seconds accumulated for the given year and month. :param year: Year :type year: int :param month: Month, in numeric format ([1:12] range) :type month: int :returns: Leap seconds accumulated for given year and month. :rtype: int >>> Epoch.leap_seconds(1972, 4) 0 >>> Epoch.leap_seconds(1972, 6) 0 >>> Epoch.leap_seconds(1972, 7) 1 >>> Epoch.leap_seconds(1983, 6) 11 >>> Epoch.leap_seconds(1983, 7) 12 >>> Epoch.leap_seconds(1985, 8) 13 >>> Epoch.leap_seconds(2016, 11) 26 >>> Epoch.leap_seconds(2017, 1) 27 >>> Epoch.leap_seconds(2018, 7) 27 """ list_years = sorted(LEAP_TABLE.keys()) # First test the extremes of the table if (year + month/12.0) <= list_years[0]: return 0 if (year + month/12.0) >= list_years[-1]: return LEAP_TABLE[list_years[-1]] lyear = (year + 0.25) if month <= 6 else (year + 0.75) idx = 0 while lyear > list_years[idx]: idx += 1 return LEAP_TABLE[list_years[idx - 1]]
[docs] @staticmethod def get_last_leap_second(): """Method to get the date and value of the last leap second added to the table :returns: Tuple with year, month, day, leap second value. :rtype: tuple """ list_years = sorted(LEAP_TABLE.keys()) lyear = list_years[-1] lseconds = LEAP_TABLE[lyear] year = INT(lyear) # So far, leap seconds are added either on June 30th or December 31th if lyear % 1 == 0.0: year -= 1 month = 12 day = 31.0 else: month = 6 day = 30.0 return year, month, day, lseconds
[docs] @staticmethod def utc2local(): """Method to return the difference between UTC and local time. By default, dates in this Epoch class are handled in Coordinated Universal Time (UTC). This method provides you the seconds that you have to add or subtract to UTC time to convert to your local time. Please bear in mind that, in order for this method to work, you operative system must be correctly configured, with the right time and corresponding time zone. :returns: Difference in seconds between local and UTC time. :rtype: float """ localhour = datetime.datetime.now().hour utchour = datetime.datetime.utcnow().hour localminute = datetime.datetime.now().minute utcminute = datetime.datetime.utcnow().minute return (localhour - utchour)*3600.0 + (localminute - utcminute)*60.0
[docs] @staticmethod def easter(year): """Method to return the Easter day for given year. .. note:: This method is valid for both Gregorian and Julian years. :param year: Year :type year: int :returns: Easter month and day, as a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> Epoch.easter(1991) (3, 31) >>> Epoch.easter(1818) (3, 22) >>> Epoch.easter(1943) (4, 25) >>> Epoch.easter(2000) (4, 23) >>> Epoch.easter(1954) (4, 18) >>> Epoch.easter(179) (4, 12) >>> Epoch.easter(1243) (4, 12) """ # This algorithm is describes in pages 67-69 of Meeus book if not isinstance(year, (int, float)): raise TypeError("Invalid input type") year = int(year) if year >= 1583: # In this case, we are using the Gregorian calendar a = year % 19 b = INT(year/100.0) c = year % 100 d = INT(b/4.0) e = b % 4 f = INT((b + 8.0)/25.0) g = INT((b - f + 1.0)/3.0) h = (19*a + b - d - g + 15) % 30 i = INT(c/4.0) k = c % 4 ll = (32 + 2*(e + i) - h - k) % 7 m = INT((a + 11*h + 22*ll)/451.0) n = INT((h + ll - 7*m + 114)/31.0) p = (h + ll - 7*m + 114) % 31 return (n, p + 1) else: # The Julian calendar is used here a = year % 4 b = year % 7 c = year % 19 d = (19*c + 15) % 30 e = (2*a + 4*b - d + 34) % 7 f = INT((d + e + 114)/31.0) g = (d + e + 114) % 31 return (f, g + 1)
[docs] @staticmethod def jewish_pesach(year): """Method to return the Jewish Easter (Pesach) day for given year. .. note:: This method is valid for both Gregorian and Julian years. :param year: Year :type year: int :returns: Jewish Easter (Pesach) month and day, as a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> Epoch.jewish_pesach(1990) (4, 10) """ # This algorithm is described in pages 71-73 of Meeus book if not isinstance(year, (int, float)): raise TypeError("Invalid input type") year = INT(year) c = INT(year/100.0) s = 0 if year < 1583 else INT((3.0*c - 5.0)/4.0) a = (12*(year + 1)) % 19 b = year % 4 q = -1.904412361576 + 1.554241796621*a + 0.25*b \ - 0.003177794022*year + s j = (INT(q) + 3*year + 5*b + 2 + s) % 7 r = q - INT(q) if j == 2 or j == 4 or j == 6: d = INT(q) + 23 elif j == 1 and a > 6 and r > 0.632870370: d = INT(q) + 24 elif j == 0 and a > 11 and r > 0.897723765: d = INT(q) + 23 else: d = INT(q) + 22 if d > 31: return (4, d - 31) else: return (3, d)
[docs] @staticmethod def moslem2gregorian(year, month, day): """Method to convert a date in the Moslen calendar to the Gregorian (or Julian) calendar. .. note:: This method is valid for both Gregorian and Julian years. :param year: Year :type year: int :param month: Month :type month: int :param day: Day :type day: int :returns: Date in Gregorian (Julian) calendar: year, month and day, as a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> Epoch.moslem2gregorian(1421, 1, 1) (2000, 4, 6) """ # First, check that input types are correct if not isinstance(year, (int, float)) or \ not isinstance(month, (int, float)) or \ not isinstance(day, (int, float)): raise TypeError("Invalid input type") if day < 1 or day > 30 or month < 1 or month > 12 or year < 1: raise ValueError("Invalid input data") # This algorithm is described in pages 73-75 of Meeus book # Note: Ramadan is month Nr. 9 h = INT(year) m = INT(month) d = INT(day) n = d + INT(29.5001*(m - 1) + 0.99) q = INT(h/30.0) r = h % 30 a = INT((11.0*r + 3.0)/30.0) w = 404*q + 354*r + 208 + a q1 = INT(w/1461.0) q2 = w % 1461 g = 621 + 4*INT(7.0*q + q1) k = INT(q2/365.2422) e = INT(365.2422*k) j = q2 - e + n - 1 x = g + k if j > 366 and x % 4 == 0: j -= 366 x += 1 elif j > 365 and x % 4 > 0: j -= 365 x += 1 # Check if date is in Gregorian calendar. '277' is DOY of October 4th if (x > 1583) or (x == 1582 and j > 277): jd = INT(365.25*(x - 1.0)) + 1721423 + j alpha = INT((jd - 1867216.25)/36524.25) beta = jd if jd < 2299161 else (jd + 1 + alpha - INT(alpha/4.0)) b = beta + 1524 c = INT((b - 122.1)/365.25) d = INT(365.25*c) e = INT((b - d)/30.6001) day = b - d - INT(30.6001*e) month = (e - 1) if e < 14 else (e - 13) year = (c - 4716) if month > 2 else (c - 4715) return year, month, day else: # It is a Julian date. We have year and DOY return Epoch.doy2date(x, j)
[docs] @staticmethod def gregorian2moslem(year, month, day): """Method to convert a date in the Gregorian (or Julian) calendar to the Moslen calendar. :param year: Year :type year: int :param month: Month :type month: int :param day: Day :type day: int :returns: Date in Moslem calendar: year, month and day, as a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> Epoch.gregorian2moslem(1991, 8, 13) (1412, 2, 2) """ # First, check that input types are correct if not isinstance(year, (int, float)) or \ not isinstance(month, (int, float)) or \ not isinstance(day, (int, float)): raise TypeError("Invalid input type") if day < 1 or day > 31 or month < 1 or month > 12 or year < -4712: raise ValueError("Invalid input data") # This algorithm is described in pages 75-76 of Meeus book x = INT(year) m = INT(month) d = INT(day) if m < 3: x -= 1 m += 12 alpha = INT(x/100.0) beta = 2 - alpha + INT(alpha/4.0) b = INT(365.25*x) + INT(30.6001*(m + 1.0)) + d + 1722519 + beta c = INT((b - 122.1)/365.25) d = INT(365.25*c) e = INT((b - d)/30.6001) d = b - d - INT(30.6001*e) m = (e - 1) if e < 14 else (e - 13) x = (c - 4716) if month > 2 else (c - 4715) w = 1 if x % 4 == 0 else 2 n = INT((275.0*m)/9.0) - w*INT((m + 9.0)/12.0) + d - 30 a = x - 623 b = INT(a/4.0) c = a % 4 c1 = 365.2501*c c2 = INT(c1) if c1 - c2 > 0.5: c2 += 1 dp = 1461*b + 170 + c2 q = INT(dp/10631.0) r = dp % 10631 j = INT(r/354.0) k = r % 354 o = INT((11.0*j + 14.0)/30.0) h = 30*q + j + 1 jj = k - o + n - 1 # jj is the number of the day in the moslem year h. If jj > 354 we need # to know if h is a leap year if jj > 354: cl = h % 30 dl = (11*cl + 3) % 30 if dl < 19: jj -= 354 h += 1 else: jj -= 355 h += 1 if jj == 0: jj = 355 h -= 1 # Now, let's convert DOY jj to month and day if jj == 355: m = 12 d = 30 else: s = INT((jj - 1.0)/29.5) m = 1 + s d = INT(jj - 29.5*s) return h, m, d
[docs] def __str__(self): """Method used when trying to print the object. :returns: Internal JDE value as a string. :rtype: string >>> e = Epoch(1987, 6, 19.5, leap_seconds=0.0) >>> print(e) 2446966.0 """ return str(self._jde)
[docs] def get_date(self, **kwargs): """This method converts the internal JDE value back to a date. Use **leap_seconds=0.0** to disable the automatic TT to UTC conversion mechanism, or provide a non zero value to **leap_seconds** to apply a specific leap seconds value. :param leap_seconds: Optional value for leap seconds. :type leap_seconds: int, float :returns: Year, month, day in a tuple :rtype: tuple >>> e = Epoch(2436116.31) >>> y, m, d = e.get_date(leap_seconds=0.0) >>> print("{}/{}/{}".format(y, m, round(d, 2))) 1957/10/4.81 >>> e = Epoch(1988, 1, 27) >>> y, m, d = e.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 1988/1/27.0 >>> e = Epoch(1842713.0) >>> y, m, d = e.get_date(leap_seconds=0.0) >>> print("{}/{}/{}".format(y, m, round(d, 2))) 333/1/27.5 >>> e = Epoch(1507900.13) >>> y, m, d = e.get_date(leap_seconds=0.0) >>> print("{}/{}/{}".format(y, m, round(d, 2))) -584/5/28.63 """ jd = self._jde + 0.5 z = INT(jd) f = jd % 1 if z < 2299161: a = z else: alpha = INT((z - 1867216.25)/36524.25) a = z + 1 + alpha - INT(alpha/4.0) b = a + 1524 c = INT((b - 122.1)/365.25) d = INT(365.25*c) e = INT((b - d)/30.6001) day = b - d - INT(30.6001*e) + f if e < 14: month = e - 1 elif e == 14 or e == 15: month = e - 13 if month > 2: year = c - 4716 elif month == 1 or month == 2: year = c - 4715 year = int(year) month = int(month) tt2utc = True if 'leap_seconds' in kwargs: tt2utc = False leap_seconds = kwargs['leap_seconds'] # If enabled, let's convert from TT to UTC, subtracting needed seconds deltasec = 0.0 # In this case, TT to UTC correction is applied automatically, but only # for dates after July 1st, 1972 if tt2utc: if year > 1972 or (year == 1972 and month >= 7): deltasec = 32.184 # Difference between TT and TAI deltasec += 10.0 # Difference between UTC and TAI in 1972 deltasec += Epoch.leap_seconds(year, month) else: # Correction is NOT automatic if leap_seconds != 0.0: # We apply provided leap seconds if year > 1972 or (year == 1972 and month >= 7): deltasec = 32.184 # Difference between TT and TAI deltasec += 10.0 # Difference between UTC-TAI in 1972 deltasec += leap_seconds if deltasec != 0.0: doy = Epoch.getDOY(year, month, day) doy -= deltasec/DAY2SEC # Check that we didn't change year if doy < 1.0: year -= 1 doy = 366.0 + doy if Epoch.is_leap(year) else 365.0 + doy year, month, day = Epoch.doy2date(year, doy) return year, month, day
[docs] @staticmethod def tt2ut(year, month): """This method provides an approximation of the difference, in seconds, between Terrestrial Time and Universal Time, denoted **DeltaT**, where: DeltaT = TT - UT. Here we depart from Meeus book and use the polynomial expressions from: https://eclipse.gsfc.nasa.gov/LEcat5/deltatpoly.html Which are regarded as more elaborate and precise than Meeus'. Please note that, by definition, the UTC time used internally in this Epoch class by default is kept within 0.9 seconds from UT. Therefore, UTC is in itself a quite good approximation to UT, arguably better than some of the results provided by this method. :param year: Year we want to compute DeltaT for. :type year: int, float :param month: Month we want to compute DeltaT for. :type month: int, float :returns: DeltaT, in seconds :rtype: float >>> round(Epoch.tt2ut(1642, 1), 1) 62.1 >>> round(Epoch.tt2ut(1680, 1), 1) 15.3 >>> round(Epoch.tt2ut(1700, 1), 1) 8.8 >>> round(Epoch.tt2ut(1726, 1), 1) 10.9 >>> round(Epoch.tt2ut(1750, 1), 1) 13.4 >>> round(Epoch.tt2ut(1774, 1), 1) 16.7 >>> round(Epoch.tt2ut(1800, 1), 1) 13.7 >>> round(Epoch.tt2ut(1820, 1), 1) 11.9 >>> round(Epoch.tt2ut(1890, 1), 1) -6.1 >>> round(Epoch.tt2ut(1928, 2), 1) 24.2 >>> round(Epoch.tt2ut(1977, 2), 1) 47.7 >>> round(Epoch.tt2ut(1998, 1), 1) 63.0 >>> round(Epoch.tt2ut(2015, 7), 1) 69.3 """ y = year + (month - 0.5)/12.0 if year < -500: u = (year - 1820.0)/100.0 dt = -20.0 + 32.0*u*u elif year >= -500 and year < 500: u = y/100.0 dt = 10583.6 + u*(-1014.41 + u*(33.78311 + u*(-5.952053 + u*(-0.1798452 + u*(0.022174192 + 0.0090316521*u))))) elif year >= 500 and year < 1600: dt = 1574.2 + u*(-556.01 + u*(71.23472 + u*(0.319781 + u*(-0.8503463 + u*(-0.005050998 + 0.0083572073*u))))) elif year >= 1600 and year < 1700: t = y - 1600.0 dt = 120.0 + t*(-0.9808 + t*(-0.01532 + t/7129.0)) elif year >= 1700 and year < 1800: t = y - 1700.0 dt = 8.83 + t*(0.1603 + t*(-0.0059285 + t*(0.00013336 - t/1174000.0))) elif year >= 1800 and year < 1860: t = y - 1800.0 dt = 13.72 + t*(-0.332447 + t*(0.0068612 + t*(0.0041116 + t*(-0.00037436 + t*(0.0000121272 + t*(-0.0000001699 + 0.000000000875*t)))))) elif year >= 1860 and year < 1900: t = y - 1860.0 dt = 7.62 + t*(0.5737 + t*(-0.251754 + t*(0.01680668 + t*(-0.0004473624 + t/233174.0)))) elif year >= 1900 and year < 1920: t = y - 1900.0 dt = -2.79 + t*(1.494119 + t*(-0.0598939 + t*(0.0061966 - 0.000197*t))) elif year >= 1920 and year < 1941: t = y - 1920.0 dt = 21.20 + t*(0.84493 + t*(-0.076100 + 0.0020936*t)) elif year >= 1941 and year < 1961: t = y - 1950.0 dt = 29.07 + t*(0.407 + t*(-1.0/233.0 + t/2547.0)) elif year >= 1961 and year < 1986: t = y - 1975.0 dt = 45.45 + t*(1.067 + t*(-1.0/260.0 - t/718.0)) elif year >= 1986 and year < 2005: t = y - 2000.0 dt = 63.86 + t*(0.3345 + t*(-0.060374 + t*(0.0017275 + t*(0.000651814 + 0.00002373599*t)))) elif year >= 2005 and year < 2050: t = y - 2000.0 dt = 62.92 + t*(0.32217 + 0.005589*t) elif year >= 2050 and year < 2150: dt = -20.0 + 32.0 * ((y - 1820.0)/100.0)**2 - 0.5628*(2150.0 - y) else: u = (year - 1820.0)/100.0 dt = -20.0 + 32.0*u*u return dt
[docs] def dow(self, as_string=False): """Method to return the day of week corresponding to this Epoch. By default, this method returns an integer value: 0 for Sunday, 1 for Monday, etc. However, when **as_string=True** is passed, the names of the days are returned. :param as_string: Whether result will be given as a integer or as a string. False by default. :type as_string: bool :returns: Day of the week, as a integer or as a string. :rtype: int, str >>> e = Epoch(1954, 'June', 30) >>> e.dow() 3 >>> e = Epoch(2018, 'Feb', 14.9) >>> e.dow(as_string=True) 'Wednesday' >>> e = Epoch(2018, 'Feb', 15) >>> e.dow(as_string=True) 'Thursday' >>> e = Epoch(2018, 'Feb', 15.99) >>> e.dow(as_string=True) 'Thursday' >>> e.set(2018, 'Jul', 15.4) >>> e.dow(as_string=True) 'Sunday' >>> e.set(2018, 'Jul', 15.9) >>> e.dow(as_string=True) 'Sunday' """ jd = INT(self._jde - 0.5) + 2.0 doy = INT(jd % 7) if not as_string: return doy else: day_names = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] return day_names[doy]
[docs] def sidereal_time(self): """Method to compute the _mean_ sidereal time at Greenwich for the epoch represented by this object. .. note:: If you require the result as an angle, you should convert the result from this method to hours with decimals (with :const:`DAY2HOURS`), and then multiply by 15 deg/hr. Alternatively, you can convert the result to hours with decimals, and feed this value to an :class:`Angle` object, setting **ra=True**, and making use of :class:`Angle` facilities for further handling. :returns: Mean sidereal time, in days :rtype: float >>> e = Epoch(1987, 4, 10, leap_seconds=0.0) >>> round(e.sidereal_time(), 9) 0.549147764 >>> e = Epoch(1987, 4, 10, 19, 21, 0.0, leap_seconds=0.0) >>> round(e.sidereal_time(), 9) 0.357605204 """ jd0 = INT(self()) + 0.5 if self() % 1 >= 0.5 else INT(self()) - 0.5 t = (jd0 - 2451545.0)/36525.0 theta0 = 6.0/DAY2HOURS + 41.0/DAY2MIN + 50.54841/DAY2SEC s = t*(8640184.812866 + t*(0.093104 - 0.0000062*t)) theta0 += (s % DAY2SEC) / DAY2SEC deltajd = self() - jd0 if abs(deltajd) < TOL: # In this case, we are done return theta0 % 1 else: deltajd *= 1.00273790935 return (theta0 + deltajd) % 1
[docs] def mjd(self): """This method returns the Modified Julian Day (MJD). :returns: Modified Julian Day (MJD). :rtype: float >>> e = Epoch(1858, 'NOVEMBER', 17, leap_seconds=0.0) >>> e.mjd() 0.0 """ return self._jde - 2400000.5
[docs] def jde(self): """Method to return the internal value of the Julian Ephemeris Day. :returns: The internal value of the Julian Ephemeris Day. :rtype: float >>> a = Epoch(-1000, 2, 29.0, leap_seconds=0.0) >>> print(a.jde()) 1355866.5 """ return self()
[docs] def __call__(self): """Method used when Epoch is called only with parenthesis. :returns: The internal value of the Julian Ephemeris Day. :rtype: float >>> a = Epoch(-122, 1, 1.0, leap_seconds=0.0) >>> print(a()) 1676497.5 """ return self._jde
[docs] def __add__(self, b): """This method defines the addition between an Epoch and some days. :param b: Value to be added, in days. :type b: int, float :returns: A new Epoch object. :rtype: :py:class:`Epoch` :raises: TypeError if operand is of wrong type. >>> a = Epoch(1991, 7, 11) >>> b = a + 10000 >>> y, m, d = b.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 2018/11/26.0 """ if isinstance(b, (int, float)): return Epoch(self._jde + float(b)) else: raise TypeError("Wrong operand type")
[docs] def __sub__(self, b): """This method defines the subtraction between Epochs or between an Epoch and a given number of days. :param b: Value to be subtracted, either an Epoch or days. :type b: py:class:`Epoch`, int, float :returns: A new Epoch object if parameter 'b' is in days, or the difference between provided Epochs, in days. :rtype: :py:class:`Epoch`, float :raises: TypeError if operand is of wrong type. >>> a = Epoch(1986, 2, 9.0) >>> print(round(a(), 2)) 2446470.5 >>> b = Epoch(1910, 4, 20.0) >>> print(round(b(), 2)) 2418781.5 >>> c = a - b >>> print(round(c, 2)) 27689.0 >>> a = Epoch(2003, 12, 31.0) >>> b = a - 365.5 >>> y, m, d = b.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 2002/12/30.5 """ if isinstance(b, (int, float)): return Epoch(self._jde - b) elif isinstance(b, Epoch): return float(self._jde - b._jde) else: raise TypeError("Invalid operand type")
[docs] def __iadd__(self, b): """This method defines the accumulative addition to this Epoch. :param b: Value to be added, in days. :type b: int, float :returns: This Epoch. :rtype: :py:class:`Epoch` :raises: TypeError if operand is of wrong type. >>> a = Epoch(2003, 12, 31.0) >>> a += 32.5 >>> y, m, d = a.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 2004/2/1.5 """ if isinstance(b, (int, float)): self = self + b return self else: raise TypeError("Wrong operand type")
[docs] def __isub__(self, b): """This method defines the accumulative subtraction to this Epoch. :param b: Value to be subtracted, in days. :type b: int, float :returns: This Epoch. :rtype: :py:class:`Epoch` :raises: TypeError if operand is of wrong type. >>> a = Epoch(2001, 12, 31.0) >>> a -= 2*365 >>> y, m, d = a.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 2000/1/1.0 """ if isinstance(b, (int, float)): self = self - b return self else: raise TypeError("Wrong operand type")
[docs] def __radd__(self, b): """This method defines the addition to a Epoch by the right :param b: Value to be added, in days. :type b: int, float :returns: A new Epoch object. :rtype: :py:class:`Epoch` :raises: TypeError if operand is of wrong type. >>> a = Epoch(2004, 2, 27.8) >>> b = 2.2 + a >>> y, m, d = b.get_date() >>> print("{}/{}/{}".format(y, m, round(d, 2))) 2004/3/1.0 """ if isinstance(b, (int, float)): return self.__add__(b) # It is the same as by the left else: raise TypeError("Wrong operand type")
[docs] def __int__(self): """This method returns the internal JDE value as an int. :returns: Internal JDE value as an int. :rtype: int >>> a = Epoch(2434923.85) >>> int(a) 2434923 """ return int(self._jde)
[docs] def __float__(self): """This method returns the internal JDE value as a float. :returns: Internal JDE value as a float. :rtype: float >>> a = Epoch(2434923.85) >>> float(a) 2434923.85 """ return float(self._jde)
[docs] def __eq__(self, b): """This method defines the 'is equal' operator between Epochs. .. note:: For the comparison, the **base.TOL** value is used. :returns: A boolean. :rtype: bool :raises: TypeError if input values are of wrong type. >>> a = Epoch(2007, 5, 20.0) >>> b = Epoch(2007, 5, 20.000001) >>> a == b False >>> a = Epoch(2004, 10, 15.7) >>> b = Epoch(a) >>> a == b True >>> a = Epoch(2434923.85) >>> a == 2434923.85 True """ if isinstance(b, (int, float)): return abs(self._jde - float(b)) < TOL elif isinstance(b, Epoch): return abs(self._jde - b._jde) < TOL else: raise TypeError("Wrong operand type")
[docs] def __ne__(self, b): """This method defines the 'is not equal' operator between Epochs. .. note:: For the comparison, the **base.TOL** value is used. :returns: A boolean. :rtype: bool >>> a = Epoch(2007, 5, 20.0) >>> b = Epoch(2007, 5, 20.000001) >>> a != b True >>> a = Epoch(2004, 10, 15.7) >>> b = Epoch(a) >>> a != b False >>> a = Epoch(2434923.85) >>> a != 2434923.85 False """ return not self.__eq__(b) # '!=' == 'not(==)'
[docs] def __lt__(self, b): """This method defines the 'is less than' operator between Epochs. :returns: A boolean. :rtype: bool :raises: TypeError if input values are of wrong type. >>> a = Epoch(2004, 10, 15.7) >>> b = Epoch(2004, 10, 15.7) >>> a < b False """ if isinstance(b, (int, float)): return self._jde < float(b) elif isinstance(b, Epoch): return self._jde < b._jde else: raise TypeError("Wrong operand type")
[docs] def __ge__(self, b): """This method defines 'is equal or greater' operator between Epochs. :returns: A boolean. :rtype: bool :raises: TypeError if input values are of wrong type. >>> a = Epoch(2004, 10, 15.71) >>> b = Epoch(2004, 10, 15.7) >>> a >= b True """ return not self.__lt__(b) # '>=' == 'not(<)'
[docs] def __gt__(self, b): """This method defines the 'is greater than' operator between Epochs. :returns: A boolean. :rtype: bool :raises: TypeError if input values are of wrong type. >>> a = Epoch(2004, 10, 15.71) >>> b = Epoch(2004, 10, 15.7) >>> a > b True >>> a = Epoch(-207, 11, 5.2) >>> b = Epoch(-207, 11, 5.2) >>> a > b False """ if isinstance(b, (int, float)): return self._jde > float(b) elif isinstance(b, Epoch): return self._jde > b._jde else: raise TypeError("Wrong operand type")
[docs] def __le__(self, b): """This method defines 'is equal or less' operator between Epochs. :returns: A boolean. :rtype: bool :raises: TypeError if input values are of wrong type. >>> a = Epoch(2004, 10, 15.71) >>> b = Epoch(2004, 10, 15.7) >>> a <= b False >>> a = Epoch(-207, 11, 5.2) >>> b = Epoch(-207, 11, 5.2) >>> a <= b True """ return not self.__gt__(b) # '<=' == 'not(>)'
def main(): # Let's define a small helper function def print_me(msg, val): print("{}: {}".format(msg, val)) # Let's do some work with the Epoch class print('\n' + 35*'*') print("*** Use of Epoch class") print(35*'*' + '\n') e = Epoch(1987, 6, 19.5) print_me("JDE for 1987/6/19.5", e) # Redefine the Epoch object e.set(333, 'Jan', 27, 12, leap_seconds=0.0) print_me("JDE for 333/1/27.5", e) # We can create an Epoch from a 'date' or 'datetime' object d = datetime.datetime(837, 4, 10, 7, 12, 0, 0) f = Epoch(d, leap_seconds=0.0) print_me("JDE for 837/4/10.3", f) print("") # Check if a given date belong to the Julian or Gregorian calendar print_me("Is 1590/4/21.4 a Julian date?", Epoch.is_julian(1590, 4, 21.4)) print("") # We can also check if a given year is leap or not print_me("Is -1000 a leap year?", Epoch.is_leap(-1000)) print_me("Is 1800 a leap year?", Epoch.is_leap(1800)) print_me("Is 2012 a leap year?", Epoch.is_leap(2012)) print("") # Get the Day Of Year corresponding to a given date print_me("Day Of Year (DOY) of 1978/11/14", Epoch.getDOY(1978, 11, 14)) print_me("Day Of Year (DOY) of -400/2/29.9", Epoch.getDOY(-400, 2, 29.9)) print("") # Now the opposite: Get a date from a DOY t = Epoch.doy2date(2017, 365.7) s = str(t[0]) + "/" + str(t[1]) + "/" + str(round(t[2], 2)) print_me("Date from DOY 2017:365.7", s) t = Epoch.doy2date(-4, 60) s = str(t[0]) + "/" + str(t[1]) + "/" + str(round(t[2], 2)) print_me("Date from DOY -4:60", s) print("") # There is an internal table which we can use to get the leap seconds print_me("Number of leap seconds applied up to July 1983", Epoch.leap_seconds(1983, 7)) print("") # We can convert the internal JDE value back to a date e = Epoch(2436116.31) y, m, d = e.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("Date from JDE 2436116.31", s) print("") # It is possible to get the day of the week corresponding to a given date e = Epoch(2018, 'Feb', 15) print_me("The day of week of 2018/2/15 is", e.dow(as_string=True)) print("") # In some cases it is useful to get the Modified Julian Day (MJD) e = Epoch(1923, 'August', 23) print_me("Modified Julian Day for 1923/8/23", round(e.mjd(), 2)) print("") # If your system is appropriately configured, you can get the difference in # seconds between your local time and UTC print_me("To convert from local system time to UTC you must add/subtract" + " this amount of seconds", Epoch.utc2local()) print("") # Compute DeltaT = TT - UT differences for various dates print_me("DeltaT (TT - UT) for Feb/333", round(Epoch.tt2ut(333, 2), 1)) print_me("DeltaT (TT - UT) for Jan/1642", round(Epoch.tt2ut(1642, 1), 1)) print_me("DeltaT (TT - UT) for Feb/1928", round(Epoch.tt2ut(1928, 1), 1)) print_me("DeltaT (TT - UT) for Feb/1977", round(Epoch.tt2ut(1977, 2), 1)) print_me("DeltaT (TT - UT) for Jan/1998", round(Epoch.tt2ut(1998, 1), 1)) print("") # The difference between civil day and sidereal day is almost 4 minutes e = Epoch(1987, 4, 10, leap_seconds=0.0) st1 = round(e.sidereal_time(), 9) e = Epoch(1987, 4, 11, leap_seconds=0.0) st2 = round(e.sidereal_time(), 9) ds = (st2 - st1)*DAY2MIN msg = "{}m {}s".format(INT(ds), (ds % 1)*60.0) print_me("Difference between sidereal time 1987/4/11 and 1987/4/10", msg) print("") # Epoch class can also provide the date of Easter for a given year # Let's spice up the output a little bit, calling dow() and get_month() month, day = Epoch.easter(2019) e = Epoch(2019, month, day) s = e.dow(as_string=True) + ", " + str(day) + get_ordinal_suffix(day) + \ " of " + Epoch.get_month(month, as_string=True) print_me("Easter day for 2019", s) # I know Easter is always on Sunday, by the way... ;-) print("") # Compute the date of the Jewish Easter (Pesach) for a given year month, day = Epoch.jewish_pesach(1990) s = str(day) + get_ordinal_suffix(day) + " of " + \ Epoch.get_month(month, as_string=True) print_me("Jewish Pesach day for 1990", s) print("") # Now, convert a date in the Moslem calendar to the Gregorian calendar y, m, d = Epoch.moslem2gregorian(1421, 1, 1) print_me("The date 1421/1/1 in the Moslem calendar is, in Gregorian " + "calendar", "{}/{}/{}".format(y, m, d)) y, m, d = Epoch.moslem2gregorian(1439, 9, 1) print_me("The start of Ramadan month (9/1) for Gregorian year 2018 is", "{}/{}/{}".format(y, m, d)) # We can go from the Gregorian calendar back to the Moslem calendar too print_me("Date 1991/8/13 in Gregorian calendar is, in Moslem calendar", "{}/{}/{}".format(*Epoch.gregorian2moslem(1991, 8, 13))) # Note: The '*' before 'Epoch' will _unpack_ the tuple into components print("") # It is possible to carry out some algebraic operations with Epochs # Add 10000 days to a given date a = Epoch(1991, 7, 11) b = a + 10000 y, m, d = b.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("1991/7/11 plus 10000 days is", s) # Subtract two Epochs to find the number of days between them a = Epoch(1986, 2, 9.0) b = Epoch(1910, 4, 20.0) print_me("The number of days between 1986/2/9 and 1910/4/20 is", round(a - b, 2)) # We can also subtract a given amount of days from an Epoch a = Epoch(2003, 12, 31.0) b = a - 365.5 y, m, d = b.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("2003/12/31 minus 365.5 days is", s) # Accumulative addition and subtraction of days is also allowed a = Epoch(2003, 12, 31.0) a += 32.5 y, m, d = a.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("2003/12/31 plus 32.5 days is", s) a = Epoch(2001, 12, 31.0) a -= 2*365 y, m, d = a.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("2001/12/31 minus 2*365 days is", s) # It is also possible to add days from the right a = Epoch(2004, 2, 27.8) b = 2.2 + a y, m, d = b.get_date() s = str(y) + "/" + str(m) + "/" + str(round(d, 2)) print_me("2004/2/27.8 plus 2.2 days is", s) print("") # Comparison operadors between epochs are also defined a = Epoch(2007, 5, 20.0) b = Epoch(2007, 5, 20.000001) print_me("2007/5/20.0 == 2007/5/20.000001", a == b) print_me("2007/5/20.0 != 2007/5/20.000001", a != b) print_me("2007/5/20.0 > 2007/5/20.000001", a > b) print_me("2007/5/20.0 <= 2007/5/20.000001", a <= b) if __name__ == '__main__': main()