For #406, add Time constructors from datetimes
This was previously an implicit power of `ts.utc()` which was more difficult for users to find.
This commit is contained in:
parent
d23ce4e1ee
commit
24b0ac3700
|
@ -71,6 +71,8 @@ any given version of Skyfield will fall gradually out of date.
|
|||
.. autosummary::
|
||||
|
||||
Timescale.now
|
||||
Timescale.from_datetime
|
||||
Timescale.from_datetimes
|
||||
Timescale.utc
|
||||
Timescale.tai
|
||||
Timescale.tai_jd
|
||||
|
|
|
@ -42,8 +42,8 @@ or how early I need to rise to see the morning sky:
|
|||
next_midnight = midnight + dt.timedelta(days=1)
|
||||
|
||||
ts = load.timescale(builtin=True)
|
||||
t0 = ts.utc(midnight)
|
||||
t1 = ts.utc(next_midnight)
|
||||
t0 = ts.from_datetime(midnight)
|
||||
t1 = ts.from_datetime(next_midnight)
|
||||
eph = load('de421.bsp')
|
||||
bluffton = Topos('40.8939 N', '83.8917 W')
|
||||
f = almanac.dark_twilight_day(eph, bluffton)
|
||||
|
|
|
@ -235,7 +235,7 @@ and pass the result to Skyfield:
|
|||
|
||||
d = datetime(2014, 1, 16, 1, 32, 9)
|
||||
e = eastern.localize(d)
|
||||
t = ts.utc(e)
|
||||
t = ts.from_datetime(e)
|
||||
|
||||
And if Skyfield returns a Julian date at the end of a calculation,
|
||||
you can ask the Julian date object to build a ``datetime`` object
|
||||
|
|
|
@ -68,29 +68,36 @@ def test_timescale_utc_method_with_array_inside(ts):
|
|||
|
||||
def test_that_building_time_from_naive_datetime_raises_exception(ts):
|
||||
with assert_raises(ValueError) as info:
|
||||
ts.utc(datetime(1973, 12, 29, 23, 59, 48))
|
||||
ts.from_datetime(datetime(1973, 12, 29, 23, 59, 48))
|
||||
assert 'import timezone' in str(info.exception)
|
||||
|
||||
def test_building_time_from_single_utc_datetime(ts):
|
||||
t = ts.from_datetime(datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc))
|
||||
assert t.tai == 2442046.5
|
||||
t = ts.utc(datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc))
|
||||
assert t.tai == 2442046.5
|
||||
|
||||
def test_building_time_from_single_utc_datetime_with_timezone(ts):
|
||||
tz = timezone('US/Eastern')
|
||||
t = ts.utc(tz.localize(datetime(2020, 5, 10, 12, 44, 13, 797865)))
|
||||
t = ts.from_datetime(tz.localize(datetime(2020, 5, 10, 12, 44, 13, 797865)))
|
||||
dt, leap_second = t.utc_datetime_and_leap_second()
|
||||
assert dt == datetime(2020, 5, 10, 16, 44, 13, 797865, tzinfo=utc)
|
||||
assert leap_second == 0
|
||||
|
||||
def test_building_time_from_list_of_utc_datetimes(ts):
|
||||
t = ts.utc([
|
||||
datetimes = [
|
||||
datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc),
|
||||
datetime(1973, 12, 30, 23, 59, 48, tzinfo=utc),
|
||||
datetime(1973, 12, 31, 23, 59, 48, tzinfo=utc),
|
||||
datetime(1974, 1, 1, 23, 59, 47, tzinfo=utc),
|
||||
datetime(1974, 1, 2, 23, 59, 47, tzinfo=utc),
|
||||
datetime(1974, 1, 3, 23, 59, 47, tzinfo=utc),
|
||||
])
|
||||
]
|
||||
t = ts.from_datetimes(datetimes)
|
||||
assert list(t.tai) == [
|
||||
2442046.5, 2442047.5, 2442048.5, 2442049.5, 2442050.5, 2442051.5,
|
||||
]
|
||||
t = ts.utc(datetimes)
|
||||
assert list(t.tai) == [
|
||||
2442046.5, 2442047.5, 2442048.5, 2442049.5, 2442050.5, 2442051.5,
|
||||
]
|
||||
|
@ -162,7 +169,7 @@ def test_utc_datetime_and_leap_second(ts):
|
|||
|
||||
def test_utc_datetime_microseconds_round_trip(ts):
|
||||
dt = datetime(2020, 5, 10, 11, 50, 9, 727799, tzinfo=utc)
|
||||
t = ts.utc(dt)
|
||||
t = ts.from_datetime(dt)
|
||||
dt2, leap_second = t.utc_datetime_and_leap_second()
|
||||
assert dt2 == dt
|
||||
assert leap_second == 0
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime as dt
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from datetime import date, datetime
|
||||
|
@ -32,21 +33,18 @@ class CalendarArray(ndarray):
|
|||
@property
|
||||
def second(self): return self[5]
|
||||
|
||||
try:
|
||||
from datetime import timezone
|
||||
utc = timezone.utc
|
||||
except ImportError:
|
||||
if hasattr(dt, 'timezone'):
|
||||
utc = dt.timezone.utc
|
||||
else:
|
||||
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.
|
||||
|
||||
from datetime import timedelta, tzinfo
|
||||
|
||||
class UTC(tzinfo):
|
||||
class UTC(dt.tzinfo):
|
||||
'UTC'
|
||||
zero = timedelta(0)
|
||||
zero = dt.timedelta(0)
|
||||
def utcoffset(self, dt):
|
||||
return self.zero
|
||||
def tzname(self, dt):
|
||||
|
@ -58,6 +56,7 @@ except ImportError:
|
|||
|
||||
# Much of the following code is adapted from the USNO's "novas.c".
|
||||
|
||||
_time_zero = dt.time()
|
||||
_half_minute = 30.0 / DAY_S
|
||||
_half_second = 0.5 / DAY_S
|
||||
_half_microsecond = 0.5e-6 / DAY_S
|
||||
|
@ -106,47 +105,62 @@ class Timescale(object):
|
|||
correct UTC date and time.
|
||||
|
||||
"""
|
||||
return self.utc(self._utcnow().replace(tzinfo=utc))
|
||||
return self.from_datetime(self._utcnow().replace(tzinfo=utc))
|
||||
|
||||
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``.
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
def utc(self, year, month=1, day=1, hour=0, minute=0, second=0.0):
|
||||
"""Build a `Time` from a UTC calendar date.
|
||||
|
||||
You can either specify the date as separate components, or
|
||||
provide a time zone aware Python datetime. The following two
|
||||
calls are equivalent (the ``utc`` time zone object can be
|
||||
imported from the ``skyfield.api`` module, or from ``pytz`` if
|
||||
you have it)::
|
||||
|
||||
ts.utc(2014, 1, 18, 1, 35, 37.5)
|
||||
ts.utc(datetime(2014, 1, 18, 1, 35, 37, 500000, tzinfo=utc))
|
||||
|
||||
Note that only by passing the components separately can you
|
||||
specify a leap second, because a Python datetime will not allow
|
||||
the value 60 in its seconds field.
|
||||
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.
|
||||
|
||||
"""
|
||||
# TODO: someday deprecate passing datetime objects here, as
|
||||
# there are now separate constructors for them.
|
||||
if isinstance(year, datetime):
|
||||
dt = year
|
||||
tai1, tai2 = _utc_datetime_to_tai(self.leap_dates,
|
||||
self.leap_offsets, dt)
|
||||
elif isinstance(year, date):
|
||||
d = year
|
||||
tai1, tai2 = _utc_date_to_tai(self.leap_dates, self.leap_offsets, d)
|
||||
elif hasattr(year, '__len__') and isinstance(year[0], datetime):
|
||||
# TODO: clean this up and better document the possibilities.
|
||||
list_of_datetimes = year
|
||||
tai1, tai2 = array([
|
||||
_utc_datetime_to_tai(self.leap_dates, self.leap_offsets, dt)
|
||||
for dt in list_of_datetimes
|
||||
]).T
|
||||
else:
|
||||
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),
|
||||
)
|
||||
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),
|
||||
)
|
||||
t = Time(self, tai1, tai2 + tt_minus_tai)
|
||||
t.tai = tai1 + tai2
|
||||
t.tai_fraction = tai2
|
||||
return t
|
||||
|
||||
def tai(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
|
||||
|
@ -326,7 +340,7 @@ class Time(object):
|
|||
# TODO: raise non-IndexError exception if this Time is not an array;
|
||||
# otherwise, a `for` loop over it will not raise an error.
|
||||
t = Time(self.ts, self.whole[index], self.tt_fraction[index])
|
||||
for name in 'tai', 'tdb_fraction', 'ut1_fraction':
|
||||
for name in 'tai_fraction', 'tdb_fraction', 'ut1_fraction':
|
||||
value = getattr(self, name, None)
|
||||
if value is not None:
|
||||
if getattr(value, 'shape', None):
|
||||
|
@ -653,15 +667,15 @@ class Time(object):
|
|||
"""Decimal Julian years centered on J2000.0 = TT 2000 January 1 12h."""
|
||||
return (self.whole - 1721045.0 + self.tt_fraction) / 365.25
|
||||
|
||||
@reify
|
||||
def tai(self):
|
||||
return self.tt - tt_minus_tai
|
||||
|
||||
@reify
|
||||
def utc(self):
|
||||
utc = self._utc_tuple()
|
||||
return array(utc).view(CalendarArray) if self.shape else CalendarTuple(*utc)
|
||||
|
||||
@reify
|
||||
def tai_fraction(self):
|
||||
return self.tt_fraction - tt_minus_tai
|
||||
|
||||
@reify
|
||||
def tdb_fraction(self):
|
||||
fr = self.tt_fraction
|
||||
|
@ -701,6 +715,10 @@ class Time(object):
|
|||
|
||||
# Low-precision floats generated from internal float pairs.
|
||||
|
||||
@property
|
||||
def tai(self):
|
||||
return self.whole + self.tai_fraction
|
||||
|
||||
@property
|
||||
def tt(self):
|
||||
return self.whole + self.tt_fraction
|
||||
|
@ -899,7 +917,8 @@ _format_uses_minutes = re.compile(r'%[-_0^#EO]*[MR]').search
|
|||
def _utc_datetime_to_tai(leap_dates, leap_offsets, dt):
|
||||
if dt.tzinfo is None:
|
||||
raise ValueError(_naive_complaint)
|
||||
dt = dt.astimezone(utc)
|
||||
if dt.tzinfo is not utc:
|
||||
dt = dt.astimezone(utc)
|
||||
return _utc_to_tai(leap_dates, leap_offsets,
|
||||
dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second + dt.microsecond * 1e-6)
|
||||
|
|
Loading…
Reference in New Issue