Skip to content

Commit

Permalink
Use old ∆T files instead, if already on disk
Browse files Browse the repository at this point in the history
This will hopefully stop #452 from breaking existing scripts.
  • Loading branch information
brandon-rhodes committed Oct 17, 2020
1 parent 7063005 commit 3f26903
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 31 deletions.
46 changes: 33 additions & 13 deletions skyfield/documentation/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -790,12 +790,12 @@ It is hard to predict future values for UT1.
The Earth is a young world
with a still-molten iron core,
a viscous mantle,
and ice ages that move water weight up to the poles
then release it back again into the ocean.
While we think we can predict (for example)
and ice ages that move water weight into glaciers at the poles
then release it back into the ocean.
While we think we can predict, for example,
Jupiter’s position thousands of years from now,
predicting the fluid dynamics of the elastic rotating ellipsoid we call home
is, at the moment, beyond us.
isat the moment beyond us.
We can only watch with sensitive instruments
to see what the Earth does next.

Expand All @@ -806,8 +806,8 @@ and for the schedule of leap seconds (discussed above)
that keeps UTC from straying more than 0.9 seconds away from UT1.

Each new version of Skyfield carries recent IERS data in internal tables.
This data will gradually fall out of date, however,
with two consequences:
This data will gradually fall out of date after each Skyfield release,
however, with two consequences:

* The next time the IERS declares a new leap second
that is not listed in Skyfield’s built-in tables,
Expand All @@ -819,13 +819,14 @@ with two consequences:
Skyfield’s idea of where the Earth is pointing will grow less accurate.
This will affect both the position and direction
of each :class:`~skyfield.toposlib.Topos` geographic location —
whether used as an observer or as an observation target —
whether used as an observer or a target —
and will also affect Earth satellite positions.

You can avoid both of these problems
by periodically downloading new data from the IERS.
Simply specify that you don’t want Skyfield to use its builtin tables.
Skyfield will instead download ``finals2000A.all`` from the IERS:
In that case :meth:`~skyfield.iokit.Loader.timescale()`
will instead download ``finals2000A.all`` from the IERS:

::

Expand All @@ -843,8 +844,12 @@ then will keep using that same copy of the file that it finds on disk.
If your script will always have Internet access
and you worry about the file falling out of date
(and if you can trust the “modify time” file attribute on your filesystem),
you can have Skyfield download a new copy
once the copy on disk has grown too old:
then you can have Skyfield download a new copy
once the file on disk has grown too old
(where “too old” for your application
must be determined by comparing your accuracy needs
with how quickly UT1 diverges without fresh IERS data;
this example uses 30 days only as an illustration):

::

Expand All @@ -853,9 +858,24 @@ once the copy on disk has grown too old:

ts = load.timescale(builtin=False)

Astronomers use two common conventions
for stating the difference between clock time and UT1,
and Skyfield supports them both.
But, beware!
For compatibility with versions of Skyfield ≤ 1.30,
Skyfield will ignore ``finals2000A.all``
if the three old files
``deltat.data``, ``deltat.preds``, and ``Leap_Second.dat``
exist in the loader’s directory,
in which case it will use them instead.
This is to prevent users who specify ``builtins=False``,
but who downloaded the three necessary files long ago,
from experiencing an unexpected download attempt.
The hope is that all scripts
which did not previously need Internet access
will continue to run without it.

If you ever want to display or plot the behavior of UT1,
astronomers use two common conventions
for stating the difference between clock time and UT1.
Skyfield supports them both.

.. testcode::

Expand Down
7 changes: 3 additions & 4 deletions skyfield/io_timescale.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
from datetime import datetime, timedelta
from threading import Lock

from .timelib import Timescale, julian_date
from .timelib import julian_date

_lock = Lock()

def _build_timescale(deltat_data, deltat_preds, leap_second_dat):
def _build_legacy_data(deltat_data, deltat_preds, leap_second_dat):
data_end_time = deltat_data[0, -1]
i = np.searchsorted(deltat_preds[0], data_end_time, side='right')
delta_t_recent = np.concatenate([deltat_data, deltat_preds[:,i:]], axis=1)

leap_dates, leap_offsets = leap_second_dat
return Timescale(delta_t_recent, leap_dates, leap_offsets)
return delta_t_recent, leap_dates, leap_offsets

def parse_deltat_data(fileobj):
"""Parse the United States Naval Observatory ``deltat.data`` file.
Expand Down
26 changes: 22 additions & 4 deletions skyfield/iokit.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .data import iers
from .functions import load_bundled_npy
from .io_timescale import (
_build_legacy_data,
parse_deltat_data, parse_deltat_preds, parse_leap_seconds,
)
from .jpllib import SpiceKernel
Expand Down Expand Up @@ -154,6 +155,9 @@ def days_old(self, filename):
seconds = time() - mtime
return seconds / 86400.0

def _exists(self, filename):
return os.path.exists(self.path_to(filename))

def __call__(self, filename, reload=False, backup=False, builtin=False):
"""Open the given file, downloading it first if necessary."""
if '://' in filename:
Expand Down Expand Up @@ -321,17 +325,31 @@ def timescale(self, delta_t=None, builtin=True):
:ref:`custom-delta-t`.
``builtin`` — By default, Skyfield uses ∆T and leap second
tables that it carries internally. Set this option to ``False``
to download up-to-date data (about 3.3 MB) directly from the
International Earth Rotation Service instead. For details, see
:ref:`downloading-timescale-files`.
tables that it carries internally; to instead load this data
from files, set this option to ``False``. For compatibility
with Skyfield ≤ 1.30, if you have on disk the three files
``deltat.data``, ``deltat.preds``, and ``Leap_Second.dat``, then
Skyfield will load them. Otherwise, Skyfield will download and
use ``finals2000A.all`` from the International Earth Rotation
Service. For details, see :ref:`downloading-timescale-files`.
"""
e = self._exists
if builtin:
arrays = load_bundled_npy('iers.npz')
delta_t_recent = arrays['delta_t_recent']
leap_dates = arrays['leap_dates']
leap_offsets = arrays['leap_offsets']
elif e('deltat.data') and e('deltat.preds') and e('Leap_Second.dat'):
# Avoid changing the meaning of "builtin=False" and
# surprising the user with a file download, if their
# previous version of Skyfield already downloaded the three
# old files we used to rely on.
deltat_data = self('deltat.data')
deltat_preds = self('deltat.preds')
_, leap_second_dat = self('Leap_Second.dat')
delta_t_recent, leap_dates, leap_offsets = _build_legacy_data(
deltat_data, deltat_preds, leap_second_dat)
else:
with self.open('finals2000A.all') as f:
mjd_utc, dut1 = iers.parse_dut1_from_finals_all(f)
Expand Down
42 changes: 32 additions & 10 deletions skyfield/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from assay import assert_raises
from skyfield import api

ci = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../ci'))
old_content = (b' 2015 10 1 67.9546\n'
b' 2015 11 1 68.0055\n'
b' 2015 12 1 68.0514\n'
Expand All @@ -31,17 +32,17 @@ def load():
finally:
shutil.rmtree(path)

def save_file(load, content=old_content):
with open(load.path_to('deltat.data'), 'wb') as f:
def save_file(load, path, content=old_content):
with open(load.path_to(path), 'wb') as f:
f.write(content)

def file_contents(load):
with open(load.path_to('deltat.data'), 'rb') as f:
def file_contents(load, path):
with open(load.path_to(path), 'rb') as f:
return f.read()

@contextmanager
def fake_download(load):
download = lambda *args, **kw: save_file(load, new_content)
download = lambda *args, **kw: save_file(load, 'deltat.data', new_content)
with patch('skyfield.iokit.download', download):
yield

Expand All @@ -57,7 +58,6 @@ def test_open_in_main_directory(load):
with open(os.path.join(load.directory, 'file.tle'), 'wb') as f:
f.write(b'example text\n')
data = load.open('file.tle').read()
print(repr(data))
assert data == b'example text\n'

def test_open_in_subdirectory(load):
Expand All @@ -70,13 +70,35 @@ def test_open_in_subdirectory(load):
def test_missing_file_gets_downloaded(load):
with fake_download(load):
data = load('deltat.data')
print(repr(file_contents(load)[:-20]))
assert file_contents(load).endswith(b' 68.1577\n')
assert file_contents(load, 'deltat.data').endswith(b' 68.1577\n')
assert data[1][-1] == 68.1577

def test_builtin_timescale(load):
def test_builtin_timescale_uses_recent_IERS_data(load):
ts = load.timescale()
ts.utc(2019, 7, 21, 11, 11)
# DUT1 cut and pasted from "20 1 1" line of "finals2000A.all":
assert abs(ts.utc(2020, 1, 1).dut1 - (-0.1771547)) < 1e-8

def test_non_builtin_timescale_prefers_USNO_files(load):
with open(os.path.join(ci, 'deltat.preds'), 'rb') as f:
preds = f.read()
with open(os.path.join(ci, 'Leap_Second.dat'), 'rb') as f:
leaps = f.read()

save_file(load, 'deltat.data',
b' 1973 2 1 111.1\n'
b' 1973 3 1 222.2\n'
b' 1973 4 1 333.3\n')
save_file(load, 'deltat.preds', preds)
save_file(load, 'Leap_Second.dat', leaps)
save_file(load, 'finals2000A.all', b'invalid data')

ts = load.timescale(builtin=False)
assert abs(ts.utc(1973, 3, 1).delta_t - 222.2) < 1e-2

def test_non_builtin_timescale_tries_to_load_finals2000A_all(load):
save_file(load, 'finals2000A.all', b'invalid data')
with assert_raises(IndexError):
load.timescale(builtin=False)

# Impressive tests: synchronize threads to reproduce concurrency bugs.

Expand Down

0 comments on commit 3f26903

Please sign in to comment.