Hello community, here is the log from the commit of package python-skyfield for openSUSE:Factory checked in at 2020-09-27 11:50:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-skyfield (Old) and /work/SRC/openSUSE:Factory/.python-skyfield.new.4249 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-skyfield" Sun Sep 27 11:50:11 2020 rev:7 rq:837884 version:1.29 Changes: -------- --- /work/SRC/openSUSE:Factory/python-skyfield/python-skyfield.changes 2020-09-16 19:40:35.202883394 +0200 +++ /work/SRC/openSUSE:Factory/.python-skyfield.new.4249/python-skyfield.changes 2020-09-27 11:50:15.216074287 +0200 @@ -1,0 +2,32 @@ +Sat Sep 26 08:13:08 UTC 2020 - Benjamin Greiner <c...@bnavigator.de> + +- Update to version 1.29 + * Fix: the new Julian calendar feature was raising an + exception in the calendar methods like + `skyfield.timelib.Time.tt_calendar()` if the time + object was in fact an array of times. #450 + * Fix: trying to iterate over a time object would raise an + exception if the time was created through + `~skyfield.timelib.Timescale.ut1()`. +- Version 1.28 + * Broken URL: Because the VizieR archive apparently decided + to uncompress their copy of the hip_main.dat.gz Hipparcos + catalog file, the old URL now returns a 404 error. As an + emergency fix, this version of Skyfield switches to their + uncompressed hip_main.dat. Hopefully they don’t compress + it again and break the new URL! A more permanent solution + is discussed at: #454 + * To unblock this release, removed a few deprecated pre-1.0 + experiments from April 2015 in skyfield.hipparcos and + skyfield.named_stars that broke because the Hipparcos + catalog is no longer compressed; hopefully no one was + using them. + * In a sweeping internal change, the + `~skyfield.timelib.Timescale` and + `~skyfield.timelib.Time` objects now offer support + for the Julian calendar that’s used by historians for + dates preceding the adoption of the Gregorian calendar in + 1582. See choice of calendars if you want to turn on + Julian dates in your application. #450 + +------------------------------------------------------------------- Old: ---- skyfield-1.27.tar.gz New: ---- skyfield-1.29.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-skyfield.spec ++++++ --- /var/tmp/diff_new_pack.M4aOdM/_old 2020-09-27 11:50:16.416075580 +0200 +++ /var/tmp/diff_new_pack.M4aOdM/_new 2020-09-27 11:50:16.416075580 +0200 @@ -21,7 +21,7 @@ %define assayver 256.23c18c2 %define skip_python2 1 Name: python-skyfield -Version: 1.27 +Version: 1.29 Release: 0 Summary: Elegant astronomy for Python License: MIT ++++++ skyfield-1.27.tar.gz -> skyfield-1.29.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/PKG-INFO new/skyfield-1.29/PKG-INFO --- old/skyfield-1.27/PKG-INFO 2020-09-15 20:39:15.000000000 +0200 +++ new/skyfield-1.29/PKG-INFO 2020-09-26 03:01:08.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: skyfield -Version: 1.27 +Version: 1.29 Summary: Elegant astronomy for Python Home-page: http://github.com/brandon-rhodes/python-skyfield/ Author: Brandon Rhodes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/__init__.py new/skyfield-1.29/skyfield/__init__.py --- old/skyfield-1.27/skyfield/__init__.py 2020-09-15 20:35:41.000000000 +0200 +++ new/skyfield-1.29/skyfield/__init__.py 2020-09-26 02:58:57.000000000 +0200 @@ -5,5 +5,5 @@ the source code, as well as the http://rhodesmill.org/skyfield/ site! """ -VERSION = (1, 27) +VERSION = (1, 29) __version__ = '.'.join(map(str, VERSION)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/api.py new/skyfield-1.29/skyfield/api.py --- old/skyfield-1.27/skyfield/api.py 2020-08-29 20:22:29.000000000 +0200 +++ new/skyfield-1.29/skyfield/api.py 2020-09-24 18:56:04.000000000 +0200 @@ -15,18 +15,23 @@ from .positionlib import position_from_radec, position_of_radec from .starlib import Star from .sgp4lib import EarthSatellite -from .timelib import Time, Timescale, utc +from .timelib import ( + GREGORIAN_START, GREGORIAN_START_ENGLAND, Time, Timescale, utc +) from .toposlib import Topos from .units import Angle, Distance -from .named_stars import NamedStar load = Loader('.') -__all__ = ['Angle', 'B1950', 'Distance', 'EarthSatellite', 'Loader', - 'NamedStar', 'PlanetaryConstants', 'Star', 'T0', 'Time', - 'Timescale', 'Topos', 'datetime', 'load', 'load_constellation_map', - 'load_file', 'position_from_radec', 'position_of_radec', - 'utc', 'pi', 'tau'] +__all__ = [ + 'Angle', 'B1950', 'Distance', 'EarthSatellite', + 'GREGORIAN_START', 'GREGORIAN_START_ENGLAND', + 'Loader', 'PlanetaryConstants', 'Star', + 'T0', 'Time', 'Timescale', 'Topos', + 'datetime', 'load', 'load_constellation_map', + 'load_file', 'position_from_radec', 'position_of_radec', + 'utc', 'pi', 'tau', +] # An attempt at friendliest-possible deprecations: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/data/hipparcos.py new/skyfield-1.29/skyfield/data/hipparcos.py --- old/skyfield-1.27/skyfield/data/hipparcos.py 2020-08-29 20:22:29.000000000 +0200 +++ new/skyfield-1.29/skyfield/data/hipparcos.py 2020-09-24 19:00:36.000000000 +0200 @@ -1,38 +1,20 @@ -import gzip -from skyfield.functions import to_spherical -from skyfield.starlib import Star -from skyfield.timelib import T0 -from skyfield.units import Angle +# This URL worked until September 2020: -days = T0 - 2448349.0625 -URL = 'http://cdsarc.u-strasbg.fr/ftp/cats/I/239/hip_main.dat.gz' -url = URL # old name, in case anyone used it +#URL = 'http://cdsarc.u-strasbg.fr/ftp/cats/I/239/hip_main.dat.gz' + +# Then someone at VizieR apparently ran `gunzip` on the file, breaking +# the existing URL. The fastest fix is for us to switch to: -def parse(line): - "DEPRECATED; see :func:`~skyfield.data.hipparcos.load_dataframe() instead." - # See ftp://cdsarc.u-strasbg.fr/cats/I/239/ReadMe - star = Star( - ra=Angle(degrees=float(line[51:63])), - dec=Angle(degrees=float(line[64:76])), - ra_mas_per_year=float(line[87:95]), - dec_mas_per_year=float(line[96:104]), - parallax_mas=float(line[79:86]), - names=[('HIP', int(line[8:14]))], - ) - star._position_au += star._velocity_au_per_d * days - distance, dec, ra = to_spherical(star._position_au) - star.ra = Angle(radians=ra, preference='hours') - star.dec = Angle(radians=dec) - return star - -def load(match_function): - "DEPRECATED; see :func:`~skyfield.data.hipparcos.load_dataframe() instead." - from skyfield import api - - with api.load.open(url) as f: - for line in gzip.GzipFile(fileobj=f): - if match_function(line): - yield parse(line) +URL = 'https://cdsarc.u-strasbg.fr/ftp/cats/I/239/hip_main.dat' + +# But what if someone runs `gzip` on the file again? Then the new URL +# will break like the old one did. It appears that VizieR makes no +# guarantee that raw catalog URLs are stable, and that we need to switch +# to one of their catalog generation URLs, which produce text in a new +# format that Skyfield will have to learn. Discussion at: +# https://github.com/skyfielders/python-skyfield/issues/454 + +url = URL # old name, in case anyone used it PANDAS_MESSAGE = """Skyfield needs Pandas to load the Hipparcos catalog @@ -56,7 +38,7 @@ 'CPD', '(V-I)red', 'SpType', 'r_SpType', ) -def load_dataframe(fobj, compression='gzip'): +def load_dataframe(fobj): """Given an open file for `hip_main.dat.gz`, return a parsed dataframe. If your copy of ``hip_main.dat`` has already been unzipped, pass the @@ -68,8 +50,13 @@ except ImportError: raise ImportError(PANDAS_MESSAGE) + fobj.seek(0) + magic = fobj.read(2) + compression = 'gzip' if (magic == b'\x1f\x8b') else None + fobj.seek(0) + df = read_csv( - fobj, sep='|', compression=compression, names=_COLUMN_NAMES, + fobj, sep='|', names=_COLUMN_NAMES, compression=compression, usecols=['HIP', 'Vmag', 'RAdeg', 'DEdeg', 'Plx', 'pmRA', 'pmDE'], na_values=[' ', ' ', ' ', ' '], ) @@ -82,13 +69,3 @@ epoch_year = 1991.25, ) return df.set_index('hip') - -def get(which): - "DEPRECATED; see :func:`~skyfield.data.hipparcos.load_dataframe() instead." - if isinstance(which, str): - pattern = ('H| %6s' % which).encode('ascii') - for star in load(lambda line: line.startswith(pattern)): - return star - else: - patterns = set(id.encode('ascii').rjust(6) for id in which) - return list(load(lambda line: line[8:14] in patterns)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/documentation/api-time.rst new/skyfield-1.29/skyfield/documentation/api-time.rst --- old/skyfield-1.27/skyfield/documentation/api-time.rst 2020-09-11 10:40:22.000000000 +0200 +++ new/skyfield-1.29/skyfield/documentation/api-time.rst 2020-09-24 17:55:31.000000000 +0200 @@ -8,6 +8,26 @@ and of how you can convert time to and from familiar time scales like UTC and the worldwide time zones that are adapted from it. +.. _calendar date: + +Calendar date +============= + +1. When building a `Time` from a calendar date, + you can not only designate a single moment by its time and date, + but you can also build a `Time` representing a whole array of moments + by supplying a Python list or NumPy array + for either the year, month, day, hour, minute, or second. + See `date-arrays`. + +2. By default, + Skyfield uses the modern Gregorian calendar for all dates — + even dates before the Gregorian calendar was introduced in 1582. + +3. You can instead opt to use the old Julian calendar for ancient dates, + which is the most common practice among historians. + See `choice of calendars`. + Timescale, for building and converting times ============================================ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/documentation/api.rst new/skyfield-1.29/skyfield/documentation/api.rst --- old/skyfield-1.27/skyfield/documentation/api.rst 2020-09-11 10:48:18.000000000 +0200 +++ new/skyfield-1.29/skyfield/documentation/api.rst 2020-09-24 17:34:58.000000000 +0200 @@ -39,34 +39,22 @@ Loader.timescale Loader.tle_file +.. _api-Timescale: + Time scales =========== .. currentmodule:: skyfield.timelib -A Skyfield `Timescale` object is typically built -at the beginning of each program: +A script will typically start by building a single Skyfield `Timescale` +to use for all date and time conversions: .. testcode:: from skyfield import api ts = api.load.timescale() -It downloads and parses the data tables necessary -to correctly convert between Universal Time -and the more stable time scales used by astronomers. - -If you want to skip downloading up-to-date time scale files, -you can run: - -.. testcode:: - - ts = api.load.timescale() - -This can avoid problems connecting -to the servers from which the official files are distributed. -Note that the time scale files distributed with -any given version of Skyfield will fall gradually out of date. +Its methods are: .. autosummary:: @@ -85,6 +73,8 @@ Timescale.ut1_jd Timescale.from_astropy +.. _api-Time: + Time objects ============ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/documentation/time.rst new/skyfield-1.29/skyfield/documentation/time.rst --- old/skyfield-1.27/skyfield/documentation/time.rst 2020-08-27 16:38:03.000000000 +0200 +++ new/skyfield-1.29/skyfield/documentation/time.rst 2020-09-24 18:00:35.000000000 +0200 @@ -5,15 +5,13 @@ .. currentmodule:: skyfield.timelib -Astronomers use several numerical scales to measure time. -Skyfield often has to use more than one of them within a single computation! -The `Time` class is used to represent a single moment in time, -or an array of such moments. -It keeps track of all the different ways the same moment -might be designated in different time scales. -It performs this conversion using data tables -that are stored in the `Timescale` object -that was used to create it: +Astronomers use a variety of different scales to measure time. +Skyfield often has to use several timescales within a single computation! +The `Time` class is how Skyfield represents either a single moment in time +or a whole array of moments, +and keeps track of all of the different designations +assigned to that moment +by the various standard time scales: .. testsetup:: @@ -24,74 +22,129 @@ .. testcode:: - # Building a time object - from skyfield.api import load - ts = load.timescale() # Timescale object - t = ts.utc(2014, 1, 18) # Time object + ts = load.timescale() + t = ts.tt(2000, 1, 1, 12, 0) - print(t.tai) - print(t.tt) - print(t.tdb) + print('TT date and time: ', t.tt_strftime()) + print('TAI date and time:', t.tai_strftime()) + print('UTC date and time:', t.utc_strftime()) + print('TDB Julian date: {:.10f}'.format(t.tdb)) + print('Julian century: {:.1f}'.format(t.J)) .. testoutput:: - 2456675.5004050927 - 2456675.5007775924 - 2456675.5007775975 + TT date and time: 2000-01-01 12:00:00 TT + TAI date and time: 2000-01-01 11:59:28 TAI + UTC date and time: 2000-01-01 11:58:56 UTC + TDB Julian date: 2451544.9999999991 + Julian century: 2000.0 +The `Timescale` object returned by ``load.timescale()`` +manages the conversions between different time scales +and is also how the programmer builds `Time` objects for specific dates. Most applications create only one `Timescale` object, -which is conventionally named ``ts``, +which Skyfield programmers conventionally name ``ts``, and use it to build all of their times. -Each `Time` object only computes a given timescale -the first time it’s asked. -On subsequent requests for the same timescale, -it returns the value that it cached the first time, -without recomputing it. -This has an important consequence: -if your program will need to do several computations for the same time, -be sure to use the same `Time` object for all of them. -Otherwise your separate time objects -will have to compute the same time scales all over again. -See the :ref:`date-cache` section below for more details. - -Each time scale supported by :class:`Time` -is described in detail in one of the sections below. -The supported time scales are: - -* ``t.utc`` — Coordinated Universal Time (“Greenwich Time”) -* ``t.ut1`` — Universal Time -* ``t.tai`` — International Atomic Time -* ``t.tt`` and ``t.J`` — Terrestrial Time -* ``t.tdb`` — Barycentric Dynamical Time (the JPL’s *T*\ :sub:`eph`) - -You can build a `Time` from several different source timescales. -Here’s a summary. - -:: - - # All the ways you can create a Time object: - - t = ts.utc(year, month, day, hour, minute, second) - t = ts.from_datetime(dt) - t = ts.from_datetimes([dt1, dt2, ...]) - t = ts.now() - - t = ts.tai(year, month, day, hour, minute, second) - t = ts.tai_jd(floating_point_Julian_date) +For quick reference, +here are the supported timescales: - t = ts.tt(year, month, day, hour, minute, second) - t = ts.tt_jd(floating_point_Julian_date) - t = ts.J(floating_point_Julian_year) - - t = ts.tdb(year, month, day, hour, minute, second) - t = ts.tdb_jd(floating_point_Julian_date) - - t = ts.ut1(year, month, day, hour, minute, second) - t = ts.ut1_jd(floating_point_Julian_date) - -The meaning of each timescale is discussed in the sections below. +* UTC — Coordinated Universal Time (“Greenwich Time”) +* UT1 — Universal Time +* TAI — International Atomic Time +* TT — Terrestrial Time +* TDB — Barycentric Dynamical Time (the JPL’s *T*\ :sub:`eph`) + +And here are links to the API documentation for time scales and times: + +* :ref:`api-Timescale` +* :ref:`api-Time` + +.. _choice of calendars: + +Ancient and modern dates +======================== + +Skyfield normally uses the modern Gregorian calendar, +even for dates in history before the Gregorian calendar’s adoption in 1582. +This “proleptic” use of Gregorian dates +makes date calculations simple, +is compatible with Python’s ``datetime``, +and is also the behavior of the United States Naval Observatory library +on which many Skyfield routines were originally based. + +But the Gregorian calendar is awkward +for historians and students of ancient astronomy, +because the calendar in actual use before 1582 +was the old Julian calendar +established by Julius Caesar’s calendar reform in 45 BC. +The two calendars agree over the century +between the leap day of AD 200 and the leap day of AD 300. +But because the Julian calendar is not quite synchronized with the seasons, +its dates run ahead of the Gregorian calendar before that century +and run behind the Gregorian calendar after it. + +If you would like Skyfield +to switch to the Julian calendar for historical dates — +both when interpreting the dates you input, +and when producing calendar dates as output — +simply give your ``Timescale`` object +the Julian date on which you would like the calendar to switch. + +.. testcode:: + + from skyfield.api import GREGORIAN_START + + ts.julian_calendar_cutoff = GREGORIAN_START + + t = ts.tt_jd(range(2299159, 2299163)) + for s in t.tt_strftime(): + print(s) + +.. testoutput:: + + 1582-10-03 12:00:00 TT + 1582-10-04 12:00:00 TT + 1582-10-15 12:00:00 TT + 1582-10-16 12:00:00 TT + +As you can see from these four successive days in history, +Pope Gregory had the calendar jump +from the Julian calendar date 1582 October 4 +to the Gregorian calendar date October 15 the next day +in order to bring the date of Easter back into sync with the equinox. +Skyfield provides two constants for popular cutoff dates: + +* ``GREGORIAN_START`` — Julian day 2299161, + on which the new Gregorian calendar went into effect in Rome. + +* ``GREGORIAN_START_ENGLAND`` — Julian day 2361222, + on which the new Gregorian calendar went into effect in England in 1752 + (the reform having initially been rejected by the English bishops, + “Seeing that the Bishop of Rome is Antichrist, + therefore we may not communicate with him in any thing”). + +You are free to choose your own cutoff Julian date +if you are studying astronomy records from a country +that adopted the Gregorian calendar on some other date. +Russia, for example, did not adopt it until the twentieth century. +The default value, that always uses Gregorian dates, is ``None``: + +.. testcode:: + + ts.julian_calendar_cutoff = None + +Note that even the Julian calendar becomes anachronistic +before its adoption in 45 BC, +so all dates generated by Skyfield are “proleptic” before that date. +And, of course, the Julian calendar +was local to the civilization that ringed the Mediterranean. +If you are interested in relating astronomical events +to more ancient Roman calendars, +or the calendars of other civilizations, +try searching for a third-party Python package +that supports the calendar you are interested in. .. _building-dates: @@ -378,37 +431,42 @@ =========== If you want to ask where a planet or satellite was -at a whole list of different times and dates, -then Skyfield will work most efficiently -if you build a single :class:`Time` object -that holds an entire array of dates, -instead of building many separate :class:`Time` objects. -There are three techniques for building arrays. - -* Provide ``ts.utc()`` with a Python list of ``datetime`` objects. - -* Provide ``tai()`` or ``tt()`` or ``tdb()`` or ``ut1()`` - with an entire NumPy array or Python list of floating point values. +across a whole series of times and dates, +then Skyfield will work most efficiently if, +instead of building many separate :class:`Time` objects, +you build a single :class:`Time` object that holds the entire array of dates. + +There are three techniques for building a `Time` array. + +* Provide :meth:`~Timescale.tai()` or :meth:`~Timescale.tt()` + or :meth:`~Timescale.tdb()` or :meth:`~Timescale.ut1()` + with a Python list or NumPy array of numbers + for one of the six components of the calendar date + (year, month, day, hour, minute, or second). + +* Provide :meth:`~Timescale.tai_jd()` or :meth:`~Timescale.tt_jd()` + or :meth:`~Timescale.tdb_jd()` or :meth:`~Timescale.ut1_jd()` + with a list or NumPy array of floating point numbers. -* When specifying year, month, day, hour, minute, and second, - make one of the values a list or array. +* Provide :meth:`~Timescale.from_datetimes()` + with a Python list of ``datetime`` objects. -The last possibility is generally the one that is the most fun, +The first possibility is generally the one that is the most fun, because its lets you vary whichever time unit you want -while holding the others steady. -And you are free to provide out-of-range values +while holding the others constant. +You are free to provide out-of-range values and leave it to Skyfield to work out the correct result. Here are some examples:: ts.utc(range(1900, 1950)) # Fifty years 1900–1949 - ts.utc(1980, range(1, 25)) # Twenty-four months - ts.utc(2005, 5, [1, 10, 20]) # 1st, 10th, and 20th of May + ts.utc(1980, range(1, 25)) # 24 months of 1980 and 1981 + ts.utc(2005, 5, [1, 11, 21]) # 1st, 11th, and 21st of May - # The ten seconds crossing the 1974 leap second + # Negative values work too! Here are the + # ten seconds crossing the 1974 leap second. ts.utc(1975, 1, 1, 0, 0, range(-5, 5)) -The resulting :class:`Time` object will hold an array of times -instead of just a single scalar value. +The resulting :class:`Time` object will hold an array of times. As illustrated in the previous section (on leap seconds), you can use a Python ``for`` to print each time separately: @@ -416,8 +474,8 @@ t = ts.utc(2020, 6, 16, 7, range(4)) - for ti in t: - print(ti.utc_strftime('%Y-%m-%d %H:%M')) + for s in t.utc_strftime('%Y-%m-%d %H:%M'): + print(s) .. testoutput:: @@ -532,9 +590,9 @@ 2014-01-03 x = -0.21 y = 0.88 z = 0.38 2014-01-04 x = -0.23 y = 0.88 z = 0.38 -Finally, converting an array Julian date back into a calendar tuple -results in the year, month, and all of the other values -being as deep as the array itself: +Finally, converting an array `Time` back into a calendar tuple +results in the year, month, day, hour, minute, and second +each having the same dimension as the array itself: .. testcode:: @@ -549,7 +607,7 @@ [ 0. 0. 0. 0.] [ 0. 0. 0. 0.]] -Again, simply slice across the second dimension of the array +Simply slice across the second dimension of the array to pull a particular calendar tuple out of the larger result: .. testcode:: @@ -560,7 +618,8 @@ [2014. 1. 3. 0. 0. 0.] -The rows can be fetched not only by index +Slicing in the other direction, +the rows can be fetched not only by index but also through the attribute names ``year``, ``month``, ``day``, ``hour``, ``minute``, and ``second``. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/named_stars.py new/skyfield-1.29/skyfield/named_stars.py --- old/skyfield-1.27/skyfield/named_stars.py 2020-08-29 20:22:29.000000000 +0200 +++ new/skyfield-1.29/skyfield/named_stars.py 2020-09-24 19:35:59.000000000 +0200 @@ -1,12 +1,9 @@ -# TODO: Deprecate; maybe even remove, since it's not documented? -""" -Convenience functions for users to get a Star instance using a small database -of named stars. -""" -from .data import hipparcos +# This list was seeded from: +# https://en.wikipedia.org/wiki/List_of_stars_in_the_Hipparcos_Catalogue + +# Recent discussion: +# https://github.com/skyfielders/python-skyfield/issues/304 -#This list was seeded from -#https://en.wikipedia.org/wiki/List_of_stars_in_the_Hipparcos_Catalogue named_star_dict= { 'Achernar': 7588, 'Acrux': 60718, @@ -128,11 +125,3 @@ 'Wei': 82396, 'Wezen': 34444 } - -def NamedStar(name): - """DEPRECATED: See stars.rst for how to load a star catalog.""" - try: - hid = named_star_dict[name] - return hipparcos.get(str(hid)) - except KeyError: - raise ValueError("No star named {0} known to skyfield.".format(name)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/sgp4lib.py new/skyfield-1.29/skyfield/sgp4lib.py --- old/skyfield-1.27/skyfield/sgp4lib.py 2020-09-11 10:47:16.000000000 +0200 +++ new/skyfield-1.29/skyfield/sgp4lib.py 2020-09-17 17:48:24.000000000 +0200 @@ -157,7 +157,8 @@ """ sat = self.model - jd = t._utc_float() + whole, fraction, is_leap_second = t._utc_float(0.0) + jd = whole + fraction if getattr(jd, 'shape', None): e, r, v = sat.sgp4_array(jd, zeros_like(jd)) messages = [SGP4_ERRORS[error] if error else None for error in e] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/tests/test_against_novas.py new/skyfield-1.29/skyfield/tests/test_against_novas.py --- old/skyfield-1.27/skyfield/tests/test_against_novas.py 2020-09-11 10:40:33.000000000 +0200 +++ new/skyfield-1.29/skyfield/tests/test_against_novas.py 2020-09-24 19:24:30.000000000 +0200 @@ -6,7 +6,7 @@ from skyfield.api import Topos, load from skyfield.constants import AU_KM, AU_M from skyfield.data import hipparcos -from skyfield.functions import length_of +from skyfield.functions import BytesIO, length_of from .fixes import low_precision_ERA OLD_AU_KM = 149597870.691 # TODO: load from de405 @@ -3105,46 +3105,41 @@ compare(az.degrees, (151.19707488767745, 338.13295291812307, 156.2971102404744, 191.29497427201525), 0.0005 * arcsecond) def test_hipparcos_conversion0(earth): - line = 'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' - star = hipparcos.parse(line) - compare(star.ra.hours, 2.530301023497941, 0.001 * ra_arcsecond) - compare(star.dec.degrees, 89.26410950742938, 0.001 * arcsecond) + line = b'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' + df = hipparcos.load_dataframe(BytesIO(line)) + star = starlib.Star.from_dataframe(df.iloc[0]) ra, dec, distance = earth.at(load.timescale().tt_jd(2440423.345833333)).observe(star).radec() compare(ra.hours, 2.5283697000528966, 0.00001 * ra_arcsecond) compare(dec.degrees, 89.26420852419295, 0.00001 * arcsecond) def test_hipparcos_conversion1(earth): - line = 'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' - star = hipparcos.parse(line) - compare(star.ra.hours, 2.530301023497941, 0.001 * ra_arcsecond) - compare(star.dec.degrees, 89.26410950742938, 0.001 * arcsecond) + line = b'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' + df = hipparcos.load_dataframe(BytesIO(line)) + star = starlib.Star.from_dataframe(df.iloc[0]) ra, dec, distance = earth.at(load.timescale().tt_jd(2448031.5)).observe(star).radec() compare(ra.hours, 2.529691010447949, 0.00001 * ra_arcsecond) compare(dec.degrees, 89.26413900274704, 0.00001 * arcsecond) def test_hipparcos_conversion2(earth): - line = 'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' - star = hipparcos.parse(line) - compare(star.ra.hours, 2.530301023497941, 0.001 * ra_arcsecond) - compare(star.dec.degrees, 89.26410950742938, 0.001 * arcsecond) + line = b'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' + df = hipparcos.load_dataframe(BytesIO(line)) + star = starlib.Star.from_dataframe(df.iloc[0]) ra, dec, distance = earth.at(load.timescale().tt_jd(2451545.0)).observe(star).radec() compare(ra.hours, 2.5302921836971946, 0.00001 * ra_arcsecond) compare(dec.degrees, 89.26411033462212, 0.00001 * arcsecond) def test_hipparcos_conversion3(earth): - line = 'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' - star = hipparcos.parse(line) - compare(star.ra.hours, 2.530301023497941, 0.001 * ra_arcsecond) - compare(star.dec.degrees, 89.26410950742938, 0.001 * arcsecond) + line = b'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' + df = hipparcos.load_dataframe(BytesIO(line)) + star = starlib.Star.from_dataframe(df.iloc[0]) ra, dec, distance = earth.at(load.timescale().tt_jd(2456164.5)).observe(star).radec() compare(ra.hours, 2.5311170753257395, 0.00001 * ra_arcsecond) compare(dec.degrees, 89.26406913848278, 0.00001 * arcsecond) def test_hipparcos_conversion4(earth): - line = 'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' - star = hipparcos.parse(line) - compare(star.ra.hours, 2.530301023497941, 0.001 * ra_arcsecond) - compare(star.dec.degrees, 89.26410950742938, 0.001 * arcsecond) + line = b'H| 11767| |02 31 47.08|+89 15 50.9| 1.97|1|H|037.94614689|+89.26413805| | 7.56| 44.22| -11.74| 0.39| 0.45| 0.48| 0.47| 0.55|-0.16| 0.05| 0.27|-0.01| 0.08| 0.05| 0.04|-0.12|-0.09|-0.36| 1| 1.22| 11767| 2.756|0.003| 2.067|0.003| | 0.636|0.003|T|0.70|0.00|L| | 2.1077|0.0021|0.014|102| | 2.09| 2.13| 3.97|P|1|A|02319+8915|I| 1| 1| | | | | | | | | |S| |P| 8890|B+88 8 | | |0.68|F7:Ib-IIv SB|G\n' + df = hipparcos.load_dataframe(BytesIO(line)) + star = starlib.Star.from_dataframe(df.iloc[0]) ra, dec, distance = earth.at(load.timescale().tt_jd([2440423.345833333, 2448031.5, 2451545.0, 2456164.5])).observe(star).radec() compare(ra.hours, (2.5283697000528966, 2.529691010447949, 2.5302921836971946, 2.5311170753257395), 0.00001 * ra_arcsecond) compare(dec.degrees, (89.26420852419295, 89.26413900274704, 89.26411033462212, 89.26406913848278), 0.00001 * arcsecond) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/tests/test_api.py new/skyfield-1.29/skyfield/tests/test_api.py --- old/skyfield-1.27/skyfield/tests/test_api.py 2020-08-30 18:58:27.000000000 +0200 +++ new/skyfield-1.29/skyfield/tests/test_api.py 2020-09-24 18:55:30.000000000 +0200 @@ -126,14 +126,6 @@ assert str(p.from_altaz(alt=a, az=a).distance()) == '0.1 au' assert str(p.from_altaz(alt=a, az=a, distance=d).distance()) == '0.234 au' -def test_named_star_throws_valueerror(): - with assert_raises(ValueError, 'No star named foo known to skyfield'): - api.NamedStar('foo') - -def test_named_star_returns_star(): - s = api.NamedStar('Polaris') - assert isinstance(s, api.Star) - def test_github_81(ts): t = ts.utc(1980, 1, 1) assert t == t diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/tests/test_earth_satellites.py new/skyfield-1.29/skyfield/tests/test_earth_satellites.py --- old/skyfield-1.27/skyfield/tests/test_earth_satellites.py 2020-09-13 13:33:26.000000000 +0200 +++ new/skyfield-1.29/skyfield/tests/test_earth_satellites.py 2020-09-17 17:49:26.000000000 +0200 @@ -106,7 +106,8 @@ jd_epoch = sat.model.jdsatepoch + sat.model.jdsatepochF three_days_later = jd_epoch + 3.0 - offset = ts.tt(jd=three_days_later)._utc_float() - three_days_later + whole, fraction, is_leap_second = ts.tt(jd=three_days_later)._utc_float(0.0) + offset = whole + fraction - three_days_later t = ts.tt(jd=three_days_later - offset) # First, a crucial sanity check (which is, technically, a test of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/tests/test_strs_and_reprs.py new/skyfield-1.29/skyfield/tests/test_strs_and_reprs.py --- old/skyfield-1.27/skyfield/tests/test_strs_and_reprs.py 2020-09-11 09:04:48.000000000 +0200 +++ new/skyfield-1.29/skyfield/tests/test_strs_and_reprs.py 2020-09-16 01:15:55.000000000 +0200 @@ -25,7 +25,7 @@ """) assert repr(e) == expected -def test_satellite_with_name(eph): +def test_satellite_with_name(): s = EarthSatellite(lines[1], lines[2], lines[0]) expected = dedent("""\ ISS (ZARYA) catalog #25544 epoch 2013-11-26 13:57:03 UTC @@ -36,7 +36,7 @@ """) assert repr(s) == expected -def test_satellite_without_name(eph): +def test_satellite_without_name(): s = EarthSatellite(lines[1], lines[2]) expected = dedent("""\ catalog #25544 epoch 2013-11-26 13:57:03 UTC @@ -47,18 +47,21 @@ """) assert repr(s) == expected -def test_topos(eph): +def test_topos(): t = Topos(latitude_degrees=42.2, longitude_degrees=-88.1) expected = dedent("""\ Earth latitude 42deg 12' 00.0" N longitude -88deg 06' 00.0" E """) assert str(t) == expected - # TODO expected = dedent("""\ <Topos Earth latitude 42deg 12' 00.0" N longitude -88deg 06' 00.0" E> """) assert repr(t) == expected + t.target_name = 'Custom name' + assert str(t) == 'Custom name' + assert repr(t) == "<Topos Custom name>" + def test_jpl_vector_sum(eph): e = eph['earth'] expected = dedent("""\ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/tests/test_timelib.py new/skyfield-1.29/skyfield/tests/test_timelib.py --- old/skyfield-1.27/skyfield/tests/test_timelib.py 2020-09-14 02:54:44.000000000 +0200 +++ new/skyfield-1.29/skyfield/tests/test_timelib.py 2020-09-26 00:51:10.000000000 +0200 @@ -1,38 +1,43 @@ import datetime as dt_module +import numbers import numpy as np import sys from assay import assert_raises from pytz import timezone from skyfield import api from skyfield.constants import DAY_S, T0 -from skyfield.timelib import utc, calendar_tuple, julian_date +from skyfield.timelib import ( + GREGORIAN_START, GREGORIAN_START_ENGLAND, + calendar_tuple, compute_calendar_date, julian_date, julian_day, utc, +) from datetime import datetime one_second = 1.0 / DAY_S epsilon = one_second * 42.0e-6 # 20.1e-6 is theoretical best precision -time_parameter = ['tai', 'tt', 'tdb', 'ut1'] +continuous_timescale = ['tai', 'tt', 'tdb', 'ut1'] +time_scale_name = ['utc', 'tai', 'tt', 'tdb', 'ut1'] time_value = [(1973, 1, 18, 1, 35, 37.5), 2441700.56640625] def ts(): yield api.load.timescale() -def test_time_creation_methods(ts, time_parameter, time_value): - method = getattr(ts, time_parameter) +def test_time_creation_methods(ts, continuous_timescale, time_value): + method = getattr(ts, continuous_timescale) if isinstance(time_value, tuple): t = method(*time_value) else: t = method(jd=time_value) # TODO: deprecate - assert getattr(t, time_parameter) == 2441700.56640625 + assert getattr(t, continuous_timescale) == 2441700.56640625 # Also go ahead and test the calendar and formatting operations. - tup = getattr(t, time_parameter + '_calendar')() + tup = getattr(t, continuous_timescale + '_calendar')() assert tup == (1973, 1, 18, 1, 35, 37.5) - strftime = getattr(t, time_parameter + '_strftime') + strftime = getattr(t, continuous_timescale + '_strftime') string = strftime() - assert string == '1973-01-18 01:35:38 ' + time_parameter.upper() + assert string == '1973-01-18 01:35:38 ' + continuous_timescale.upper() if sys.version_info <= (3,): return # we do not currently support %f under Python 2 @@ -40,6 +45,11 @@ string = strftime('%S.%f') assert string == '37.500000' +def test_is_time_iterable(ts, time_scale_name): + t = getattr(ts, time_scale_name)(2020, 9, (25, 26)) + for item in t: + pass + def test_strftime_on_prehistoric_dates(ts): if sys.version_info <= (3,): return # Python 2 time.strftime() complains about negative years @@ -98,7 +108,6 @@ assert c[:5] == (2020, 6, 7, 2, 2) assert '%.10f' % c[5] == '12.0123456789' -time_scale_name = ['utc', 'tai', 'tt', 'tdb'] time_params_with_array = [ ((2018, 2019, 2020), 3, 25, 13, 1, 10), (2018, (3, 4, 5), 25, 13, 1, 10), @@ -216,6 +225,10 @@ assert dt == tz.localize(datetime(1969, 7, 20, 16, 18, 0, 0)) assert leap_second == 0 +def test_toordinal(ts): + t = ts.utc(1973, 12, 31, 11, 59, 60) + assert t.toordinal() == 720623.5 + def test_utc_datetime(ts): t = ts.utc(1969, 7, 20, 20, 18, 42.186479) dt = t.utc_datetime() @@ -400,9 +413,6 @@ assert repr(ts.tt_jd([1, 2, 3, 4])) == '<Time tt=[1.0 ... 4.0] len=4>' def test_jd_calendar(): - - import numbers - # Check a specific instance (using UNIX epoch here, though that's # an arbitrary choice) jd_unix = 2440587.5 @@ -438,6 +448,121 @@ # Check reversal of array assert (julian_date(*cal_array) == jd_array).all() +def test_raw_julian_gregorian_cutover(): + gregory = 2299161 + assert compute_calendar_date(gregory - 2, gregory) == (1582, 10, 3) + assert compute_calendar_date(gregory - 1, gregory) == (1582, 10, 4) + assert compute_calendar_date(gregory + 0, gregory) == (1582, 10, 15) + assert compute_calendar_date(gregory + 1, gregory) == (1582, 10, 16) + + jd = np.arange(gregory - 2, gregory + 2) + assert [list(a) for a in compute_calendar_date(jd, gregory)] == [ + [1582, 1582, 1582, 1582], + [10, 10, 10, 10], + [3, 4, 15, 16], + ] + + assert julian_day(1582, 10, 3, gregory) == (gregory - 2) + assert julian_day(1582, 10, 4, gregory) == (gregory - 1) + assert julian_day(1582, 10, 15, gregory) == (gregory + 0) + assert julian_day(1582, 10, 16, gregory) == (gregory + 1) + + days = [3, 4, 15, 16] + assert list(julian_day(1582, 10, np.array(days), gregory)) == [ + 2299159, 2299160, 2299161, 2299162, + ] + +def test_constructor_julian_gregorian_cutover(ts, time_scale_name): + if sys.version_info <= (3,): + return # Python 2 time.strftime() complains about the year 1582 + + def jd(y, m, d): + t = getattr(ts, time_scale_name)(y, m, d) + if time_scale_name == 'utc': + return sum(t._utc_float(0.0)) + return getattr(t, time_scale_name) + + ts = api.load.timescale() + + assert jd(1582, 10, 4) == 2299149.5 + assert jd(1582, 10, 15) == 2299160.5 + assert jd(1752, 9, 2) == 2361209.5 + assert jd(1752, 9, 14) == 2361221.5 + + ts.julian_calendar_cutoff = GREGORIAN_START + + assert jd(1582, 10, 4) == 2299159.5 + assert jd(1582, 10, 15) == 2299160.5 + assert jd(1752, 9, 2) == 2361209.5 + assert jd(1752, 9, 14) == 2361221.5 + + ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND + + assert jd(1582, 10, 4) == 2299159.5 + assert jd(1582, 10, 15) == 2299170.5 + assert jd(1752, 9, 2) == 2361220.5 + assert jd(1752, 9, 14) == 2361221.5 + +def test_calendar_tuple_julian_gregorian_cutover(ts, time_scale_name): + if sys.version_info <= (3,): + return # Python 2 time.strftime() complains about the year 1582 + + def ymd(jd): + t = ts.tt_jd(jd, 0.1) + if time_scale_name == 'utc': + return t.utc[:3] + return getattr(t, time_scale_name + '_calendar')()[:3] + + ts = api.load.timescale() + + assert ymd(2299149.5) == (1582, 10, 4) + assert ymd(2299160.5) == (1582, 10, 15) + assert ymd(2361209.5) == (1752, 9, 2) + assert ymd(2361221.5) == (1752, 9, 14) + + ts.julian_calendar_cutoff = GREGORIAN_START + + assert ymd(2299159.5) == (1582, 10, 4) + assert ymd(2299160.5) == (1582, 10, 15) + assert ymd(2361209.5) == (1752, 9, 2) + assert ymd(2361221.5) == (1752, 9, 14) + + ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND + + assert ymd(2299159.5) == (1582, 10, 4) + assert ymd(2299170.5) == (1582, 10, 15) + assert ymd(2361220.5) == (1752, 9, 2) + assert ymd(2361221.5) == (1752, 9, 14) + +def test_strftime_julian_gregorian_cutover(ts, time_scale_name): + if sys.version_info <= (3,): + return # Python 2 time.strftime() complains about the year 1582 + + def ymd(jd): + t = ts.tt_jd(jd, 0.1) + return getattr(t, time_scale_name + '_strftime')('%Y %m %d') + + ts = api.load.timescale() + + assert ymd(2299149.5) == '1582 10 04' + assert ymd(2299160.5) == '1582 10 15' + assert ymd(2361209.5) == '1752 09 02' + assert ymd(2361221.5) == '1752 09 14' + + ts.julian_calendar_cutoff = GREGORIAN_START + + assert ymd(2299159.5) == '1582 10 04' + assert ymd(2299160.5) == '1582 10 15' + assert ymd(2361209.5) == '1752 09 02' + assert ymd(2361221.5) == '1752 09 14' + + ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND + + assert ymd(2299159.5) == '1582 10 04' + assert ymd(2299170.5) == '1582 10 15' + assert ymd(2361220.5) == '1752 09 02' + assert ymd(2361221.5) == '1752 09 14' + def test_time_equality(ts): t0 = ts.tt_jd(2459008.5, 0.125) t1 = ts.tt_jd(2459008.0, 0.625) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/timelib.py new/skyfield-1.29/skyfield/timelib.py --- old/skyfield-1.27/skyfield/timelib.py 2020-09-15 00:48:15.000000000 +0200 +++ new/skyfield-1.29/skyfield/timelib.py 2020-09-26 00:51:39.000000000 +0200 @@ -18,6 +18,9 @@ ) from .precessionlib import compute_precession +GREGORIAN_START = 2299161 +GREGORIAN_START_ENGLAND = 2361222 + CalendarTuple = namedtuple('CalendarTuple', 'year month day hour minute second') class CalendarArray(ndarray): @@ -87,6 +90,7 @@ self._leap_reverse_dates = leap_dates + leap_offsets / DAY_S self.J2000 = Time(self, float_(T0)) self.B1950 = Time(self, float_(B1950)) + self.julian_calendar_cutoff = None def now(self): """Return the current date and time as a `Time` object. @@ -106,35 +110,21 @@ 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 + return self._utc(_datetime_to_utc_tuple(datetime)) def from_datetimes(self, datetime_list): - """Return a `Time` for a Python ``datetime`` list. + """Return a `Time` for a list of Python ``datetime``\ s. The ``datetime`` objects must each be “timezone-aware”: they must each have a time zone object as their ``tzinfo`` attribute instead of ``None``. """ - pairs = [_utc_datetime_to_tai(self.leap_dates, self.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 + tuples = (_datetime_to_utc_tuple(d) for d in datetime_list) + return self._utc(array(value) for value in zip(*tuples)) def utc(self, year, month=1, day=1, hour=0, minute=0, second=0.0): - """Build a `Time` from a UTC calendar date. - - 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. - - """ + """Build a `Time` from a UTC `calendar date`.""" # TODO: someday deprecate passing datetime objects here, as # there are now separate constructors for them. if isinstance(year, datetime): @@ -144,41 +134,89 @@ 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_fraction = tai2 + tup = year, month, day, hour, minute, second + return self._utc(tup) + + def _utc(self, tup): + year, month, day, hour, minute, second = tup + whole, fraction = self._jd(year, month, day, hour, minute, 0.0) + i = searchsorted(self.leap_dates, whole + fraction, 'right') + fraction += (self.leap_offsets[i] + second) / DAY_S + whole, fraction = _reconcile(whole, fraction) # second could be array + t = Time(self, whole, fraction + tt_minus_tai) + t.tai_fraction = fraction return t - def tai(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, - jd=None): - """Build a `Time` from a TAI proleptic Gregorian date. + def _jd(self, year, month, day, hour, minute, second): + a = _to_array + cutoff = self.julian_calendar_cutoff + whole = julian_day(a(year), a(month), a(day), cutoff) - 0.5 + fraction = (a(second) + a(minute) * 60.0 + a(hour) * 3600.0) / DAY_S + return _reconcile(whole, fraction) - Supply the International Atomic Time (TAI) as a proleptic - Gregorian calendar date: + def _cal(self, whole, fraction): + return calendar_tuple(whole, fraction, self.julian_calendar_cutoff) - >>> t = ts.tai(2014, 1, 18, 1, 35, 37.5) - >>> t.tai - 2456675.56640625 - >>> t.tai_calendar() - (2014, 1, 18, 1, 35, 37.5) + def _strftime(self, format, jd, fraction, seconds_bump=None): + # Python forces an unhappy choice upon us: either use the faster + # time.strftime() and lose support for '%f', or use the slower + # datetime.strftime() and crash if years are negative. We take the + # first option, but then patch '%f' support back in by secretly + # passing the microseconds string as the time zone name. After all, + # the routines supported by this function never use time zones. + # What could go wrong? + + ms = _format_uses_milliseconds(format) + + if ms: + fraction = fraction + 1e-16 # encourage .0 to not turn into .999999 + elif _format_uses_seconds(format): + fraction = fraction + _half_second + elif _format_uses_minutes(format): + fraction = fraction + _half_minute + + year, month, day, hour, minute, second = self._cal(jd, fraction) + z = year * 0 + + # TODO: will this always produce the same whole number that + # calendar_tuple() produces internally? Or should we make a private + # version of calendar_tuple() that returns it to us for use here? + weekday = (fraction + 0.5 + _to_array(jd)).astype(int) % 7 + + if ms: + format = format[:ms.start()] + '%Z' + format[ms.end():] + second = (second * 1e6).astype(int) + second, usec = divmod(second, 1000000) + if seconds_bump is not None: + second += seconds_bump + if getattr(jd, 'ndim', 0): + u = ['%06d' % u for u in usec] + tup = year, month, day, hour, minute, second, weekday, z, z, u + return [strftime(format, struct_time(t)) for t in zip(*tup)] + u = '%06d' % usec + tup = year, month, day, hour, minute, second, weekday, z, z, u + return strftime(format, struct_time(tup)) + else: + second = second.astype(int) + if seconds_bump is not None: + second += seconds_bump + tup = year, month, day, hour, minute, second, weekday, z, z + if getattr(jd, 'ndim', 0): + return [strftime(format, item) for item in zip(*tup)] + return strftime(format, tup) - """ + def tai(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, + jd=None): + """Build a `Time` from an International Atomic Time `calendar date`.""" if jd is not None: 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 - whole, fraction = _reconcile(whole, fraction) + whole, fraction = self._jd(year, month, day, hour, minute, second) t = Time(self, whole, fraction + tt_minus_tai) t.tai_fraction = fraction return t def tai_jd(self, jd, fraction=0.0): - """Build a `Time` from a TAI Julian date.""" + """Build a `Time` from an International Atomic Time Julian date.""" whole, fraction2 = divmod(_to_array(jd), 1.0) fraction2 += fraction t = Time(self, whole, fraction2 + tt_minus_tai) @@ -187,34 +225,20 @@ def tt(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, jd=None): - """Build a `Time` from a TT calendar date. - - Supply the Terrestrial Time (TT) as a proleptic Gregorian - calendar date: - - >>> t = ts.tt(2014, 1, 18, 1, 35, 37.5) - >>> t.tt - 2456675.56640625 - >>> t.tt_calendar() - (2014, 1, 18, 1, 35, 37.5) - - """ + """Build a `Time` from a Terrestrial Time `calendar date`.""" if jd is not None: 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 - whole, fraction = _reconcile(whole, fraction) + whole, fraction = self._jd(year, month, day, hour, minute, second) return Time(self, whole, fraction) def tt_jd(self, jd, fraction=0.0): - """Build a `Time` from a TT Julian date.""" + """Build a `Time` from a Terrestrial Time Julian date.""" whole, fraction2 = divmod(_to_array(jd), 1.0) fraction2 += fraction return Time(self, whole, fraction2) def J(self, year): - """Build a `Time` from a TT Julian year or array of years. + """Build a `Time` from a Terrestrial Time Julian year or array. Julian years are convenient uniform periods of exactly 365.25 days of Terrestrial Time, centered on 2000 January 1 12h TT = @@ -226,29 +250,15 @@ def tdb(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, jd=None): - """Build a `Time` from a TDB calendar date. - - Supply the Barycentric Dynamical Time (TDB) as a proleptic - Gregorian calendar date: - - >>> t = ts.tdb(2014, 1, 18, 1, 35, 37.5) - >>> t.tdb - 2456675.56640625 - - """ + """Build a `Time` from a Barycentric Dynamical Time `calendar date`.""" if jd is not None: - tdb = jd - else: - tdb = julian_date( - _to_array(year), _to_array(month), _to_array(day), - _to_array(hour), _to_array(minute), _to_array(second), - ) - tdb = _to_array(tdb) - t = Time(self, tdb, - tdb_minus_tt(tdb) / DAY_S) - return t + return self.tdb_jd(jd) # deprecate someday + whole, fraction = self._jd(year, month, day, hour, minute, second) + jd = whole + fraction # TODO: why do tests break if we pass separately + return Time(self, jd, - tdb_minus_tt(jd) / DAY_S) def tdb_jd(self, jd, fraction=0.0): - """Build `Time` from a Barycentric Dynamical Time (TDB) Julian date.""" + """Build `Time` from a Barycentric Dynamical Time Julian date.""" whole, fraction2 = divmod(jd, 1.0) fraction2 += fraction t = Time(self, whole, fraction2 - tdb_minus_tt(jd, fraction) / DAY_S) @@ -257,27 +267,14 @@ def ut1(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, jd=None): - """Build a `Time` from a UT1 calendar date. - - Supply the Universal Time (UT1) as a proleptic Gregorian - calendar date: - - >>> t = ts.ut1(2014, 1, 18, 1, 35, 37.5) - >>> t.ut1 - 2456675.56640625 - - """ - 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), - ) - return self.ut1_jd(ut1) + """Build a `Time` from a UT1 Universal Time `calendar date`.""" + if jd is None: # TODO: deprecate the jd parameter to this method + whole, fraction = self._jd(year, month, day, hour, minute, second) + jd = whole + fraction # TODO: can we pass high precision on? + return self.ut1_jd(jd) def ut1_jd(self, jd): - """Build a `Time` from a UT1 Julian date.""" + """Build a `Time` from a UT1 Universal Time Julian date.""" ut1 = _to_array(jd) # Estimate TT = UT1, to get a rough Delta T estimate. @@ -295,7 +292,7 @@ # and look for the notebook "error-in-timescale-ut1.ipynb". delta_t_approx /= DAY_S t = Time(self, ut1, delta_t_approx) - t.ut1_fraction = 0.0 + t.ut1_fraction = 0.0 * ut1 return t def from_astropy(self, t): @@ -344,6 +341,8 @@ # 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_fraction', 'tdb_fraction', 'ut1_fraction': + # TODO: drat, I suspect this forces the creation of all of + # the fractions whether we need them or not. value = getattr(self, name, None) if value is not None: setattr(t, name, value[index]) @@ -403,7 +402,8 @@ directly as a coordinate for a plot. """ - return self._utc_float() - 1721424.5 + whole, fraction, is_leap_second = self._utc_float(0.0) + return whole - 1721424.5 + fraction def utc_datetime(self): """Convert to a Python ``datetime`` in UTC. @@ -437,14 +437,13 @@ """ year, month, day, hour, minute, second = self._utc_tuple( _half_microsecond) - second, fraction = divmod(second, 1.0) - second = second.astype(int) + micro = (second * 1e6).astype(int) + second, micro = divmod(micro, 1000000) leap_second = second // 60 second -= leap_second # fit within limited bounds of Python datetime - micro = (fraction * 1e6).astype(int) if self.shape: - utcs = [utc] * self.shape[0] - argsets = zip(year, month, day, hour, minute, second, micro, utcs) + zone = [utc] * self.shape[0] + argsets = zip(year, month, day, hour, minute, second, micro, zone) dt = array([datetime(*args) for args in argsets]) else: dt = datetime(year, month, day, hour, minute, second, micro, utc) @@ -532,12 +531,8 @@ Otherwise the value is truncated rather than rounded. """ - ts = self.ts - tai = self.tai - i = searchsorted(ts._leap_reverse_dates, tai, 'right') - fraction = self.tai_fraction - ts.leap_offsets[i] / DAY_S - is_leap_second = fraction + self.whole - ts.leap_dates[i-1] < 0 - return _strftime(format, self.whole, fraction, is_leap_second) + whole, fraction, is_leap_second = self._utc_float(0.0) + return self.ts._strftime(format, self.whole, fraction, is_leap_second) def _utc_tuple(self, offset=0.0): """Return UTC as (year, month, day, hour, minute, second.fraction). @@ -551,60 +546,54 @@ throw away the seconds; and so forth. """ - ts = self.ts - tai = self.tai + offset - i = searchsorted(ts._leap_reverse_dates, tai, 'right') - year, month, day, hour, minute, second = calendar_tuple( - self.whole, - offset - ts.leap_offsets[i] / DAY_S + self.tai_fraction, - ) - j = tai - ts.leap_offsets[i] / DAY_S - is_leap_second = j < ts.leap_dates[i-1] + jd, fraction, is_leap_second = self._utc_float(offset) + year, month, day, hour, minute, second = self.ts._cal(jd, fraction) second += is_leap_second return year, month, day, hour.astype(int), minute.astype(int), second - def _utc_float(self): - """Return UTC as a floating point Julian date.""" - tai = self.tai + def _utc_float(self, offset): ts = self.ts - i = searchsorted(ts._leap_reverse_dates, tai, 'right') - return tai - ts.leap_offsets[i] / DAY_S + i = searchsorted(ts._leap_reverse_dates, self.tai + offset, 'right') + whole = self.whole + fraction = offset - ts.leap_offsets[i] / DAY_S + self.tai_fraction + is_leap_second = (whole + fraction) < ts.leap_dates[i-1] + return whole, fraction, is_leap_second # Calendar tuples. def tai_calendar(self): - """Return TAI as Gregorian (year, month, day, hour, minute, second).""" - return calendar_tuple(self.whole, self.tai_fraction) + """TAI as a (year, month, day, hour, minute, second) `calendar date`.""" + return self.ts._cal(self.whole, self.tai_fraction) def tt_calendar(self): - """Return TT as Gregorian (year, month, day, hour, minute, second).""" - return calendar_tuple(self.whole, self.tt_fraction) + """TT as a (year, month, day, hour, minute, second) `calendar date`.""" + return self.ts._cal(self.whole, self.tt_fraction) def tdb_calendar(self): - """Return TDB as Gregorian (year, month, day, hour, minute, second).""" - return calendar_tuple(self.whole, self.tdb_fraction) + """TDB as a (year, month, day, hour, minute, second) `calendar date`.""" + return self.ts._cal(self.whole, self.tdb_fraction) def ut1_calendar(self): - """Return UT1 as Gregorian (year, month, day, hour, minute, second).""" - return calendar_tuple(self.whole, self.ut1_fraction) + """UT1 as a (year, month, day, hour, minute, second) `calendar date`.""" + return self.ts._cal(self.whole, self.ut1_fraction) # Date formatting. def tai_strftime(self, format='%Y-%m-%d %H:%M:%S TAI'): """Format TAI with a datetime strftime() format string.""" - return _strftime(format, self.whole, self.tai_fraction) + return self.ts._strftime(format, self.whole, self.tai_fraction) def tt_strftime(self, format='%Y-%m-%d %H:%M:%S TT'): """Format TT with a datetime strftime() format string.""" - return _strftime(format, self.whole, self.tt_fraction) + return self.ts._strftime(format, self.whole, self.tt_fraction) def tdb_strftime(self, format='%Y-%m-%d %H:%M:%S TDB'): """Format TDB with a datetime strftime() format string.""" - return _strftime(format, self.whole, self.tdb_fraction) + return self.ts._strftime(format, self.whole, self.tdb_fraction) def ut1_strftime(self, format='%Y-%m-%d %H:%M:%S UT1'): """Format UT1 with a datetime strftime() format string.""" - return _strftime(format, self.whole, self.ut1_fraction) + return self.ts._strftime(format, self.whole, self.ut1_fraction) # Convenient caching of several expensive functions of time. @@ -713,6 +702,7 @@ def dut1(self): ts = self.ts i = searchsorted(ts._leap_reverse_dates, self.tai, 'right') + print(32.184, ts.leap_offsets[i], self.delta_t) return 32.184 + ts.leap_offsets[i] - self.delta_t @reify @@ -792,14 +782,24 @@ @reify def NT(self): return rollaxis(self.N, 1) -def julian_day(year, month=1, day=1): - """Given a proleptic Gregorian calendar date, return a Julian day int.""" - janfeb = month < 3 - return (day - + 1461 * (year + 4800 - janfeb) // 4 - + 367 * (month - 2 + janfeb * 12) // 12 - - 3 * ((year + 4900 - janfeb) // 100) // 4 - - 32075) +def julian_day(year, month=1, day=1, julian_before=None): + """Given a calendar date, return a Julian day integer. + + Uses the proleptic Gregorian calendar unless ``julian_before`` is + set to a specific Julian day, in which case the Julian calendar is + used for dates older than that. + + """ + # See the Explanatory Supplement to the Astronomical Almanac 15.11. + janfeb = month <= 2 + g = year + 4716 - janfeb + f = (month + 9) % 12 + e = 1461 * g // 4 + day - 1402 + J = e + (153 * f + 2) // 5 + + mask = 1 if (julian_before is None) else (J >= julian_before) + J += (38 - (g + 184) // 100 * 3 // 4) * mask + return J def julian_date(year, month=1, day=1, hour=0, minute=0, second=0.0): """Given a proleptic Gregorian calendar date and time, build a Julian date. @@ -818,34 +818,14 @@ def compute_calendar_date(jd_integer, julian_before=None): """Convert Julian day ``jd_integer`` into a calendar (year, month, day). - By default, this returns a proleptic Gregorian date. If you instead - want to use the Julian calendar for old dates, provide a Julian day - ``julian_before`` on which the calendar should cut over. One - popular choice is the date on which Pope Gregory adopted the new - calendar in October 1582: - - >>> gregory = 2299161 - >>> for jd in range(gregory - 2, gregory + 2): - ... print(compute_calendar_date(jd, gregory)) - (1582, 10, 3) - (1582, 10, 4) - (1582, 10, 15) - (1582, 10, 16) - - Another is the date on which England switched in September 1752: - - >>> england = 2361222 - >>> for jd in range(england - 2, england + 2): - ... print(compute_calendar_date(jd, england)) - (1752, 9, 1) - (1752, 9, 2) - (1752, 9, 14) - (1752, 9, 15) + Uses the proleptic Gregorian calendar unless ``julian_before`` is + set to a specific Julian day, in which case the Julian calendar is + used for dates older than that. """ use_gregorian = (julian_before is None) or (jd_integer >= julian_before) - # Adapted from the Astronomical Almanac 15.11.1. + # See the Explanatory Supplement to the Astronomical Almanac 15.11. f = jd_integer + 1401 f += use_gregorian * ((4 * jd_integer + 274277) // 146097 * 3 // 4 - 38) e = 4 * f + 3 @@ -858,13 +838,13 @@ calendar_date = compute_calendar_date # old name, in case anyone used it -def calendar_tuple(jd_float, fraction=0.0): +def calendar_tuple(jd_float, fraction=0.0, julian_before=None): """Return a (year, month, day, hour, minute, second.fraction) tuple.""" jd_float = _to_array(jd_float) whole1, fraction1 = divmod(jd_float, 1.0) whole2, fraction = divmod(fraction1 + fraction + 0.5, 1.0) whole = (whole1 + whole2).astype(int) - year, month, day = compute_calendar_date(whole) + year, month, day = compute_calendar_date(whole, julian_before) second = fraction * 86400.0 minute, second = divmod(second, 60.0) minute = minute.astype(int) @@ -964,71 +944,14 @@ _format_uses_seconds = re.compile(r'%[-_0^#EO]*[STXc]').search _format_uses_minutes = re.compile(r'%[-_0^#EO]*[MR]').search -def _strftime(format, jd, fraction, seconds_bump=None): - # Python forces an unhappy choice upon us: either use the faster - # time.strftime() and lose support for '%f', or use the slower - # datetime.strftime() and crash if years are negative. We take the - # first option, but then patch '%f' support back in by secretly - # passing the microseconds string as the time zone name. After all, - # the routines supported by this function never use time zones. - # What could go wrong? - - ms = _format_uses_milliseconds(format) - - if ms: - fraction = fraction + 1e-16 # encourage .0 to not turn into .999999 - elif _format_uses_seconds(format): - fraction = fraction + _half_second - elif _format_uses_minutes(format): - fraction = fraction + _half_minute - - year, month, day, hour, minute, second = calendar_tuple(jd, fraction) - z = year * 0 - - # TODO: will this always produce the same whole number that - # calendar_tuple() produces internally? Or should we make a private - # version of calendar_tuple() that returns it to us for use here? - weekday = (fraction + 0.5 + _to_array(jd)).astype(int) % 7 - - if ms: - format = format[:ms.start()] + '%Z' + format[ms.end():] - second = (second * 1e6).astype(int) - second, usec = divmod(second, 1000000) - if seconds_bump is not None: - second += seconds_bump - if getattr(jd, 'ndim', 0): - usec = ['%06d' % u for u in usec] - tup = year, month, day, hour, minute, second, weekday, z, z, usec - return [strftime(format, struct_time(item)) for item in zip(*tup)] - usec = '%06d' % usec - return strftime(format, struct_time( - (year, month, day, hour, minute, second, weekday, z, z, usec) - )) - else: - second = second.astype(int) - if seconds_bump is not None: - second += seconds_bump - tup = year, month, day, hour, minute, second, weekday, z, z - if getattr(jd, 'ndim', 0): - return [strftime(format, item) for item in zip(*tup)] - return strftime(format, tup) - -def _utc_datetime_to_tai(leap_dates, leap_offsets, dt): - if dt.tzinfo is None: +def _datetime_to_utc_tuple(dt): + z = dt.tzinfo + if z is None: raise ValueError(_naive_complaint) - if dt.tzinfo is not utc: + if z 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) - -def _utc_to_tai(leap_dates, leap_offsets, - year, month, day, hour, minute, second): - j = julian_day(year, month, day) - 0.5 - i = searchsorted(leap_dates, j, 'right') - seconds = leap_offsets[i] + second + minute * 60.0 + hour * 3600.0 - j, seconds = _reconcile(j, seconds) - return j, seconds / DAY_S + return (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second + dt.microsecond / 1e6) _JulianDate_deprecation_message = """Skyfield no longer supports direct\ instantiation of JulianDate objects (which are now called Time objects) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/skyfield-1.27/skyfield/toposlib.py new/skyfield-1.29/skyfield/toposlib.py --- old/skyfield-1.27/skyfield/toposlib.py 2020-09-03 23:46:40.000000000 +0200 +++ new/skyfield-1.29/skyfield/toposlib.py 2020-09-16 01:15:34.000000000 +0200 @@ -4,6 +4,7 @@ from .constants import ASEC2RAD, tau from .earthlib import refract, terra from .functions import mxmxm, mxv, rot_x, rot_y, rot_z +from .descriptorlib import reify from .units import Distance, Angle, _interpret_ltude from .vectorlib import VectorFunction @@ -72,7 +73,7 @@ # reference that delays garbage collection.) return self - @property + @reify # not @property, so users have the option of overwriting it def target_name(self): m = self.elevation.m e = ' elevation {0:.0f} m'.format(m) if m else ''