1import datetime as dt_module
2import numbers
3import numpy as np
4import sys
5from assay import assert_raises
6from pytz import timezone
7from numpy import array, inf
8from skyfield import api
9from skyfield.constants import DAY_S, T0
10from skyfield.data import iers
11from skyfield.timelib import (
12    GREGORIAN_START, GREGORIAN_START_ENGLAND, Time, Timescale,
13    calendar_tuple, compute_calendar_date, julian_date, julian_day, utc,
14)
15from datetime import datetime
16
17one_second = 1.0 / DAY_S
18epsilon = one_second * 42.0e-6  # 20.1e-6 is theoretical best precision
19
20continuous_timescale = ['tai', 'tt', 'tdb', 'ut1']
21time_scale_name = ['utc', 'tai', 'tt', 'tdb', 'ut1']
22time_value = [(1973, 1, 18, 1, 35, 37.5), 2441700.56640625]
23
24def test_timescale_with_old_fashioned_leap_second_table():
25    # Skyfield no longer uses the awkward old-style leap second table,
26    # written before I knew better, with inf's on both ends; but in case
27    # any users built such tables and supplied them manually:
28    delta_t = array([[2442046.5005113888], [44.4782581]])
29    leap_dates = array([-inf, 2441317.5, 2441499.5, 2441683.5, 2442048.5, inf])
30    leap_offsets = array([10, 10, 10, 11, 12, 13])
31    ts = Timescale(delta_t, leap_dates, leap_offsets)
32    t = ts.tai(1973, 1, 1, 0, 0, [11, 12])
33    assert t.utc.T.tolist() == [
34        [1972, 12, 31, 23, 59, 60],
35        [1973, 1, 1, 0, 0, 0],
36    ]
37
38    # The offset prior to the first leap second should be +10.
39    t = ts.tai(1970, 1, 1)
40    assert t.utc == (1969, 12, 31, 23, 59, 50)
41
42def ts():
43    yield api.load.timescale()
44
45def ts_either():
46    # This fixture is for "tests that should pass given *either* a
47    # built-in Timescale or one loaded from a file."  Without a few such
48    # tests, an adjustment to the loading scheme can introduce a bug
49    # that no tests catch, because most tests use the builtin tables
50    # intead of loading from a file.  The tests below that use this
51    # fixture are ones that broke in the past when the loader code and
52    # timescale disagreed about something.
53    yield api.load.timescale()
54    yield api.load.timescale(builtin=False)
55
56def a(*args):
57    return np.array(args)
58
59def all_kinds_of_time_array(ts):
60    yield ts.utc(2020, 10, [8, 9])
61    yield ts.tai(2020, 10, [8, 9])
62    yield ts.tt(2020, 10, [8, 9])
63    yield ts.tdb(2020, 10, [8, 9])
64    yield ts.ut1(2020, 10, [8, 9])
65
66    jd = a(2459130.5, 2459131.5)
67
68    yield ts.tai_jd(jd)
69    yield ts.tt_jd(jd)
70    yield ts.tdb_jd(jd)
71    yield ts.ut1_jd(jd)
72    yield Time(ts, jd)
73
74    for jd, fraction in [
75            (a(2459130.5, 2459131.5), 0.25),
76            (2459130.5, a(0.0, 0.25)),
77            (a(2459130.5, 2459131.5), a(0.0, 0.25)),
78    ]:
79        yield ts.tai_jd(jd, fraction)
80        yield ts.tt_jd(jd, fraction)
81        yield ts.tdb_jd(jd, fraction)
82        # yield ts.ut1_jd(jd, fraction)  # not yet supported
83
84    # We only support direct Time instantiation for the final case,
85    # where jd and fraction are arrays that already agree in their
86    # dimensions.
87
88    yield Time(ts, jd, fraction)
89
90def test_time_creation_methods(ts, continuous_timescale, time_value):
91    method = getattr(ts, continuous_timescale)
92    if isinstance(time_value, tuple):
93        t = method(*time_value)
94    else:
95        t = method(jd=time_value) # TODO: deprecate
96    assert getattr(t, continuous_timescale) == 2441700.56640625
97
98    # Also go ahead and test the calendar and formatting operations.
99
100    tup = getattr(t, continuous_timescale + '_calendar')()
101    assert tup == (1973, 1, 18, 1, 35, 37.5)
102
103    strftime = getattr(t, continuous_timescale + '_strftime')
104    string = strftime()
105    assert string == '1973-01-18 01:35:38 ' + continuous_timescale.upper()
106
107    if sys.version_info <= (3,):
108        return  # we do not currently support %f under Python 2
109
110    string = strftime('%S.%f')
111    assert string == '37.500000'
112
113def test_months_overflow_correctly(ts):
114    assert ts.tt(2020, -1).tt_strftime('%Y-%m') == '2019-11'
115    assert ts.tt(2020, 15).tt_strftime('%Y-%m') == '2021-03'
116    assert ts.tt(2020, [-1, 0, 1, 13, 14, 15]).tt_strftime('%Y-%m') == [
117        '2019-11', '2019-12', '2020-01', '2021-01', '2021-02', '2021-03',
118    ]
119
120def test_days_overflow_correctly(ts):
121    months = range(1, 13)
122    assert ts.tt(2020, months, -1).tt_strftime('%Y-%m-%d') == [
123        '2019-12-30', '2020-01-30', '2020-02-28', '2020-03-30',
124        '2020-04-29', '2020-05-30', '2020-06-29', '2020-07-30',
125        '2020-08-30', '2020-09-29', '2020-10-30', '2020-11-29',
126    ]
127    assert ts.tt(2020, months, 0).tt_strftime('%Y-%m-%d') == [
128        '2019-12-31', '2020-01-31', '2020-02-29', '2020-03-31',
129        '2020-04-30', '2020-05-31', '2020-06-30', '2020-07-31',
130        '2020-08-31', '2020-09-30', '2020-10-31', '2020-11-30',
131    ]
132    assert ts.tt(2020, months, 32).tt_strftime('%Y-%m-%d') == [
133        '2020-02-01', '2020-03-03', '2020-04-01', '2020-05-02',
134        '2020-06-01', '2020-07-02', '2020-08-01', '2020-09-01',
135        '2020-10-02', '2020-11-01', '2020-12-02', '2021-01-01',
136    ]
137
138def test_time_can_be_indexed(ts):
139    for t in all_kinds_of_time_array(ts):
140        t[0]
141
142def test_is_time_iterable(ts, time_scale_name):
143    t = getattr(ts, time_scale_name)(2020, 9, (25, 26))
144    for item in t:
145        pass
146
147def test_strftime_on_prehistoric_dates(ts_either):
148    if sys.version_info <= (3,):
149        return  # Python 2 time.strftime() complains about negative years
150
151    ts = ts_either
152    t = ts.tt(-746, 2, 26)
153    assert t.utc_strftime('%Y %S') == '-746 18'
154    assert t.ut1_strftime('%Y %S') == '-746 28'
155    assert t.tai_strftime('%Y %S') == '-746 28'
156    assert t.tt_strftime('%Y %S') == '-746 00'
157    assert t.tdb_strftime('%Y %S') == '-746 00'
158
159    t = ts.tt(-746, 2, [26, 26])
160    assert t.utc_strftime('%Y %S') == ['-746 18'] * 2
161    assert t.ut1_strftime('%Y %S') == ['-746 28'] * 2
162    assert t.tai_strftime('%Y %S') == ['-746 28'] * 2
163    assert t.tt_strftime('%Y %S') == ['-746 00'] * 2
164    assert t.tdb_strftime('%Y %S') == ['-746 00'] * 2
165
166def test_strftime_with_microseconds():
167    if sys.version_info <= (3,):
168        return  # we do not currently support %f under Python 2
169
170    ts = api.load.timescale(builtin=False)  # load "ci/finals2000A.all"
171    t = ts.tt(1980, 9, 12)
172    assert t.utc_strftime('%Y %S %f') == '1980 08 816000'
173    assert t.ut1_strftime('%Y %S %f') == '1980 08 892775'
174    assert t.tai_strftime('%Y %S %f') == '1980 27 816000'
175    assert t.tt_strftime('%Y %S %f') == '1980 00 000000'
176    assert t.tdb_strftime('%Y %S %f') == '1980 59 998471'
177
178    t = ts.tt(1980, 9, [12, 12])
179    assert t.utc_strftime('%Y %S %f') == ['1980 08 816000'] * 2
180    assert t.ut1_strftime('%Y %S %f') == ['1980 08 892775'] * 2
181    assert t.tai_strftime('%Y %S %f') == ['1980 27 816000'] * 2
182    assert t.tt_strftime('%Y %S %f') == ['1980 00 000000'] * 2
183    assert t.tdb_strftime('%Y %S %f') == ['1980 59 998471'] * 2
184
185def test_tai_fraction_loses_no_precision(ts):
186    t = ts.tai_jd(2459008.0, 0.0123456789)
187    assert t.whole == 2459008.0
188    assert t.tai_fraction == 0.0123456789
189
190def test_tdb_fraction_loses_no_precision(ts):
191    t = ts.tdb_jd(2459008.0, 0.0123456789)
192    assert t.whole == 2459008.0
193    assert t.tdb_fraction == 0.0123456789
194
195def test_tai_seconds_preserve_10_decimal_places_in_calendar_seconds(ts):
196    t = ts.tai(2020, 6, 7, 2, 2, 12.0123456789)
197    c = t.tai_calendar()
198    assert c[:5] == (2020, 6, 7, 2, 2)
199    assert '%.10f' % c[5] == '12.0123456789'
200
201def test_tt_seconds_preserve_10_decimal_places_in_calendar_seconds(ts):
202    t = ts.tt(2020, 6, 7, 2, 2, 12.0123456789)
203    c = t.tt_calendar()
204    assert c[:5] == (2020, 6, 7, 2, 2)
205    assert '%.10f' % c[5] == '12.0123456789'
206
207time_params_with_array = [
208    ((2018, 2019, 2020), 3, 25, 13, 1, 10),
209    (2018, (3, 4, 5), 25, 13, 1, 10),
210    (2018, 3, (25, 26, 27), 13, 1, 10),
211    (2018, 3, 25, (13, 14, 15), 1, 10),
212    (2018, 3, 25, 13, (1, 2, 3), 10),
213    (2018, 3, 25, 13, 1, (10, 11, 12)),
214]
215
216def test_time_creation_with_arrays(ts, time_scale_name, time_params_with_array):
217    print(time_scale_name)
218    t = getattr(ts, time_scale_name)(*time_params_with_array)
219    t.utc_jpl()  # a reasonably complicated operation
220
221def test_timescale_utc_method_with_array_inside(ts):
222    seconds = np.arange(48.0, 58.0, 1.0)
223    t = ts.utc(1973, 12, 29, 23, 59, seconds)
224    assert seconds.shape == t.shape
225    for i, second in enumerate(seconds):
226        assert t.tai[i] == ts.utc(1973, 12, 29, 23, 59, second).tai
227
228def test_that_building_time_from_naive_datetime_raises_exception(ts):
229    with assert_raises(ValueError) as info:
230        ts.from_datetime(datetime(1973, 12, 29, 23, 59, 48))
231    assert 'import timezone' in str(info.exception)
232
233def test_building_time_from_single_utc_datetime(ts):
234    t = ts.from_datetime(datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc))
235    assert t.tai == 2442046.5
236    t = ts.utc(datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc))
237    assert t.tai == 2442046.5
238
239def test_building_time_from_single_utc_datetime_with_timezone(ts):
240    tz = timezone('US/Eastern')
241    t = ts.from_datetime(tz.localize(datetime(2020, 5, 10, 12, 44, 13, 797865)))
242    dt, leap_second = t.utc_datetime_and_leap_second()
243    assert dt == datetime(2020, 5, 10, 16, 44, 13, 797865, tzinfo=utc)
244    assert leap_second == 0
245
246def test_building_time_from_list_of_utc_datetimes(ts):
247    datetimes = [
248        datetime(1973, 12, 29, 23, 59, 48, tzinfo=utc),
249        datetime(1973, 12, 30, 23, 59, 48, tzinfo=utc),
250        datetime(1973, 12, 31, 23, 59, 48, tzinfo=utc),
251        datetime(1974, 1, 1, 23, 59, 47, tzinfo=utc),
252        datetime(1974, 1, 2, 23, 59, 47, tzinfo=utc),
253        datetime(1974, 1, 3, 23, 59, 47, tzinfo=utc),
254    ]
255    t = ts.from_datetimes(datetimes)
256    assert list(t.tai) == [
257        2442046.5, 2442047.5, 2442048.5, 2442049.5, 2442050.5, 2442051.5,
258    ]
259    t = ts.utc(datetimes)
260    assert list(t.tai) == [
261        2442046.5, 2442047.5, 2442048.5, 2442049.5, 2442050.5, 2442051.5,
262    ]
263
264def test_building_time_from_python_date(ts):
265    d = dt_module.date(2020, 7, 22)
266    t = ts.utc(d)
267    assert t.utc == (2020, 7, 22, 0, 0, 0.0)
268
269def test_timescale_linspace(ts):
270    t0 = ts.tt(2021, 11, 3, 6)
271    t1 = ts.tt(2021, 11, 5, 18)
272    t = ts.linspace(t0, t1, 3)
273    assert [n for a in t.tt_calendar() for n in a] == [
274	2021, 2021, 2021,
275        11, 11, 11,
276        3, 4, 5,
277        6, 12, 18,
278        0, 0, 0,
279        0, 0, 0,
280    ]
281
282def test_converting_ut1_to_tt(ts):
283    ten_thousand_years = 365 * 10000
284
285    jd = api.T0 - ten_thousand_years
286    t = ts.ut1(jd=jd)
287    del t.ut1_fraction          # force re-computation of UT1
288    print(jd - t.ut1)
289    assert abs(jd - t.ut1) < 1e-10
290
291    jd = api.T0 + ten_thousand_years
292    t = ts.ut1(jd=jd)
293    del t.ut1_fraction          # force re-computation of UT1
294    print(jd - t.ut1)
295    assert abs(jd - t.ut1) < 1e-10
296
297def test_indexing_time(ts):
298    t = ts.utc(1974, 10, range(1, 6))
299    assert t.shape == (5,)
300    t0 = t[0]
301    assert t.tai[0] == t0.tai
302    assert t.tt[0] == t0.tt
303    assert t.tdb[0] == t0.tdb
304    assert t.ut1[0] == t0.ut1
305    assert t.delta_t[0] == t0.delta_t
306
307def test_slicing_time(ts):
308    t = ts.utc(1974, 10, range(1, 6))
309    assert t.shape == (5,)
310    t24 = t[2:4]
311    assert t24.shape == (2,)
312    assert (t.tai[2:4] == t24.tai).all()
313    assert (t.tt[2:4] == t24.tt).all()
314    assert (t.tdb[2:4] == t24.tdb).all()
315    assert (t.ut1[2:4] == t24.ut1).all()
316    assert (t.delta_t[2:4] == t24.delta_t).all()
317
318def test_early_utc(ts_either):
319    ts = ts_either
320    t = ts.utc(1915, 12, 2, 3, 4, 5.6786786)
321    assert abs(t.tt - 2420833.6283317441) < epsilon
322    assert t.utc_iso() == '1915-12-02T03:04:06Z'
323
324def test_astimezone(ts):
325    t = ts.utc(1969, 7, 20, 20, 18)
326    tz = timezone('US/Eastern')
327    dt = t.astimezone(tz)
328    assert dt == tz.localize(datetime(1969, 7, 20, 16, 18, 0, 0))
329
330def test_astimezone_and_leap_second(ts):
331    t = ts.utc(1969, 7, 20, 20, 18)
332    tz = timezone('US/Eastern')
333    dt, leap_second = t.astimezone_and_leap_second(tz)
334    assert dt == tz.localize(datetime(1969, 7, 20, 16, 18, 0, 0))
335    assert leap_second == 0
336
337def test_toordinal(ts):
338    t = ts.utc(1973, 12, 31, 11, 59, 60)
339    assert t.toordinal() == 720623.5
340
341def test_utc_datetime(ts):
342    t = ts.utc(1969, 7, 20, 20, 18, 42.186479)
343    dt = t.utc_datetime()
344    assert dt == datetime(1969, 7, 20, 20, 18, 42, 186479, utc)
345
346def test_utc_datetime_and_leap_second(ts):
347    t = ts.utc(1969, 7, 20, 20, 18)
348    dt, leap_second = t.utc_datetime_and_leap_second()
349    assert dt == datetime(1969, 7, 20, 20, 18, 0, 0, utc)
350    assert leap_second == 0
351
352def test_utc_datetime_microseconds_round_trip(ts):
353    dt = datetime(2020, 5, 10, 11, 50, 9, 727799, tzinfo=utc)
354    t = ts.from_datetime(dt)
355    dt2, leap_second = t.utc_datetime_and_leap_second()
356    assert dt2 == dt
357    assert leap_second == 0
358
359def test_utc_datetime_agrees_with_public_utc_tuple(ts):
360    # https://github.com/skyfielders/python-skyfield/issues/542
361    # The %j day-of-year was advancing to the next day before strftime.
362    t = ts.utc(2021, 1, 1, 23, 59, 59.9999798834251798497)
363    assert t.utc[:5] == (2021, 1, 1, 23, 59)
364    assert t.utc_strftime("%j") == '001'
365
366def test_iso_of_decimal_that_rounds_up(ts):
367    t = ts.utc(1915, 12, 2, 3, 4, 5.6786786)
368    assert t.utc_iso(places=0) == '1915-12-02T03:04:06Z'
369    assert t.utc_iso(places=1) == '1915-12-02T03:04:05.7Z'
370    assert t.utc_iso(places=2) == '1915-12-02T03:04:05.68Z'
371    assert t.utc_iso(places=3) == '1915-12-02T03:04:05.679Z'
372    assert t.utc_iso(places=4) == '1915-12-02T03:04:05.6787Z'
373
374def test_iso_of_decimal_that_rounds_down(ts):
375    t = ts.utc(2014, 12, 21, 6, 3, 1.234234)
376    assert t.utc_iso(places=0) == '2014-12-21T06:03:01Z'
377    assert t.utc_iso(places=1) == '2014-12-21T06:03:01.2Z'
378    assert t.utc_iso(places=2) == '2014-12-21T06:03:01.23Z'
379    assert t.utc_iso(places=3) == '2014-12-21T06:03:01.234Z'
380    assert t.utc_iso(places=4) == '2014-12-21T06:03:01.2342Z'
381
382def test_iso_of_leap_second_with_fraction(ts):
383    t = ts.utc(1973, 12, 31, 23, 59, 60.12349)
384    assert t.utc_iso(places=0) == '1973-12-31T23:59:60Z'
385    assert t.utc_iso(places=1) == '1973-12-31T23:59:60.1Z'
386    assert t.utc_iso(places=2) == '1973-12-31T23:59:60.12Z'
387    assert t.utc_iso(places=3) == '1973-12-31T23:59:60.123Z'
388    assert t.utc_iso(places=4) == '1973-12-31T23:59:60.1235Z'
389
390def test_iso_of_array_showing_whole_seconds(ts):
391    t = ts.utc(1973, 12, 31, 23, 59, np.arange(58.75, 63.1, 0.5))
392    assert t.utc_iso(places=0) == [
393        '1973-12-31T23:59:59Z',
394        '1973-12-31T23:59:59Z',
395        '1973-12-31T23:59:60Z',
396        '1973-12-31T23:59:60Z',
397        '1974-01-01T00:00:00Z',
398        '1974-01-01T00:00:00Z',
399        '1974-01-01T00:00:01Z',
400        '1974-01-01T00:00:01Z',
401        '1974-01-01T00:00:02Z',
402        ]
403
404def test_iso_of_array_showing_fractions(ts):
405    t = ts.utc(1973, 12, 31, 23, 59, np.arange(58.75, 63.1, 0.5))
406    assert t.utc_iso(places=2) == [
407        '1973-12-31T23:59:58.75Z',
408        '1973-12-31T23:59:59.25Z',
409        '1973-12-31T23:59:59.75Z',
410        '1973-12-31T23:59:60.25Z',
411        '1973-12-31T23:59:60.75Z',
412        '1974-01-01T00:00:00.25Z',
413        '1974-01-01T00:00:00.75Z',
414        '1974-01-01T00:00:01.25Z',
415        '1974-01-01T00:00:01.75Z',
416    ]
417
418def test_jpl_format(ts):
419    t = ts.utc(range(-300, 301, 100), 7, 1)
420    assert t.utc_jpl() == [
421        'B.C. 0301-Jul-01 00:00:00.0000 UTC',
422        'B.C. 0201-Jul-01 00:00:00.0000 UTC',
423        'B.C. 0101-Jul-01 00:00:00.0000 UTC',
424        'B.C. 0001-Jul-01 00:00:00.0000 UTC',
425        'A.D. 0100-Jul-01 00:00:00.0000 UTC',
426        'A.D. 0200-Jul-01 00:00:00.0000 UTC',
427        'A.D. 0300-Jul-01 00:00:00.0000 UTC',
428        ]
429
430def test_strftime_of_a_leap_second(ts):
431    t = ts.utc(1973, 12, 31, 23, 59, 60)
432    assert t.utc_strftime('%Y %m %d %H %M %S') == '1973 12 31 23 59 60'
433
434def test_strftime_of_date_array_over_a_leap_second(ts):
435    t = ts.utc(1973, 12, 31, 23, 59, np.arange(59.0, 61.1, 1.0))
436    assert t.utc_strftime('%a %Y %m %d %H %M %S') == [
437        'Mon 1973 12 31 23 59 59',
438        'Mon 1973 12 31 23 59 60',
439        'Tue 1974 01 01 00 00 00',
440    ]
441
442def test_strftime_day_of_year(ts):
443    # Based on example date at https://strftime.org/
444    assert ts.utc(2013, 9, 29).utc_strftime('%j') == '272'
445    assert ts.utc(2013, 9, 30).utc_strftime('%j') == '273'
446    assert ts.utc(2013, 9, 30, 23, 59).utc_strftime('%j') == '273'
447    assert ts.utc(2013, 9, 30, 23, 60).utc_strftime('%j') == '274'
448
449    assert ts.utc(2013, 9, [29, 30]).utc_strftime('%j') == ['272', '273']
450
451def test_leap_second(ts):
452
453    # During 1973 the offset between UTC and TAI was 12.0 seconds, so
454    # TAI should reach the first moment of 1974 while the UTC clock is
455    # still reading 12s before midnight (60 - 12 = 48).  Happily, the
456    # fraction 0.5 can be precisely represented in floating point, so we
457    # can use a bare `==` in this assert:
458
459    t0 = ts.utc(1973, 12, 31, 23, 59, 48.0).tai
460    assert t0 == 2442048.5
461
462    # Here are some more interesting values:
463
464    t1 = ts.utc(1973, 12, 31, 23, 59, 58.0).tai
465    t2 = ts.utc(1973, 12, 31, 23, 59, 59.0).tai
466    t3 = ts.utc(1973, 12, 31, 23, 59, 60.0).tai
467    t4 = ts.utc(1974, 1, 1, 0, 0, 0.0).tai
468    t5 = ts.utc(1974, 1, 1, 0, 0, 1.0).tai
469
470    # The step from 23:59:59 to 0:00:00 is here a two-second step,
471    # because of the leap second 23:59:60 that falls in between:
472
473    assert abs(t4 - t2 - 2.0 * one_second) < epsilon
474
475    # Otherwise, the five dates given above are all one second apart:
476
477    assert abs(t2 - t1 - one_second) < epsilon
478    assert abs(t3 - t2 - one_second) < epsilon
479    assert abs(t4 - t3 - one_second) < epsilon
480    assert abs(t5 - t4 - one_second) < epsilon
481
482    # And all these dates can be converted back to UTC.
483
484    assert ts.tai(jd=t0).utc_iso() == '1973-12-31T23:59:48Z'
485    assert ts.tai(jd=t1).utc_iso() == '1973-12-31T23:59:58Z'
486    assert ts.tai(jd=t2).utc_iso() == '1973-12-31T23:59:59Z'
487    assert ts.tai(jd=t3).utc_iso() == '1973-12-31T23:59:60Z'
488    assert ts.tai(jd=t4).utc_iso() == '1974-01-01T00:00:00Z'
489    assert ts.tai(jd=t5).utc_iso() == '1974-01-01T00:00:01Z'
490
491def test_leap_second_sensitivity(ts):
492    t = ts.utc(2017, 1, 1, 0, 0, 0)
493    a, b = t.whole, t.tai_fraction
494
495    # First, make sure the UTC time round-trips.
496    t2 = ts.tai_jd(a, b)
497    tup = t2._utc_tuple(0.0)
498    assert tup == (2017, 1, 1, 0, 0, 0.0)
499
500    # Second, bump back infinitesimally into the previous leap second
501    # and make sure the UTC time is just shy of second 61.0.
502    b = np.nextafter(b, -1)
503    t3 = ts.tai_jd(a, b)
504    tup = t3._utc_tuple(0.0)
505    assert tup[:5] == (2016, 12, 31, 23, 59)
506    assert tup[5] > 60.99999999999
507
508def test_delta_t(ts):
509    # The IERS "finals2000A.all" for 2000 Jan 1 gives DUT1 = 0.3554779,
510    # and 0.3554779 - 0.184 - 1.0 = -0.8285221.
511    t = ts.utc(2000, 1, 1, 0, 0, 0)
512    assert t.delta_t == 63.8285221
513
514    # Check historic value. Compare to the table in Morrison and
515    # Stephenson 2004, the tolerance is 2 sigma
516    t = ts.utc(year=1000)
517    assert abs(t.delta_t - 1570.0) < 110.0
518
519    # Check far-future value against the long-term parabola formula.
520    centuries = 20
521    t = ts.J(1825 + centuries * 100)
522    assert t.delta_t == -320 + 32.5 * centuries**2
523
524def test_dut1(ts):
525    # Roughly agreeing with tables on NIST website
526    t = ts.utc(2017, 3, 30)
527    assert str(t.dut1)[:3] == '0.4'
528
529    t = ts.utc(2018, 9, 21)
530    assert str(t.dut1)[:3] == '0.0'
531
532    t = ts.utc(2019, 5, 2)
533    assert str(t.dut1)[:4] == '-0.1'  # table says -0.2
534
535def test_polar_motion_table():
536    with api.load.open('finals2000A.all') as f:
537        finals_data = iers.parse_x_y_dut1_from_finals_all(f)
538
539    ts = api.load.timescale()
540    iers.install_polar_motion_table(ts, finals_data)
541
542    bump = 1e-4  # TODO: can this be improved?
543
544    t = ts.utc(1973, 1, 3, 0, 0, -bump)
545    sprime, x, y = t.polar_motion_angles()
546    assert x > 0.118980
547    assert y > 0.135656
548
549    t = ts.utc(1973, 1, 3, 0, 0, +bump)
550    sprime, x, y = t.polar_motion_angles()
551    assert x < 0.118980
552    assert y < 0.135656
553
554def test_J(ts):
555    assert ts.J(2000).tt == T0
556    assert ts.J(1900).tt == T0 - 36525.0
557    assert (ts.J([1900, 2000]).tt == [T0 - 36525.0, T0]).all()
558    assert ts.tt(2000, 1, 1.5).J == 2000.0
559    assert ts.tt(1900, 1, 0.5).J == 1900.0
560
561def test_time_repr(ts):
562
563    # Check that repr return is a str (this is required on Python 2,
564    # unicode is not allowed)
565    assert isinstance(repr(ts.utc(year=2000)), str)
566
567    # Check array conversion
568    assert isinstance(repr(ts.utc(year=range(2000, 2010))), str)
569
570    assert repr(ts.tt_jd(1)) == '<Time tt=1.0>'
571    assert repr(ts.tt_jd([])) == '<Time tt=[]>'
572    assert repr(ts.tt_jd([1])) == '<Time tt=[1.]>'
573    assert repr(ts.tt_jd([1, 2])) == '<Time tt=[1. 2.]>'
574    assert repr(ts.tt_jd([1, 2, 3])) == '<Time tt=[1. 2. 3.]>'
575    assert repr(ts.tt_jd([1, 2, 3, 4])) == '<Time tt=[1.0 ... 4.0] len=4>'
576
577def test_jd_calendar():
578    # Check a specific instance (using UNIX epoch here, though that's
579    # an arbitrary choice)
580    jd_unix = 2440587.5
581    cal_unix = (1970, 1, 1, 0, 0, 0.0)
582
583    cal = calendar_tuple(jd_unix)
584
585    # Check that the returned value is correct
586    assert cal == cal_unix
587
588    # Check that all the return types are correct
589    assert isinstance(cal[0], numbers.Integral)  # Year
590    assert isinstance(cal[1], numbers.Integral)  # Month
591    assert isinstance(cal[2], numbers.Integral)  # Day
592    assert isinstance(cal[3], numbers.Integral)  # Hour
593    assert isinstance(cal[4], numbers.Integral)  # Minute
594    assert isinstance(cal[5], numbers.Real)  # Second
595
596    # Check backward conversion
597    assert julian_date(*cal) == jd_unix
598
599    # Check array conversion components
600    jd_array = jd_unix + np.arange(5.0)
601    cal_array = calendar_tuple(jd_array)
602
603    assert (cal_array[0] == 1970).all()
604    assert (cal_array[1] == 1).all()
605    assert (cal_array[2] == np.arange(1, 6)).all()
606    assert (cal_array[3] == 0).all()
607    assert (cal_array[4] == 0).all()
608    assert (cal_array[5] == 0.0).all()
609
610    # Check reversal of array
611    assert (julian_date(*cal_array) == jd_array).all()
612
613def test_raw_julian_gregorian_cutover():
614    gregory = 2299161
615    assert compute_calendar_date(gregory - 2, gregory) == (1582, 10, 3)
616    assert compute_calendar_date(gregory - 1, gregory) == (1582, 10, 4)
617    assert compute_calendar_date(gregory + 0, gregory) == (1582, 10, 15)
618    assert compute_calendar_date(gregory + 1, gregory) == (1582, 10, 16)
619
620    jd = np.arange(gregory - 2, gregory + 2)
621    assert [list(a) for a in compute_calendar_date(jd, gregory)] == [
622        [1582, 1582, 1582, 1582],
623        [10, 10, 10, 10],
624        [3, 4, 15, 16],
625    ]
626
627    assert julian_day(1582, 10, 3, gregory) == (gregory - 2)
628    assert julian_day(1582, 10, 4, gregory) == (gregory - 1)
629    assert julian_day(1582, 10, 15, gregory) == (gregory + 0)
630    assert julian_day(1582, 10, 16, gregory) == (gregory + 1)
631
632    days = [3, 4, 15, 16]
633    assert list(julian_day(1582, 10, np.array(days), gregory)) == [
634        2299159, 2299160, 2299161, 2299162,
635    ]
636
637def test_constructor_julian_gregorian_cutover(time_scale_name):
638    if sys.version_info <= (3,):
639        return  # Python 2 time.strftime() complains about the year 1582
640
641    def jd(y, m, d):
642        t = getattr(ts, time_scale_name)(y, m, d)
643        if time_scale_name == 'utc':
644            return sum(t._utc_seconds(0.0)) / DAY_S
645        return getattr(t, time_scale_name)
646
647    ts = api.load.timescale()
648
649    assert jd(1582, 10, 4) == 2299149.5
650    assert jd(1582, 10, 15) == 2299160.5
651    assert jd(1752, 9, 2) == 2361209.5
652    assert jd(1752, 9, 14) == 2361221.5
653
654    ts.julian_calendar_cutoff = GREGORIAN_START
655
656    assert jd(1582, 10, 4) == 2299159.5
657    assert jd(1582, 10, 15) == 2299160.5
658    assert jd(1752, 9, 2) == 2361209.5
659    assert jd(1752, 9, 14) == 2361221.5
660
661    ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND
662
663    assert jd(1582, 10, 4) == 2299159.5
664    assert jd(1582, 10, 15) == 2299170.5
665    assert jd(1752, 9, 2) == 2361220.5
666    assert jd(1752, 9, 14) == 2361221.5
667
668def test_calendar_tuple_julian_gregorian_cutover(time_scale_name):
669    if sys.version_info <= (3,):
670        return  # Python 2 time.strftime() complains about the year 1582
671
672    def ymd(jd):
673        t = ts.tt_jd(jd, 0.1)
674        if time_scale_name == 'utc':
675            return t.utc[:3]
676        return getattr(t, time_scale_name + '_calendar')()[:3]
677
678    ts = api.load.timescale()
679
680    assert ymd(2299149.5) == (1582, 10, 4)
681    assert ymd(2299160.5) == (1582, 10, 15)
682    assert ymd(2361209.5) == (1752, 9, 2)
683    assert ymd(2361221.5) == (1752, 9, 14)
684
685    ts.julian_calendar_cutoff = GREGORIAN_START
686
687    assert ymd(2299159.5) == (1582, 10, 4)
688    assert ymd(2299160.5) == (1582, 10, 15)
689    assert ymd(2361209.5) == (1752, 9, 2)
690    assert ymd(2361221.5) == (1752, 9, 14)
691
692    ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND
693
694    assert ymd(2299159.5) == (1582, 10, 4)
695    assert ymd(2299170.5) == (1582, 10, 15)
696    assert ymd(2361220.5) == (1752, 9, 2)
697    assert ymd(2361221.5) == (1752, 9, 14)
698
699def test_strftime_julian_gregorian_cutover(time_scale_name):
700    if sys.version_info <= (3,):
701        return  # Python 2 time.strftime() complains about the year 1582
702
703    def ymd(jd):
704        t = ts.tt_jd(jd, 0.1)
705        return getattr(t, time_scale_name + '_strftime')('%Y %m %d')
706
707    ts = api.load.timescale()
708
709    assert ymd(2299149.5) == '1582 10 04'
710    assert ymd(2299160.5) == '1582 10 15'
711    assert ymd(2361209.5) == '1752 09 02'
712    assert ymd(2361221.5) == '1752 09 14'
713
714    ts.julian_calendar_cutoff = GREGORIAN_START
715
716    assert ymd(2299159.5) == '1582 10 04'
717    assert ymd(2299160.5) == '1582 10 15'
718    assert ymd(2361209.5) == '1752 09 02'
719    assert ymd(2361221.5) == '1752 09 14'
720
721    ts.julian_calendar_cutoff = GREGORIAN_START_ENGLAND
722
723    assert ymd(2299159.5) == '1582 10 04'
724    assert ymd(2299170.5) == '1582 10 15'
725    assert ymd(2361220.5) == '1752 09 02'
726    assert ymd(2361221.5) == '1752 09 14'
727
728def test_time_equality(ts):
729    t0 = ts.tt_jd(2459008.5, 0.125)
730    t1 = ts.tt_jd(2459008.0, 0.625)
731    assert t0 == t1
732    assert t1 - t0 == 0.0
733    assert hash(t0) == hash(t1)
734
735    t2 = ts.tt_jd(2459008.0, 0.6251)
736    assert t2 != t0
737    assert t2 - t0 > 0
738    assert hash(t0) != hash(t2)
739
740def test_time_math(ts):
741    t = ts.tt_jd(2459008.5, 0.125)
742
743    assert (t - 1).tt_strftime() == '2020-06-07 03:00:00 TT'
744    assert (t + 1).tt_strftime() == '2020-06-09 03:00:00 TT'
745
746    assert (t - 1.25).tt_strftime() == '2020-06-06 21:00:00 TT'
747    assert (t + 1.25).tt_strftime() == '2020-06-09 09:00:00 TT'
748
749    bump = dt_module.timedelta(days=1, seconds=1)
750    assert (t - bump).tt_strftime() == '2020-06-07 02:59:59 TT'
751    assert (t + bump).tt_strftime() == '2020-06-09 03:00:01 TT'
752
753    bump = dt_module.timedelta(microseconds=300)
754    assert (t - bump).utc_jpl() == 'A.D. 2020-Jun-08 02:58:50.8157 UTC'
755    assert (t + bump).utc_jpl() == 'A.D. 2020-Jun-08 02:58:50.8163 UTC'
756