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 ''


Reply via email to