2020-06-07 17:12:11 +02:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2020-07-17 15:41:48 +02:00
|
|
|
|
import datetime as dt
|
2020-05-10 14:50:57 +02:00
|
|
|
|
import re
|
2020-05-24 21:05:32 +02:00
|
|
|
|
from collections import namedtuple
|
2020-07-17 14:18:15 +02:00
|
|
|
|
from datetime import date, datetime
|
2020-06-06 03:12:26 +02:00
|
|
|
|
from numpy import (array, concatenate, cos, float_, interp, isnan, nan,
|
|
|
|
|
ndarray, pi, rollaxis, searchsorted, sin, where, zeros_like)
|
2014-01-11 17:19:22 +01:00
|
|
|
|
from time import strftime
|
2020-06-06 17:06:26 +02:00
|
|
|
|
from .constants import ASEC2RAD, B1950, DAY_S, T0, tau
|
2018-05-15 01:58:48 +02:00
|
|
|
|
from .descriptorlib import reify
|
2018-09-05 22:55:37 +02:00
|
|
|
|
from .earthlib import sidereal_time, earth_rotation_angle
|
2013-12-27 05:47:01 +01:00
|
|
|
|
from .framelib import ICRS_to_J2000 as B
|
2020-06-12 20:18:08 +02:00
|
|
|
|
from .functions import mxm, mxmxm, _to_array, load_bundled_npy, rot_z
|
2020-06-06 03:12:26 +02:00
|
|
|
|
from .nutationlib import (
|
2020-06-07 18:57:47 +02:00
|
|
|
|
build_nutation_matrix, equation_of_the_equinoxes_complimentary_terms,
|
|
|
|
|
iau2000a_radians, mean_obliquity,
|
2020-06-06 03:12:26 +02:00
|
|
|
|
)
|
2013-12-22 18:13:01 +01:00
|
|
|
|
from .precessionlib import compute_precession
|
2013-08-12 20:27:13 +02:00
|
|
|
|
|
2020-05-24 21:05:32 +02:00
|
|
|
|
CalendarTuple = namedtuple('CalendarTuple', 'year month day hour minute second')
|
|
|
|
|
|
|
|
|
|
class CalendarArray(ndarray):
|
|
|
|
|
@property
|
|
|
|
|
def year(self): return self[0]
|
|
|
|
|
@property
|
|
|
|
|
def month(self): return self[1]
|
|
|
|
|
@property
|
|
|
|
|
def day(self): return self[2]
|
|
|
|
|
@property
|
|
|
|
|
def hour(self): return self[3]
|
|
|
|
|
@property
|
|
|
|
|
def minute(self): return self[4]
|
|
|
|
|
@property
|
|
|
|
|
def second(self): return self[5]
|
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
if hasattr(dt, 'timezone'):
|
|
|
|
|
utc = dt.timezone.utc
|
|
|
|
|
else:
|
2020-07-17 14:18:15 +02:00
|
|
|
|
try:
|
|
|
|
|
from pytz import utc
|
|
|
|
|
except ImportError:
|
|
|
|
|
# Lacking a full suite of timezones from pytz, we at least need a
|
|
|
|
|
# time zone object for UTC.
|
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
class UTC(dt.tzinfo):
|
2020-07-17 14:18:15 +02:00
|
|
|
|
'UTC'
|
2020-07-17 15:41:48 +02:00
|
|
|
|
zero = dt.timedelta(0)
|
2020-07-17 14:18:15 +02:00
|
|
|
|
def utcoffset(self, dt):
|
|
|
|
|
return self.zero
|
|
|
|
|
def tzname(self, dt):
|
|
|
|
|
return 'UTC'
|
|
|
|
|
def dst(self, dt):
|
|
|
|
|
return self.zero
|
|
|
|
|
|
|
|
|
|
utc = UTC()
|
2014-01-16 08:41:15 +01:00
|
|
|
|
|
2013-01-30 22:23:04 +01:00
|
|
|
|
# Much of the following code is adapted from the USNO's "novas.c".
|
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
_time_zero = dt.time()
|
2020-05-10 14:50:57 +02:00
|
|
|
|
_half_minute = 30.0 / DAY_S
|
2014-01-11 17:19:22 +01:00
|
|
|
|
_half_second = 0.5 / DAY_S
|
2014-01-16 08:41:15 +01:00
|
|
|
|
_half_microsecond = 0.5e-6 / DAY_S
|
2014-01-19 14:53:44 +01:00
|
|
|
|
_months = array(['Month zero', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
2014-01-19 07:48:08 +01:00
|
|
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
|
2013-08-07 02:45:13 +02:00
|
|
|
|
|
2014-01-11 07:51:42 +01:00
|
|
|
|
tt_minus_tai = array(32.184 / DAY_S)
|
2013-08-23 05:00:28 +02:00
|
|
|
|
|
2016-02-22 15:19:47 +01:00
|
|
|
|
class Timescale(object):
|
2016-03-08 05:32:00 +01:00
|
|
|
|
"""The data necessary to express dates in different timescales.
|
|
|
|
|
|
|
|
|
|
Whenever you want to express a date in Skyfield, you need a
|
2016-03-20 22:18:24 +01:00
|
|
|
|
`Timescale` that can translate between several different systems for
|
|
|
|
|
expressing time. You will usually create a single `Timescale` at
|
|
|
|
|
the beginning of your program, and use it every time you want to
|
2016-03-23 06:07:19 +01:00
|
|
|
|
generate a specific `Time`:
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2016-03-20 22:18:24 +01:00
|
|
|
|
>>> from skyfield.api import load
|
2020-06-07 17:12:11 +02:00
|
|
|
|
>>> ts = load.timescale(builtin=True)
|
2016-03-21 05:27:32 +01:00
|
|
|
|
>>> t = ts.utc(1980, 3, 1, 9, 30)
|
2016-03-08 05:32:00 +01:00
|
|
|
|
>>> t
|
2018-09-09 18:32:21 +02:00
|
|
|
|
<Time tt=2444299.896425741>
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
Loading a timescale without specifying ``builtin=True`` downloads
|
|
|
|
|
fresh data tables from the United States Naval Observatory and the
|
|
|
|
|
International Earth Rotation Service. These files go out of date,
|
|
|
|
|
and Skyfield will fetch updated copies once your copy of the files
|
|
|
|
|
are too old.
|
2016-02-21 15:48:03 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2016-03-28 15:57:29 +02:00
|
|
|
|
_utcnow = datetime.utcnow
|
2016-02-21 20:54:03 +01:00
|
|
|
|
|
2016-03-20 23:51:30 +01:00
|
|
|
|
def __init__(self, delta_t_recent, leap_dates, leap_offsets):
|
|
|
|
|
self.delta_t_table = build_delta_t_table(delta_t_recent)
|
|
|
|
|
self.leap_dates, self.leap_offsets = leap_dates, leap_offsets
|
2020-05-10 16:03:26 +02:00
|
|
|
|
self._leap_reverse_dates = leap_dates + leap_offsets / DAY_S
|
2016-03-27 04:18:40 +02:00
|
|
|
|
self.J2000 = Time(self, float_(T0))
|
|
|
|
|
self.B1950 = Time(self, float_(B1950))
|
2016-02-21 15:48:03 +01:00
|
|
|
|
|
2016-02-21 20:54:03 +01:00
|
|
|
|
def now(self):
|
2016-03-23 06:07:19 +01:00
|
|
|
|
"""Return the current date and time as a `Time` object.
|
2016-02-21 20:54:03 +01:00
|
|
|
|
|
|
|
|
|
For the return value to be correct, your operating system time
|
|
|
|
|
and timezone settings must be set so that the Python Standard
|
|
|
|
|
Library constructor ``datetime.datetime.utcnow()`` returns a
|
|
|
|
|
correct UTC date and time.
|
|
|
|
|
|
|
|
|
|
"""
|
2020-07-17 15:41:48 +02:00
|
|
|
|
return self.from_datetime(self._utcnow().replace(tzinfo=utc))
|
2016-02-21 20:54:03 +01:00
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
def from_datetime(self, datetime):
|
|
|
|
|
"""Return a `Time` for a Python ``datetime``.
|
|
|
|
|
|
|
|
|
|
The ``datetime`` must be “timezone-aware”: it must have a time
|
|
|
|
|
zone object as its ``tzinfo`` attribute instead of ``None``.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
jd, fr = _utc_datetime_to_tai(
|
|
|
|
|
self.leap_dates, self.leap_offsets, datetime)
|
|
|
|
|
t = Time(self, jd, fr + tt_minus_tai)
|
|
|
|
|
t.tai_fraction = fr
|
|
|
|
|
return t
|
|
|
|
|
|
|
|
|
|
def from_datetimes(self, datetime_list):
|
|
|
|
|
"""Return a `Time` for a Python ``datetime`` list.
|
|
|
|
|
|
|
|
|
|
The ``datetime`` objects must each be “timezone-aware”: they
|
|
|
|
|
must each have a time zone object as their ``tzinfo`` attribute
|
|
|
|
|
instead of ``None``.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
"""
|
|
|
|
|
leap_dates = self.leap_dates
|
|
|
|
|
leap_offsets = self.leap_offsets
|
|
|
|
|
pairs = [_utc_datetime_to_tai(leap_dates, leap_offsets, d)
|
|
|
|
|
for d in datetime_list]
|
|
|
|
|
jd, fr = zip(*pairs)
|
|
|
|
|
t = Time(self, _to_array(jd), fr + tt_minus_tai)
|
|
|
|
|
t.tai_fraction = fr
|
|
|
|
|
return t
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
def utc(self, year, month=1, day=1, hour=0, minute=0, second=0.0):
|
|
|
|
|
"""Build a `Time` from a UTC calendar date.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
Specify the date as a numeric year, month, day, hour, minute,
|
|
|
|
|
and second. Any argument may be an array in which case the
|
|
|
|
|
return value is a ``Time`` representing a whole array of times.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2020-07-17 15:41:48 +02:00
|
|
|
|
# TODO: someday deprecate passing datetime objects here, as
|
|
|
|
|
# there are now separate constructors for them.
|
2016-03-21 05:27:32 +01:00
|
|
|
|
if isinstance(year, datetime):
|
2020-07-17 15:41:48 +02:00
|
|
|
|
return self.from_datetime(year)
|
|
|
|
|
if isinstance(year, date):
|
|
|
|
|
return self.from_datetime(dt.combine(year, _time_zero))
|
|
|
|
|
if hasattr(year, '__len__') and isinstance(year[0], datetime):
|
|
|
|
|
return self.from_datetimes(year)
|
|
|
|
|
|
|
|
|
|
tai1, tai2 = _utc_to_tai(
|
|
|
|
|
self.leap_dates, self.leap_offsets, _to_array(year),
|
|
|
|
|
_to_array(month), _to_array(day), _to_array(hour),
|
|
|
|
|
_to_array(minute), _to_array(second),
|
|
|
|
|
)
|
2020-05-10 19:19:47 +02:00
|
|
|
|
t = Time(self, tai1, tai2 + tt_minus_tai)
|
2020-07-17 15:41:48 +02:00
|
|
|
|
t.tai_fraction = tai2
|
2016-03-23 06:07:19 +01:00
|
|
|
|
return t
|
2016-02-23 14:37:35 +01:00
|
|
|
|
|
2016-03-21 05:27:32 +01:00
|
|
|
|
def tai(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
|
2016-03-23 06:07:19 +01:00
|
|
|
|
jd=None):
|
2020-06-07 21:06:15 +02:00
|
|
|
|
"""Build a `Time` from a TAI proleptic Gregorian date.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
Supply the International Atomic Time (TAI) as a proleptic
|
|
|
|
|
Gregorian calendar date:
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
>>> t = ts.tai(2014, 1, 18, 1, 35, 37.5)
|
|
|
|
|
>>> t.tai
|
|
|
|
|
2456675.56640625
|
|
|
|
|
>>> t.tai_calendar()
|
|
|
|
|
(2014, 1, 18, 1, 35, 37.5)
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2016-03-23 06:07:19 +01:00
|
|
|
|
if jd is not None:
|
2020-06-07 21:06:15 +02:00
|
|
|
|
return self.tai_jd(jd) # deprecate someday
|
|
|
|
|
a = _to_array
|
|
|
|
|
whole = julian_day(a(year), a(month), a(day)) - 0.5
|
|
|
|
|
fraction = (a(second) + a(minute) * 60.0 + a(hour) * 3600.0) / DAY_S
|
|
|
|
|
t = Time(self, whole, fraction + tt_minus_tai)
|
|
|
|
|
t.tai_fraction = fraction
|
|
|
|
|
return t
|
2018-07-09 03:31:50 +02:00
|
|
|
|
|
2020-06-07 19:38:27 +02:00
|
|
|
|
def tai_jd(self, jd, fraction=0.0):
|
|
|
|
|
"""Build a `Time` from a TAI Julian date."""
|
|
|
|
|
whole, fraction2 = divmod(_to_array(jd), 1.0)
|
|
|
|
|
fraction2 += fraction
|
|
|
|
|
t = Time(self, whole, fraction2 + tt_minus_tai)
|
|
|
|
|
t.tai_fraction = fraction2
|
2016-03-23 06:07:19 +01:00
|
|
|
|
return t
|
2016-02-24 01:59:44 +01:00
|
|
|
|
|
2016-03-21 05:27:32 +01:00
|
|
|
|
def tt(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
|
2016-03-23 06:07:19 +01:00
|
|
|
|
jd=None):
|
2018-07-09 03:31:50 +02:00
|
|
|
|
"""Build a `Time` from a TT calendar date.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
Supply the Terrestrial Time (TT) as a proleptic Gregorian
|
|
|
|
|
calendar date:
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
>>> t = ts.tt(2014, 1, 18, 1, 35, 37.5)
|
|
|
|
|
>>> t.tt
|
|
|
|
|
2456675.56640625
|
|
|
|
|
>>> t.tt_calendar()
|
|
|
|
|
(2014, 1, 18, 1, 35, 37.5)
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2016-03-23 06:07:19 +01:00
|
|
|
|
if jd is not None:
|
2020-06-07 21:06:15 +02:00
|
|
|
|
return self.tt_jd(jd) # deprecate someday
|
|
|
|
|
a = _to_array
|
|
|
|
|
whole = julian_day(a(year), a(month), a(day)) - 0.5
|
|
|
|
|
fraction = (a(second) + a(minute) * 60.0 + a(hour) * 3600.0) / DAY_S
|
|
|
|
|
return Time(self, whole, fraction)
|
2016-02-23 14:37:35 +01:00
|
|
|
|
|
2020-06-07 17:47:06 +02:00
|
|
|
|
def tt_jd(self, jd, fraction=0.0):
|
2020-06-07 19:38:27 +02:00
|
|
|
|
"""Build a `Time` from a TT Julian date."""
|
2020-06-07 19:10:03 +02:00
|
|
|
|
whole, fraction2 = divmod(_to_array(jd), 1.0)
|
|
|
|
|
fraction2 += fraction
|
|
|
|
|
return Time(self, whole, fraction2)
|
2018-07-09 03:31:50 +02:00
|
|
|
|
|
2016-03-21 05:27:32 +01:00
|
|
|
|
def tdb(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
|
2016-03-23 06:07:19 +01:00
|
|
|
|
jd=None):
|
2018-07-09 03:31:50 +02:00
|
|
|
|
"""Build a `Time` from a TDB calendar date.
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
Supply the Barycentric Dynamical Time (TDB) as a proleptic
|
|
|
|
|
Gregorian calendar date:
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
>>> t = ts.tdb(2014, 1, 18, 1, 35, 37.5)
|
|
|
|
|
>>> t.tdb
|
|
|
|
|
2456675.56640625
|
2016-03-08 05:32:00 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2016-03-23 06:07:19 +01:00
|
|
|
|
if jd is not None:
|
|
|
|
|
tdb = jd
|
2016-03-21 05:27:32 +01:00
|
|
|
|
else:
|
2018-03-25 19:08:29 +02:00
|
|
|
|
tdb = julian_date(
|
|
|
|
|
_to_array(year), _to_array(month), _to_array(day),
|
|
|
|
|
_to_array(hour), _to_array(minute), _to_array(second),
|
|
|
|
|
)
|
2016-02-24 01:59:44 +01:00
|
|
|
|
tdb = _to_array(tdb)
|
2020-05-12 16:09:52 +02:00
|
|
|
|
t = Time(self, tdb, - tdb_minus_tt(tdb) / DAY_S)
|
2016-03-23 06:07:19 +01:00
|
|
|
|
return t
|
2016-02-23 14:37:35 +01:00
|
|
|
|
|
2020-06-04 21:27:31 +02:00
|
|
|
|
def tdb_jd(self, jd, fraction=0.0):
|
2020-06-07 19:38:27 +02:00
|
|
|
|
"""Build `Time` from a Barycentric Dynamical Time (TDB) Julian date."""
|
2020-06-07 19:10:03 +02:00
|
|
|
|
whole, fraction2 = divmod(jd, 1.0)
|
|
|
|
|
fraction2 += fraction
|
2020-06-07 19:38:27 +02:00
|
|
|
|
t = Time(self, whole, fraction2 - tdb_minus_tt(jd, fraction) / DAY_S)
|
2020-06-07 19:10:03 +02:00
|
|
|
|
t.tdb_fraction = fraction2
|
2018-07-09 03:31:50 +02:00
|
|
|
|
return t
|
|
|
|
|
|
2018-05-20 20:13:06 +02:00
|
|
|
|
def ut1(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
|
|
|
|
|
jd=None):
|
2018-07-09 03:31:50 +02:00
|
|
|
|
"""Build a `Time` from a UT1 calendar date.
|
2018-05-20 20:13:06 +02:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
Supply the Universal Time (UT1) as a proleptic Gregorian
|
|
|
|
|
calendar date:
|
2018-05-20 20:13:06 +02:00
|
|
|
|
|
2018-07-09 03:31:50 +02:00
|
|
|
|
>>> t = ts.ut1(2014, 1, 18, 1, 35, 37.5)
|
|
|
|
|
>>> t.ut1
|
|
|
|
|
2456675.56640625
|
2018-05-20 20:13:06 +02:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if jd is not None:
|
|
|
|
|
ut1 = jd
|
|
|
|
|
else:
|
|
|
|
|
ut1 = julian_date(
|
|
|
|
|
_to_array(year), _to_array(month), _to_array(day),
|
|
|
|
|
_to_array(hour), _to_array(minute), _to_array(second),
|
|
|
|
|
)
|
2018-07-09 03:31:50 +02:00
|
|
|
|
return self.ut1_jd(ut1)
|
|
|
|
|
|
2020-07-15 12:24:48 +02:00
|
|
|
|
def ut1_jd(self, jd):
|
2020-06-07 19:38:27 +02:00
|
|
|
|
"""Build a `Time` from a UT1 Julian date."""
|
2018-07-09 03:31:50 +02:00
|
|
|
|
ut1 = _to_array(jd)
|
2018-05-20 20:13:06 +02:00
|
|
|
|
|
|
|
|
|
# Estimate TT = UT1, to get a rough Delta T estimate.
|
|
|
|
|
tt_approx = ut1
|
|
|
|
|
delta_t_approx = interpolate_delta_t(self.delta_t_table, tt_approx)
|
|
|
|
|
|
|
|
|
|
# Use the rough Delta T to make a much better estimate of TT,
|
|
|
|
|
# then generate an even better Delta T.
|
|
|
|
|
tt_approx = ut1 + delta_t_approx / DAY_S
|
|
|
|
|
delta_t_approx = interpolate_delta_t(self.delta_t_table, tt_approx)
|
|
|
|
|
|
|
|
|
|
# We can now estimate TT with an error of < 1e-9 seconds within
|
|
|
|
|
# 10 centuries of either side of the present; for details, see:
|
|
|
|
|
# https://github.com/skyfielders/astronomy-notebooks
|
|
|
|
|
# and look for the notebook "error-in-timescale-ut1.ipynb".
|
2020-07-15 12:24:48 +02:00
|
|
|
|
delta_t_approx /= DAY_S
|
|
|
|
|
t = Time(self, ut1, delta_t_approx)
|
|
|
|
|
t.ut1_fraction = 0.0
|
2018-05-20 20:13:06 +02:00
|
|
|
|
return t
|
|
|
|
|
|
2016-03-25 13:28:37 +01:00
|
|
|
|
def from_astropy(self, t):
|
2018-07-09 03:31:50 +02:00
|
|
|
|
"""Build a Skyfield `Time` from an AstroPy time object."""
|
2016-03-25 13:28:37 +01:00
|
|
|
|
return self.tt(jd=t.tt.jd)
|
|
|
|
|
|
2016-03-23 06:07:19 +01:00
|
|
|
|
class Time(object):
|
|
|
|
|
"""A single moment in history, or an array of several moments.
|
2013-08-07 02:45:13 +02:00
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
You will not typically instantiate this class yourself, but will
|
2016-03-28 18:34:30 +02:00
|
|
|
|
rely on a Skyfield ``Timescale`` object to build dates for you:
|
2015-04-14 00:02:15 +02:00
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
>>> t = ts.utc(1980, 1, 1)
|
|
|
|
|
>>> print(t)
|
2020-05-10 19:19:47 +02:00
|
|
|
|
<Time tt=2444239.5005924073>
|
2016-03-28 18:34:30 +02:00
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
Times are represented internally by a pair of floating point Julian
|
|
|
|
|
dates, but can be converted to other formats by using the many
|
|
|
|
|
methods that time objects make available.
|
2013-08-07 02:45:13 +02:00
|
|
|
|
|
|
|
|
|
"""
|
2020-06-06 18:17:45 +02:00
|
|
|
|
psi_correction = 0.0 # TODO: temporarily unsupported
|
|
|
|
|
eps_correction = 0.0 # TODO: temporarily unsupported
|
2016-05-12 11:09:53 +02:00
|
|
|
|
|
2020-05-12 16:09:52 +02:00
|
|
|
|
def __init__(self, ts, tt, tt_fraction=0.0):
|
2016-03-02 04:15:28 +01:00
|
|
|
|
self.ts = ts
|
2020-06-07 19:10:03 +02:00
|
|
|
|
self.whole = tt
|
|
|
|
|
self.tt_fraction = tt_fraction
|
2015-11-30 00:29:40 +01:00
|
|
|
|
self.shape = getattr(tt, 'shape', ())
|
|
|
|
|
|
2015-10-19 18:55:31 +02:00
|
|
|
|
def __len__(self):
|
|
|
|
|
return self.shape[0]
|
|
|
|
|
|
2014-06-16 00:09:54 +02:00
|
|
|
|
def __repr__(self):
|
2018-03-17 21:02:36 +01:00
|
|
|
|
size = getattr(self.tt, 'size', -1)
|
2018-09-09 18:32:21 +02:00
|
|
|
|
if size > 3:
|
|
|
|
|
rstr = '<Time tt=[{0} ... {1}] len={2}>'
|
2020-05-22 13:56:07 +02:00
|
|
|
|
return rstr.format(self.tt[0], self.tt[-1], size)
|
2018-03-17 21:02:36 +01:00
|
|
|
|
else:
|
2020-05-22 13:59:41 +02:00
|
|
|
|
return ('<Time tt={0}>'.format(self.tt)
|
|
|
|
|
.replace('[ ', '[').replace(' ', ' '))
|
2014-06-16 00:09:54 +02:00
|
|
|
|
|
2014-01-24 00:30:45 +01:00
|
|
|
|
def __getitem__(self, index):
|
|
|
|
|
# TODO: also copy cached matrices?
|
2018-09-09 14:27:36 +02:00
|
|
|
|
# TODO: raise non-IndexError exception if this Time is not an array;
|
|
|
|
|
# otherwise, a `for` loop over it will not raise an error.
|
2020-05-12 16:09:52 +02:00
|
|
|
|
t = Time(self.ts, self.whole[index], self.tt_fraction[index])
|
2020-07-17 15:41:48 +02:00
|
|
|
|
for name in 'tai_fraction', 'tdb_fraction', 'ut1_fraction':
|
2014-01-24 00:30:45 +01:00
|
|
|
|
value = getattr(self, name, None)
|
|
|
|
|
if value is not None:
|
|
|
|
|
if getattr(value, 'shape', None):
|
|
|
|
|
value = value[index]
|
2016-03-25 15:32:58 +01:00
|
|
|
|
setattr(t, name, value)
|
|
|
|
|
return t
|
2014-01-24 00:30:45 +01:00
|
|
|
|
|
2014-01-16 08:41:15 +01:00
|
|
|
|
def astimezone(self, tz):
|
2016-03-28 18:34:30 +02:00
|
|
|
|
"""Convert to a Python ``datetime`` in a ``pytz`` provided timezone.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
Convert this time to a ``datetime`` in the timezone ``tz``,
|
|
|
|
|
which should be one of the timezones provided by the third-party
|
2016-03-23 06:25:28 +01:00
|
|
|
|
``pytz`` package. If this time is an array, then an array of
|
|
|
|
|
datetimes is returned instead of a single value.
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
dt, leap_second = self.astimezone_and_leap_second(tz)
|
|
|
|
|
return dt
|
|
|
|
|
|
|
|
|
|
def astimezone_and_leap_second(self, tz):
|
2016-03-28 18:34:30 +02:00
|
|
|
|
"""Convert to a Python ``datetime`` and leap second in a timezone.
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
Convert this time to a Python ``datetime`` and a leap second::
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
2016-03-25 15:32:58 +01:00
|
|
|
|
dt, leap_second = t.astimezone_and_leap_second(tz)
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
The argument ``tz`` should be a timezone from the third-party
|
2014-11-20 22:56:12 +01:00
|
|
|
|
``pytz`` package, which must be installed separately. The date
|
|
|
|
|
and time returned will be for that time zone.
|
|
|
|
|
|
|
|
|
|
The leap second value is provided because a Python ``datetime``
|
|
|
|
|
can only number seconds ``0`` through ``59``, but leap seconds
|
|
|
|
|
have a designation of at least ``60``. The leap second return
|
|
|
|
|
value will normally be ``0``, but will instead be ``1`` if the
|
|
|
|
|
date and time are a UTC leap second. Add the leap second value
|
|
|
|
|
to the ``second`` field of the ``datetime`` to learn the real
|
|
|
|
|
name of the second.
|
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
If this time is an array, then an array of ``datetime`` objects
|
|
|
|
|
and an array of leap second integers is returned, instead of a
|
|
|
|
|
single value each.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2014-11-05 01:30:38 +01:00
|
|
|
|
dt, leap_second = self.utc_datetime_and_leap_second()
|
2014-06-17 15:11:02 +02:00
|
|
|
|
normalize = getattr(tz, 'normalize', None)
|
|
|
|
|
if self.shape and normalize is not None:
|
2014-11-20 22:56:12 +01:00
|
|
|
|
dt = array([normalize(d.astimezone(tz)) for d in dt])
|
2014-06-17 15:11:02 +02:00
|
|
|
|
elif self.shape:
|
2014-11-20 22:56:12 +01:00
|
|
|
|
dt = array([d.astimezone(tz) for d in dt])
|
2014-06-17 15:11:02 +02:00
|
|
|
|
elif normalize is not None:
|
2014-01-19 07:48:08 +01:00
|
|
|
|
dt = normalize(dt.astimezone(tz))
|
2014-06-17 15:11:02 +02:00
|
|
|
|
else:
|
|
|
|
|
dt = dt.astimezone(tz)
|
2014-01-19 07:48:08 +01:00
|
|
|
|
return dt, leap_second
|
2014-01-16 08:41:15 +01:00
|
|
|
|
|
2014-07-07 12:18:57 +02:00
|
|
|
|
def toordinal(self):
|
2016-03-28 18:34:30 +02:00
|
|
|
|
"""Return the proleptic Gregorian ordinal of the UTC date.
|
2014-07-07 12:18:57 +02:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
This method makes Skyfield `Time` objects compatible with Python
|
|
|
|
|
`datetime`_ objects, which also provide a ``toordinal()``
|
2016-03-23 06:07:19 +01:00
|
|
|
|
method. Thanks to this method, a `Time` can often be used
|
2014-08-19 00:09:14 +02:00
|
|
|
|
directly as a coordinate for a plot.
|
2014-07-07 12:18:57 +02:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
return self._utc_float() - 1721424.5
|
|
|
|
|
|
2014-11-11 16:34:26 +01:00
|
|
|
|
def utc_datetime(self):
|
2016-03-30 16:34:08 +02:00
|
|
|
|
"""Convert to a Python ``datetime`` in UTC.
|
2014-11-11 16:34:26 +01:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
If the third-party `pytz`_ package is available, then its
|
|
|
|
|
``utc`` timezone will be used as the timezone of the returned
|
|
|
|
|
`datetime`_. Otherwise, an equivalent Skyfield ``utc`` timezone
|
2019-11-08 01:52:39 +01:00
|
|
|
|
object is used.
|
|
|
|
|
|
|
|
|
|
If this time is an array, then a sequence of datetimes is
|
|
|
|
|
returned instead of a single value.
|
2014-11-11 16:34:26 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2014-11-20 22:56:12 +01:00
|
|
|
|
dt, leap_second = self.utc_datetime_and_leap_second()
|
2014-11-11 16:34:26 +01:00
|
|
|
|
return dt
|
|
|
|
|
|
2014-11-05 01:30:38 +01:00
|
|
|
|
def utc_datetime_and_leap_second(self):
|
2016-03-30 16:34:08 +02:00
|
|
|
|
"""Convert to a Python ``datetime`` in UTC, plus a leap second value.
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
Convert this time to a `datetime`_ object and a leap second::
|
2014-11-20 22:56:12 +01:00
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
dt, leap_second = t.utc_datetime_and_leap_second()
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
2016-03-30 16:34:08 +02:00
|
|
|
|
If the third-party `pytz`_ package is available, then its
|
2014-11-05 01:30:38 +01:00
|
|
|
|
``utc`` timezone will be used as the timezone of the return
|
2014-11-20 22:56:12 +01:00
|
|
|
|
value. Otherwise, Skyfield uses its own ``utc`` timezone.
|
|
|
|
|
|
|
|
|
|
The leap second value is provided because a Python ``datetime``
|
|
|
|
|
can only number seconds ``0`` through ``59``, but leap seconds
|
|
|
|
|
have a designation of at least ``60``. The leap second return
|
|
|
|
|
value will normally be ``0``, but will instead be ``1`` if the
|
|
|
|
|
date and time are a UTC leap second. Add the leap second value
|
|
|
|
|
to the ``second`` field of the ``datetime`` to learn the real
|
|
|
|
|
name of the second.
|
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
If this time is an array, then an array of ``datetime`` objects
|
|
|
|
|
and an array of leap second integers is returned, instead of a
|
|
|
|
|
single value each.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2014-06-17 15:11:52 +02:00
|
|
|
|
year, month, day, hour, minute, second = self._utc_tuple(
|
2020-05-10 19:19:47 +02:00
|
|
|
|
_half_microsecond)
|
2014-01-16 08:41:15 +01:00
|
|
|
|
second, fraction = divmod(second, 1.0)
|
|
|
|
|
second = second.astype(int)
|
2014-01-19 07:48:08 +01:00
|
|
|
|
leap_second = second // 60
|
2020-05-10 19:19:47 +02:00
|
|
|
|
second -= leap_second # fit within limited bounds of Python datetime
|
|
|
|
|
micro = (fraction * 1e6).astype(int)
|
2014-01-16 08:41:15 +01:00
|
|
|
|
if self.shape:
|
|
|
|
|
utcs = [utc] * self.shape[0]
|
2019-11-08 01:52:39 +01:00
|
|
|
|
argsets = zip(year, month, day, hour, minute, second, micro, utcs)
|
2014-11-11 06:18:31 +01:00
|
|
|
|
dt = array([datetime(*args) for args in argsets])
|
2014-01-16 08:41:15 +01:00
|
|
|
|
else:
|
2019-11-08 01:52:39 +01:00
|
|
|
|
dt = datetime(year, month, day, hour, minute, second, micro, utc)
|
2014-01-19 07:48:08 +01:00
|
|
|
|
return dt, leap_second
|
2014-01-16 08:41:15 +01:00
|
|
|
|
|
2018-09-23 22:48:40 +02:00
|
|
|
|
def utc_iso(self, delimiter='T', places=0):
|
2016-03-28 18:34:30 +02:00
|
|
|
|
"""Convert to an ISO 8601 string like ``2014-01-18T01:35:38Z`` in UTC.
|
2014-01-19 06:00:29 +01:00
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
If this time is an array of dates, then a sequence of strings is
|
|
|
|
|
returned instead of a single string.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2018-09-23 22:48:40 +02:00
|
|
|
|
# "places" used to be the 1st argument, so continue to allow an
|
|
|
|
|
# integer in that spot. TODO: deprecate this in Skyfield 2.0
|
|
|
|
|
# and remove it in 3.0.
|
|
|
|
|
if isinstance(delimiter, int):
|
|
|
|
|
places = delimiter
|
|
|
|
|
delimiter = 'T'
|
|
|
|
|
|
2014-01-11 20:45:22 +01:00
|
|
|
|
if places:
|
|
|
|
|
power_of_ten = 10 ** places
|
2014-01-11 20:42:28 +01:00
|
|
|
|
offset = _half_second / power_of_ten
|
2014-06-17 15:11:52 +02:00
|
|
|
|
year, month, day, hour, minute, second = self._utc_tuple(offset)
|
2014-01-26 22:59:31 +01:00
|
|
|
|
second, fraction = divmod(second, 1.0)
|
|
|
|
|
fraction *= power_of_ten
|
2018-09-23 22:48:40 +02:00
|
|
|
|
format = '%04d-%02d-%02d{0}%02d:%02d:%02d.%0{1}dZ'.format(
|
|
|
|
|
delimiter, places)
|
2014-01-26 22:59:31 +01:00
|
|
|
|
args = (year, month, day, hour, minute, second, fraction)
|
2014-01-11 20:42:28 +01:00
|
|
|
|
else:
|
2018-09-23 23:01:39 +02:00
|
|
|
|
format = '%04d-%02d-%02d{0}%02d:%02d:%02dZ'.format(delimiter)
|
2014-06-17 15:11:52 +02:00
|
|
|
|
args = self._utc_tuple(_half_second)
|
2014-01-11 22:28:49 +01:00
|
|
|
|
|
|
|
|
|
if self.shape:
|
|
|
|
|
return [format % tup for tup in zip(*args)]
|
|
|
|
|
else:
|
|
|
|
|
return format % args
|
2014-01-11 17:19:22 +01:00
|
|
|
|
|
2014-01-19 06:00:29 +01:00
|
|
|
|
def utc_jpl(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""Convert to a string like ``A.D. 2014-Jan-18 01:35:37.5000 UT``.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
2014-08-19 00:09:14 +02:00
|
|
|
|
Returns a string for this date and time in UTC, in the format
|
2016-03-23 06:25:28 +01:00
|
|
|
|
used by the JPL HORIZONS system. If this time is an array of
|
|
|
|
|
dates, then a sequence of strings is returned instead of a
|
|
|
|
|
single string.
|
2014-01-19 06:00:29 +01:00
|
|
|
|
|
2014-01-21 06:20:02 +01:00
|
|
|
|
"""
|
2014-01-19 06:00:29 +01:00
|
|
|
|
offset = _half_second / 1e4
|
2014-06-17 15:11:52 +02:00
|
|
|
|
year, month, day, hour, minute, second = self._utc_tuple(offset)
|
2014-01-26 22:59:31 +01:00
|
|
|
|
second, fraction = divmod(second, 1.0)
|
|
|
|
|
fraction *= 1e4
|
|
|
|
|
bc = year < 1
|
|
|
|
|
year = abs(year - bc)
|
2014-01-26 22:37:14 +01:00
|
|
|
|
era = where(bc, 'B.C.', 'A.D.')
|
|
|
|
|
format = '%s %04d-%s-%02d %02d:%02d:%02d.%04d UT'
|
2014-01-26 22:59:31 +01:00
|
|
|
|
args = (era, year, _months[month], day, hour, minute, second, fraction)
|
2014-01-19 06:00:29 +01:00
|
|
|
|
|
|
|
|
|
if self.shape:
|
|
|
|
|
return [format % tup for tup in zip(*args)]
|
|
|
|
|
else:
|
|
|
|
|
return format % args
|
|
|
|
|
|
2014-01-11 17:19:22 +01:00
|
|
|
|
def utc_strftime(self, format):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""Format the UTC time using a Python datetime formatting string.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
|
|
|
|
This internally calls the Python ``strftime()`` routine from the
|
|
|
|
|
Standard Library ``time()`` module, for which you can find a
|
2016-03-23 06:25:28 +01:00
|
|
|
|
quick reference at ``http://strftime.org/``. If this object is
|
|
|
|
|
an array of times, then a sequence of strings is returned
|
|
|
|
|
instead of a single string.
|
2014-01-21 06:20:02 +01:00
|
|
|
|
|
2020-05-10 14:50:57 +02:00
|
|
|
|
If the smallest time unit in your format is minutes or seconds,
|
|
|
|
|
then the time is rounded to the nearest minute or second.
|
|
|
|
|
Otherwise the value is truncated rather than rounded.
|
|
|
|
|
|
2014-01-21 06:20:02 +01:00
|
|
|
|
"""
|
2020-05-10 14:50:57 +02:00
|
|
|
|
if _format_uses_milliseconds(format):
|
|
|
|
|
rounding = 0.0
|
|
|
|
|
elif _format_uses_seconds(format):
|
|
|
|
|
rounding = _half_second
|
|
|
|
|
elif _format_uses_minutes(format):
|
|
|
|
|
rounding = _half_minute
|
|
|
|
|
else:
|
|
|
|
|
rounding = 0.0
|
|
|
|
|
|
|
|
|
|
tup = self._utc_tuple(rounding)
|
2014-01-26 22:54:49 +01:00
|
|
|
|
year, month, day, hour, minute, second = tup
|
|
|
|
|
second = second.astype(int)
|
2020-02-02 12:32:27 +01:00
|
|
|
|
# TODO: _utc_float() repeats the same work as _utc_tuple() above
|
|
|
|
|
weekday = (self._utc_float() + 0.5).astype(int) % 7
|
2014-01-26 22:54:49 +01:00
|
|
|
|
zero = zeros_like(year)
|
2020-02-02 12:32:27 +01:00
|
|
|
|
tup = (year, month, day, hour, minute, second, weekday, zero, zero)
|
2014-01-11 22:54:24 +01:00
|
|
|
|
if self.shape:
|
|
|
|
|
return [strftime(format, item) for item in zip(*tup)]
|
|
|
|
|
else:
|
|
|
|
|
return strftime(format, tup)
|
2014-01-11 17:19:22 +01:00
|
|
|
|
|
2014-06-17 15:11:52 +02:00
|
|
|
|
def _utc_tuple(self, offset=0.0):
|
2014-01-11 17:19:22 +01:00
|
|
|
|
"""Return UTC as (year, month, day, hour, minute, second.fraction).
|
|
|
|
|
|
|
|
|
|
The `offset` is added to the UTC time before it is split into
|
2014-01-11 17:51:58 +01:00
|
|
|
|
its components. This is useful if the user is going to round
|
|
|
|
|
the result before displaying it. If the result is going to be
|
|
|
|
|
displayed as seconds, for example, set `offset` to half a second
|
|
|
|
|
and then throw away the fraction; if the result is going to be
|
|
|
|
|
displayed as minutes, set `offset` to thirty seconds and then
|
|
|
|
|
throw away the seconds; and so forth.
|
2014-01-11 17:19:22 +01:00
|
|
|
|
|
|
|
|
|
"""
|
2020-05-10 16:03:26 +02:00
|
|
|
|
ts = self.ts
|
2014-01-11 17:19:22 +01:00
|
|
|
|
tai = self.tai + offset
|
2020-05-10 16:03:26 +02:00
|
|
|
|
i = searchsorted(ts._leap_reverse_dates, tai, 'right')
|
|
|
|
|
j = tai - ts.leap_offsets[i] / DAY_S
|
2020-05-10 19:19:47 +02:00
|
|
|
|
|
2020-05-12 16:09:52 +02:00
|
|
|
|
whole1, fraction = divmod(self.whole, 1.0)
|
|
|
|
|
whole2, fraction = divmod(offset - tt_minus_tai
|
|
|
|
|
+ fraction + self.tt_fraction
|
2020-05-10 19:19:47 +02:00
|
|
|
|
- ts.leap_offsets[i] / DAY_S + 0.5, 1.0)
|
|
|
|
|
whole = (whole1 + whole2).astype(int)
|
|
|
|
|
|
2014-01-26 23:30:48 +01:00
|
|
|
|
year, month, day = calendar_date(whole)
|
|
|
|
|
hour, hfrac = divmod(fraction * 24.0, 1.0)
|
|
|
|
|
minute, second = divmod(hfrac * 3600.0, 60.0)
|
2020-05-10 16:03:26 +02:00
|
|
|
|
is_leap_second = j < ts.leap_dates[i-1]
|
2014-01-26 23:30:48 +01:00
|
|
|
|
second += is_leap_second
|
|
|
|
|
return year, month, day, hour.astype(int), minute.astype(int), second
|
2014-01-11 17:19:22 +01:00
|
|
|
|
|
2014-06-17 15:12:27 +02:00
|
|
|
|
def _utc_float(self):
|
|
|
|
|
"""Return UTC as a floating point Julian date."""
|
|
|
|
|
tai = self.tai
|
2020-05-10 16:03:26 +02:00
|
|
|
|
ts = self.ts
|
|
|
|
|
i = searchsorted(ts._leap_reverse_dates, tai, 'right')
|
|
|
|
|
return tai - ts.leap_offsets[i] / DAY_S
|
2014-06-17 15:12:27 +02:00
|
|
|
|
|
2016-03-28 18:34:30 +02:00
|
|
|
|
def tai_calendar(self):
|
2015-11-29 23:33:03 +01:00
|
|
|
|
"""Return TAI as a tuple (year, month, day, hour, minute, second)."""
|
2020-06-07 21:06:15 +02:00
|
|
|
|
return calendar_tuple(self.whole, self.tai_fraction)
|
2015-11-29 23:33:03 +01:00
|
|
|
|
|
2016-03-28 18:34:30 +02:00
|
|
|
|
def tt_calendar(self):
|
2015-11-29 23:33:03 +01:00
|
|
|
|
"""Return TT as a tuple (year, month, day, hour, minute, second)."""
|
2020-06-07 21:06:15 +02:00
|
|
|
|
return calendar_tuple(self.whole, self.tt_fraction)
|
2015-11-29 23:33:03 +01:00
|
|
|
|
|
2018-05-15 01:58:48 +02:00
|
|
|
|
# Convenient caching of several expensive functions of time.
|
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def M(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""3×3 rotation matrix: ICRS → equinox of this date."""
|
|
|
|
|
|
|
|
|
|
# Compute N and P instead of asking for self.N and self.P to
|
|
|
|
|
# avoid keeping copies of them, since Skyfield itself never uses
|
|
|
|
|
# them again once M has been computed. But we do check in case
|
|
|
|
|
# a user has forced them to be built already.
|
2020-06-07 12:25:43 +02:00
|
|
|
|
|
|
|
|
|
d = self.__dict__
|
|
|
|
|
|
|
|
|
|
P = d.get('P')
|
|
|
|
|
if P is None:
|
2020-06-07 17:12:11 +02:00
|
|
|
|
P = self.precession_matrix()
|
2020-06-07 12:25:43 +02:00
|
|
|
|
|
|
|
|
|
N = d.get('N')
|
|
|
|
|
if N is None:
|
2020-06-07 17:12:11 +02:00
|
|
|
|
N = self.nutation_matrix()
|
2020-06-07 12:25:43 +02:00
|
|
|
|
|
2020-06-12 20:18:08 +02:00
|
|
|
|
return mxmxm(N, P, B)
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def MT(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""3×3 rotation matrix: equinox of this date → ICRS."""
|
2018-07-09 02:42:00 +02:00
|
|
|
|
return rollaxis(self.M, 1)
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
2018-09-05 22:55:37 +02:00
|
|
|
|
@reify
|
|
|
|
|
def C(self):
|
2018-09-10 03:39:26 +02:00
|
|
|
|
# Calculate the Equation of Origins in cycles
|
2018-09-05 22:55:37 +02:00
|
|
|
|
eq_origins = (earth_rotation_angle(self.ut1) - self.gast / 24.0)
|
|
|
|
|
R = rot_z(2 * pi * eq_origins)
|
2020-06-12 20:18:08 +02:00
|
|
|
|
return mxm(R, self.M)
|
2018-09-05 22:55:37 +02:00
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def CT(self):
|
|
|
|
|
return rollaxis(self.C, 1)
|
|
|
|
|
|
2020-06-06 18:17:45 +02:00
|
|
|
|
@reify
|
2020-06-07 23:09:13 +02:00
|
|
|
|
def _nutation_angles_radians(self):
|
2020-06-06 18:17:45 +02:00
|
|
|
|
# TODO: add psi and eps corrections support back in here, rather
|
|
|
|
|
# than at points of use.
|
|
|
|
|
return iau2000a_radians(self)
|
|
|
|
|
|
|
|
|
|
def _nutation_angles(self, angles):
|
2020-06-08 16:46:59 +02:00
|
|
|
|
# Sample code shared with early adopters suggested that setting
|
|
|
|
|
# this attribute manually could avoid the expense of IAU 2000A,
|
|
|
|
|
# so this setter continues to support the pattern.
|
2020-06-06 18:17:45 +02:00
|
|
|
|
|
|
|
|
|
d_psi, d_eps = angles
|
2020-06-07 23:09:13 +02:00
|
|
|
|
self._nutation_angles_radians = (
|
2020-06-06 18:17:45 +02:00
|
|
|
|
d_psi / 1e7 * ASEC2RAD,
|
|
|
|
|
d_eps / 1e7 * ASEC2RAD,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_nutation_angles = property(None, _nutation_angles)
|
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def _mean_obliquity_radians(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
# Cached because it is used to compute both gast and N.
|
2020-06-06 18:17:45 +02:00
|
|
|
|
return mean_obliquity(self.tdb) * ASEC2RAD
|
|
|
|
|
|
2018-05-15 01:58:48 +02:00
|
|
|
|
# Conversion between timescales.
|
|
|
|
|
|
2018-05-21 02:59:15 +02:00
|
|
|
|
@reify
|
|
|
|
|
def J(self):
|
|
|
|
|
"""Decimal Julian years centered on J2000.0 = TT 2000 January 1 12h."""
|
2020-06-07 17:47:06 +02:00
|
|
|
|
return (self.whole - 1721045.0 + self.tt_fraction) / 365.25
|
2018-05-21 02:59:15 +02:00
|
|
|
|
|
2018-05-15 01:58:48 +02:00
|
|
|
|
@reify
|
|
|
|
|
def utc(self):
|
2020-02-02 13:48:43 +01:00
|
|
|
|
utc = self._utc_tuple()
|
2020-05-24 21:05:32 +02:00
|
|
|
|
return array(utc).view(CalendarArray) if self.shape else CalendarTuple(*utc)
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
@reify
|
|
|
|
|
def tai_fraction(self):
|
|
|
|
|
return self.tt_fraction - tt_minus_tai
|
|
|
|
|
|
2018-05-15 01:58:48 +02:00
|
|
|
|
@reify
|
2020-05-12 16:09:52 +02:00
|
|
|
|
def tdb_fraction(self):
|
2020-06-07 19:38:27 +02:00
|
|
|
|
fr = self.tt_fraction
|
|
|
|
|
return fr + tdb_minus_tt(self.whole, fr) / DAY_S
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
|
|
|
|
@reify
|
2020-07-15 12:24:48 +02:00
|
|
|
|
def ut1_fraction(self):
|
|
|
|
|
# Calling "self.delta_t" would cache a useless intermediate value, so:
|
|
|
|
|
table = self.ts.delta_t_table
|
|
|
|
|
delta_t = interpolate_delta_t(table, self.tt)
|
|
|
|
|
return self.tt_fraction - delta_t / DAY_S
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def delta_t(self):
|
|
|
|
|
table = self.ts.delta_t_table
|
2018-07-09 02:42:00 +02:00
|
|
|
|
return interpolate_delta_t(table, self.tt)
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
2018-05-21 02:49:09 +02:00
|
|
|
|
@reify
|
|
|
|
|
def dut1(self):
|
2020-05-12 16:09:52 +02:00
|
|
|
|
# TODO: add test, and then streamline this computation.
|
2018-05-21 02:49:09 +02:00
|
|
|
|
return (self.tt - self._utc_float()) * DAY_S - self.delta_t
|
|
|
|
|
|
2018-05-15 01:58:48 +02:00
|
|
|
|
@reify
|
|
|
|
|
def gmst(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""Greenwich Mean Sidereal Time as decimal hours."""
|
2018-07-09 02:42:00 +02:00
|
|
|
|
return sidereal_time(self)
|
2018-05-15 01:58:48 +02:00
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def gast(self):
|
2020-06-07 17:12:11 +02:00
|
|
|
|
"""Greenwich Apparent Sidereal Time as decimal hours."""
|
2020-06-07 23:09:13 +02:00
|
|
|
|
d_psi, _ = self._nutation_angles_radians
|
2020-06-06 03:12:26 +02:00
|
|
|
|
tt = self.tt
|
2020-06-06 03:54:50 +02:00
|
|
|
|
# TODO: move this into an eqeq function?
|
2020-06-06 17:06:26 +02:00
|
|
|
|
c_terms = equation_of_the_equinoxes_complimentary_terms(tt)
|
2020-06-06 16:38:50 +02:00
|
|
|
|
eq_eq = d_psi * cos(self._mean_obliquity_radians) + c_terms
|
2020-06-06 17:06:26 +02:00
|
|
|
|
return self.gmst + eq_eq / tau * 24.0
|
2018-09-09 19:08:49 +02:00
|
|
|
|
|
2020-05-11 00:45:32 +02:00
|
|
|
|
# Low-precision floats generated from internal float pairs.
|
|
|
|
|
|
2020-07-17 15:41:48 +02:00
|
|
|
|
@property
|
|
|
|
|
def tai(self):
|
|
|
|
|
return self.whole + self.tai_fraction
|
|
|
|
|
|
2020-05-11 00:45:32 +02:00
|
|
|
|
@property
|
|
|
|
|
def tt(self):
|
2020-05-12 16:09:52 +02:00
|
|
|
|
return self.whole + self.tt_fraction
|
2020-05-11 00:45:32 +02:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def tdb(self):
|
2020-05-12 16:09:52 +02:00
|
|
|
|
return self.whole + self.tdb_fraction
|
2020-05-11 00:45:32 +02:00
|
|
|
|
|
2020-07-15 12:24:48 +02:00
|
|
|
|
@property
|
|
|
|
|
def ut1(self):
|
|
|
|
|
return self.whole + self.ut1_fraction
|
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
# Crucial functions of time.
|
|
|
|
|
|
|
|
|
|
def nutation_matrix(self):
|
|
|
|
|
"""Compute the 3×3 nutation matrix N for this date."""
|
2020-06-07 23:09:13 +02:00
|
|
|
|
d_psi, d_eps = self._nutation_angles_radians
|
2020-06-07 17:12:11 +02:00
|
|
|
|
mean_obliquity = self._mean_obliquity_radians
|
|
|
|
|
true_obliquity = mean_obliquity + d_eps
|
|
|
|
|
return build_nutation_matrix(mean_obliquity, true_obliquity, d_psi)
|
|
|
|
|
|
|
|
|
|
def precession_matrix(self):
|
|
|
|
|
"""Compute the 3×3 precession matrix P for this date."""
|
|
|
|
|
return compute_precession(self.tdb)
|
|
|
|
|
|
2020-05-11 00:45:32 +02:00
|
|
|
|
# Various dunders.
|
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
def __eq__(self, other_time):
|
2020-06-07 17:55:34 +02:00
|
|
|
|
return self.__sub__(other_time) == 0.0
|
2014-01-19 06:00:29 +01:00
|
|
|
|
|
2017-03-02 01:51:36 +01:00
|
|
|
|
def __sub__(self, other_time):
|
|
|
|
|
if not isinstance(other_time, Time):
|
|
|
|
|
return NotImplemented
|
2020-07-02 18:47:07 +02:00
|
|
|
|
return ((self.whole - other_time.whole)
|
|
|
|
|
+ (self.tt_fraction - other_time.tt_fraction))
|
|
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
|
# Someone wanted to use Time objects with functools.lru_cache so
|
|
|
|
|
# we make this attempt to support hashability; beware that it
|
|
|
|
|
# will return the same hash for very closely spaced times that
|
|
|
|
|
# all round to the same floating point TT.
|
|
|
|
|
return hash(self.tt)
|
2017-03-02 01:51:36 +01:00
|
|
|
|
|
2020-06-07 17:12:11 +02:00
|
|
|
|
# Deprecated attributes that were once used internally, consuming
|
|
|
|
|
# memory with matrices that are never used again by Skyfield once
|
|
|
|
|
# t.M has been computed.
|
|
|
|
|
|
|
|
|
|
P = reify(precession_matrix)
|
|
|
|
|
N = reify(nutation_matrix)
|
|
|
|
|
P.__doc__ = N.__doc__ = None # omit from Sphinx documentation
|
|
|
|
|
|
|
|
|
|
@reify
|
|
|
|
|
def PT(self): return rollaxis(self.P, 1)
|
|
|
|
|
@reify
|
|
|
|
|
def NT(self): return rollaxis(self.N, 1)
|
2020-06-07 12:25:43 +02:00
|
|
|
|
|
2014-01-19 06:00:29 +01:00
|
|
|
|
def julian_day(year, month=1, day=1):
|
2014-02-02 06:50:52 +01:00
|
|
|
|
"""Given a proleptic Gregorian calendar date, return a Julian day int."""
|
2013-02-16 19:39:43 +01:00
|
|
|
|
janfeb = month < 3
|
2014-01-19 06:00:29 +01:00
|
|
|
|
return (day
|
2013-02-16 19:39:43 +01:00
|
|
|
|
+ 1461 * (year + 4800 - janfeb) // 4
|
|
|
|
|
+ 367 * (month - 2 + janfeb * 12) // 12
|
|
|
|
|
- 3 * ((year + 4900 - janfeb) // 100) // 4
|
2014-01-19 06:00:29 +01:00
|
|
|
|
- 32075)
|
|
|
|
|
|
|
|
|
|
def julian_date(year, month=1, day=1, hour=0, minute=0, second=0.0):
|
|
|
|
|
"""Given a proleptic Gregorian calendar date, return a Julian date float."""
|
|
|
|
|
return julian_day(year, month, day) - 0.5 + (
|
|
|
|
|
second + minute * 60.0 + hour * 3600.0) / DAY_S
|
2013-02-16 19:39:43 +01:00
|
|
|
|
|
2019-10-09 21:36:36 +02:00
|
|
|
|
def julian_date_of_besselian_epoch(b):
|
|
|
|
|
return 2415020.31352 + (b - 1900.0) * 365.242198781
|
|
|
|
|
|
2014-01-11 17:51:58 +01:00
|
|
|
|
def calendar_date(jd_integer):
|
|
|
|
|
"""Convert Julian Day `jd_integer` into a Gregorian (year, month, day)."""
|
|
|
|
|
|
|
|
|
|
k = jd_integer + 68569
|
|
|
|
|
n = 4 * k // 146097
|
|
|
|
|
|
|
|
|
|
k = k - (146097 * n + 3) // 4
|
|
|
|
|
m = 4000 * (k + 1) // 1461001
|
|
|
|
|
k = k - 1461 * m // 4 + 31
|
|
|
|
|
month = 80 * k // 2447
|
|
|
|
|
day = k - 2447 * month // 80
|
|
|
|
|
k = month // 11
|
|
|
|
|
|
|
|
|
|
month = month + 2 - 12 * k
|
|
|
|
|
year = 100 * (n - 49) + m + k
|
|
|
|
|
|
|
|
|
|
return year, month, day
|
|
|
|
|
|
2020-06-07 21:06:15 +02:00
|
|
|
|
def calendar_tuple(jd_float, fraction=0.0):
|
|
|
|
|
"""Return a (year, month, day, hour, minute, second.fraction) tuple."""
|
2016-08-27 22:29:13 +02:00
|
|
|
|
jd_float = _to_array(jd_float)
|
2020-06-07 21:06:15 +02:00
|
|
|
|
whole1, fraction1 = divmod(jd_float, 1.0)
|
|
|
|
|
whole2, fraction = divmod(fraction1 + fraction + 0.5, 1.0)
|
|
|
|
|
whole = (whole1 + whole2).astype(int)
|
2015-11-29 23:33:03 +01:00
|
|
|
|
year, month, day = calendar_date(whole)
|
2020-06-07 21:06:15 +02:00
|
|
|
|
hour, hfrac = divmod(fraction * 24.0, 1.0) # TODO
|
2015-11-29 23:33:03 +01:00
|
|
|
|
minute, second = divmod(hfrac * 3600.0, 60.0)
|
|
|
|
|
return year, month, day, hour.astype(int), minute.astype(int), second
|
|
|
|
|
|
2020-06-07 19:38:27 +02:00
|
|
|
|
def tdb_minus_tt(jd_tdb, fraction_tdb=0.0):
|
2014-02-02 06:27:47 +01:00
|
|
|
|
"""Computes how far TDB is in advance of TT, given TDB.
|
2013-01-30 22:23:04 +01:00
|
|
|
|
|
2014-02-02 06:27:47 +01:00
|
|
|
|
Given that the two time scales never diverge by more than 2ms, TT
|
|
|
|
|
can also be given as the argument to perform the conversion in the
|
2014-02-02 06:18:53 +01:00
|
|
|
|
other direction.
|
2013-01-30 22:23:04 +01:00
|
|
|
|
|
2014-02-02 06:18:53 +01:00
|
|
|
|
"""
|
2020-06-07 19:38:27 +02:00
|
|
|
|
t = (jd_tdb - T0 + fraction_tdb) / 36525.0
|
2013-01-30 22:23:04 +01:00
|
|
|
|
|
2014-02-02 06:18:53 +01:00
|
|
|
|
# USNO Circular 179, eq. 2.6.
|
2013-01-30 22:23:04 +01:00
|
|
|
|
return (0.001657 * sin ( 628.3076 * t + 6.2401)
|
|
|
|
|
+ 0.000022 * sin ( 575.3385 * t + 4.2970)
|
|
|
|
|
+ 0.000014 * sin (1256.6152 * t + 6.1969)
|
|
|
|
|
+ 0.000005 * sin ( 606.9777 * t + 4.0212)
|
|
|
|
|
+ 0.000005 * sin ( 52.9691 * t + 0.4444)
|
|
|
|
|
+ 0.000002 * sin ( 21.3299 * t + 5.5431)
|
|
|
|
|
+ 0.000010 * t * sin ( 628.3076 * t + 4.2490))
|
2014-01-10 19:36:04 +01:00
|
|
|
|
|
2016-03-20 21:57:28 +01:00
|
|
|
|
def interpolate_delta_t(delta_t_table, tt):
|
|
|
|
|
"""Return interpolated Delta T values for the times in `tt`.
|
|
|
|
|
|
|
|
|
|
The 2xN table should provide TT values as element 0 and
|
|
|
|
|
corresponding Delta T values for element 1. For times outside the
|
|
|
|
|
range of the table, a long-term formula is used instead.
|
|
|
|
|
|
|
|
|
|
"""
|
2016-03-20 23:51:30 +01:00
|
|
|
|
tt_array, delta_t_array = delta_t_table
|
2016-08-08 04:16:45 +02:00
|
|
|
|
delta_t = _to_array(interp(tt, tt_array, delta_t_array, nan, nan))
|
2015-11-30 00:29:40 +01:00
|
|
|
|
missing = isnan(delta_t)
|
2016-08-08 04:16:45 +02:00
|
|
|
|
|
2015-11-30 00:29:40 +01:00
|
|
|
|
if missing.any():
|
2016-08-08 04:16:45 +02:00
|
|
|
|
# Test if we are dealing with an array and proceed appropriately
|
|
|
|
|
if missing.shape:
|
|
|
|
|
tt = tt[missing]
|
|
|
|
|
delta_t[missing] = delta_t_formula_morrison_and_stephenson_2004(tt)
|
|
|
|
|
else:
|
|
|
|
|
delta_t = delta_t_formula_morrison_and_stephenson_2004(tt)
|
2015-11-30 00:29:40 +01:00
|
|
|
|
return delta_t
|
|
|
|
|
|
2015-11-29 23:33:03 +01:00
|
|
|
|
def delta_t_formula_morrison_and_stephenson_2004(tt):
|
2016-03-20 00:02:15 +01:00
|
|
|
|
"""Delta T formula from Morrison and Stephenson, 2004.
|
|
|
|
|
|
|
|
|
|
This parabola can be used to estimate the value of Delta T for dates
|
|
|
|
|
in the far past or future, for which more specific estimates are not
|
|
|
|
|
available.
|
|
|
|
|
|
|
|
|
|
"""
|
2015-11-29 23:33:03 +01:00
|
|
|
|
t = (tt - 2385800.5) / 36525.0 # centuries before or after 1820
|
|
|
|
|
return 32.0 * t * t - 20.0
|
|
|
|
|
|
2016-03-20 23:51:30 +01:00
|
|
|
|
def build_delta_t_table(delta_t_recent):
|
2016-03-20 00:02:15 +01:00
|
|
|
|
"""Build a table for interpolating Delta T.
|
|
|
|
|
|
2016-03-20 21:57:28 +01:00
|
|
|
|
Given a 2xN array of recent Delta T values, whose element 0 is a
|
|
|
|
|
sorted array of TT Julian dates and element 1 is Delta T values,
|
|
|
|
|
this routine returns a more complete table by prepending two
|
|
|
|
|
built-in data sources that ship with Skyfield as pre-built arrays:
|
2016-03-20 00:02:15 +01:00
|
|
|
|
|
|
|
|
|
* The historical values from Morrison and Stephenson (2004) which
|
|
|
|
|
the http://eclipse.gsfc.nasa.gov/SEcat5/deltat.html NASA web page
|
|
|
|
|
presents in an HTML table.
|
|
|
|
|
|
|
|
|
|
* The United States Naval Observatory ``historic_deltat.data``
|
|
|
|
|
values for Delta T over the years 1657 through 1984.
|
|
|
|
|
|
|
|
|
|
"""
|
2018-09-09 21:27:14 +02:00
|
|
|
|
ancient = load_bundled_npy('morrison_stephenson_deltat.npy')
|
|
|
|
|
historic = load_bundled_npy('historic_deltat.npy')
|
2016-03-20 00:02:15 +01:00
|
|
|
|
|
|
|
|
|
# Prefer USNO over Morrison and Stephenson where they overlap.
|
2016-03-20 21:57:28 +01:00
|
|
|
|
historic_start_time = historic[0,0]
|
|
|
|
|
i = searchsorted(ancient[0], historic_start_time)
|
|
|
|
|
bundled = concatenate([ancient[:,:i], historic], axis=1)
|
2016-03-20 00:02:15 +01:00
|
|
|
|
|
2016-03-20 21:57:28 +01:00
|
|
|
|
# Let recent data replace everything else.
|
2016-03-20 23:51:30 +01:00
|
|
|
|
recent_start_time = delta_t_recent[0,0]
|
2016-03-20 21:57:28 +01:00
|
|
|
|
i = searchsorted(bundled[0], recent_start_time)
|
|
|
|
|
row = ((0,),(0,))
|
2016-03-20 23:51:30 +01:00
|
|
|
|
table = concatenate([row, bundled[:,:i], delta_t_recent, row], axis=1)
|
2016-03-20 00:02:15 +01:00
|
|
|
|
|
2016-03-20 21:57:28 +01:00
|
|
|
|
# Create initial and final point to provide continuity with formula.
|
2016-03-20 20:48:44 +01:00
|
|
|
|
century = 36524.0
|
2016-03-20 21:57:28 +01:00
|
|
|
|
start = table[0,1] - century
|
|
|
|
|
table[:,0] = start, delta_t_formula_morrison_and_stephenson_2004(start)
|
|
|
|
|
end = table[0,-2] + century
|
|
|
|
|
table[:,-1] = end, delta_t_formula_morrison_and_stephenson_2004(end)
|
|
|
|
|
return table
|
2016-03-20 00:02:15 +01:00
|
|
|
|
|
2020-05-10 14:50:57 +02:00
|
|
|
|
_format_uses_milliseconds = re.compile(r'%[-_0^#EO]*f').search
|
|
|
|
|
_format_uses_seconds = re.compile(r'%[-_0^#EO]*[STXc]').search
|
|
|
|
|
_format_uses_minutes = re.compile(r'%[-_0^#EO]*[MR]').search
|
|
|
|
|
|
2014-01-11 21:36:29 +01:00
|
|
|
|
def _utc_datetime_to_tai(leap_dates, leap_offsets, dt):
|
2017-02-27 06:15:24 +01:00
|
|
|
|
if dt.tzinfo is None:
|
2014-01-24 00:21:47 +01:00
|
|
|
|
raise ValueError(_naive_complaint)
|
2020-07-17 15:41:48 +02:00
|
|
|
|
if dt.tzinfo is not utc:
|
|
|
|
|
dt = dt.astimezone(utc)
|
2020-05-10 19:19:47 +02:00
|
|
|
|
return _utc_to_tai(leap_dates, leap_offsets,
|
|
|
|
|
dt.year, dt.month, dt.day,
|
|
|
|
|
dt.hour, dt.minute, dt.second + dt.microsecond * 1e-6)
|
2014-01-11 21:36:29 +01:00
|
|
|
|
|
2014-01-19 14:53:44 +01:00
|
|
|
|
def _utc_date_to_tai(leap_dates, leap_offsets, d):
|
2020-05-10 19:19:47 +02:00
|
|
|
|
return _utc_to_tai(leap_dates, leap_offsets,
|
|
|
|
|
d.year, d.month, d.day, 0.0, 0.0, 0.0)
|
2014-01-19 14:53:44 +01:00
|
|
|
|
|
2020-05-10 19:19:47 +02:00
|
|
|
|
def _utc_to_tai(leap_dates, leap_offsets,
|
|
|
|
|
year, month, day, hour, minute, second):
|
2014-01-19 06:00:29 +01:00
|
|
|
|
j = julian_day(year, month, day) - 0.5
|
2014-01-11 21:36:29 +01:00
|
|
|
|
i = searchsorted(leap_dates, j, 'right')
|
2020-05-10 19:19:47 +02:00
|
|
|
|
seconds = leap_offsets[i] + second + minute * 60.0 + hour * 3600.0
|
|
|
|
|
# TODO: Is there a more efficient way to force two arrays to the same shape?
|
|
|
|
|
zeros = zeros_like(j) + zeros_like(seconds)
|
|
|
|
|
return zeros + j, zeros + seconds / DAY_S
|
2014-01-24 00:21:47 +01:00
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
_JulianDate_deprecation_message = """Skyfield no longer supports direct\
|
|
|
|
|
instantiation of JulianDate objects (which are now called Time objects)
|
2016-03-21 01:45:43 +01:00
|
|
|
|
|
|
|
|
|
If you need to quickly get an old Skyfield script working again, simply
|
|
|
|
|
downgrade to Skyfield version 0.6.1 using a command like:
|
|
|
|
|
|
|
|
|
|
pip install skyfield==0.6.1
|
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
Skyfield used to let you build JulianDate objects directly:
|
2016-03-21 01:45:43 +01:00
|
|
|
|
|
2016-03-23 06:25:28 +01:00
|
|
|
|
t = JulianDate(utc=(1980, 4, 20)) # the old way
|
2016-03-21 01:45:43 +01:00
|
|
|
|
|
|
|
|
|
But this forced Skyfield to maintain secret global copies of several
|
|
|
|
|
time scale data files, that need to be downloaded and kept up to date
|
2016-03-23 06:25:28 +01:00
|
|
|
|
for Time objects to work. Skyfield now makes this collection of data
|
|
|
|
|
files explicit, and calls the bundle of files a "Timescale" object. You
|
|
|
|
|
can create one with the "load.timescale()" method and then build times
|
|
|
|
|
using its methods, which let you either specify a calendar date or else
|
|
|
|
|
supply a raw Julian date value with the "jd" keyword:
|
2016-03-21 01:45:43 +01:00
|
|
|
|
|
|
|
|
|
from skyfield.api import load
|
2020-06-04 11:59:31 +02:00
|
|
|
|
ts = load.timescale(builtin=True)
|
2016-03-23 06:25:28 +01:00
|
|
|
|
t = ts.utc(1980, 4, 20) # the new way
|
|
|
|
|
|
|
|
|
|
t = ts.tt(jd=2444349.500592) # jd is also supported for tai, tt, tdb
|
|
|
|
|
|
|
|
|
|
See http://rhodesmill.org/skyfield/time.html for more details."""
|
|
|
|
|
|
2014-01-24 00:21:47 +01:00
|
|
|
|
|
|
|
|
|
_naive_complaint = """cannot interpret a datetime that lacks a timezone
|
|
|
|
|
|
|
|
|
|
You must either specify that your datetime is in UTC:
|
|
|
|
|
|
|
|
|
|
from skyfield.api import utc
|
2014-02-02 06:50:52 +01:00
|
|
|
|
d = datetime(..., tzinfo=utc) # to build a new datetime
|
|
|
|
|
d = d.replace(tzinfo=utc) # to fix an existing datetime
|
2014-01-24 00:21:47 +01:00
|
|
|
|
|
2020-07-17 14:18:15 +02:00
|
|
|
|
Or use a timezone object like those provided by the third-party `pytz` library:
|
2014-01-24 00:21:47 +01:00
|
|
|
|
|
|
|
|
|
from pytz import timezone
|
|
|
|
|
eastern = timezone('US/Eastern')
|
2016-03-21 01:45:43 +01:00
|
|
|
|
d = eastern.localize(datetime(2014, 1, 16, 1, 32, 9))"""
|