# Licensed under a 3-clause BSD style license - see LICENSE.rst import functools import pytest import numpy as np from astropy.time import Time, TimeDelta from astropy import units as u from astropy.table import Column allclose_sec = functools.partial(np.allclose, rtol=2. ** -52, atol=2. ** -52 * 24 * 3600) # 20 ps atol class TestTimeQuantity: """Test Interaction of Time with Quantities""" def test_valid_quantity_input(self): """Test Time formats that are allowed to take quantity input.""" q = 2450000.125 * u.day t1 = Time(q, format='jd', scale='utc') assert t1.value == q.value q2 = q.to(u.second) t2 = Time(q2, format='jd', scale='utc') assert t2.value == q.value == q2.to_value(u.day) q3 = q - 2400000.5 * u.day t3 = Time(q3, format='mjd', scale='utc') assert t3.value == q3.value # test we can deal with two quantity arguments, with different units qs = 24. * 36. * u.second t4 = Time(q3, qs, format='mjd', scale='utc') assert t4.value == (q3 + qs).to_value(u.day) qy = 1990. * u.yr ty1 = Time(qy, format='jyear', scale='utc') assert ty1.value == qy.value ty2 = Time(qy.to(u.day), format='jyear', scale='utc') assert ty2.value == qy.value qy2 = 10. * u.yr tcxc = Time(qy2, format='cxcsec') assert tcxc.value == qy2.to_value(u.second) tgps = Time(qy2, format='gps') assert tgps.value == qy2.to_value(u.second) tunix = Time(qy2, format='unix') assert tunix.value == qy2.to_value(u.second) qd = 2000. * 365. * u.day tplt = Time(qd, format='plot_date', scale='utc') assert tplt.value == qd.value def test_invalid_quantity_input(self): with pytest.raises(u.UnitsError): Time(2450000. * u.m, format='jd', scale='utc') with pytest.raises(u.UnitsError): Time(2450000. * u.dimensionless_unscaled, format='jd', scale='utc') def test_column_with_and_without_units(self): """Ensure a Column without a unit is treated as an array [#3648]""" a = np.arange(50000., 50010.) ta = Time(a, format='mjd') c1 = Column(np.arange(50000., 50010.), name='mjd') tc1 = Time(c1, format='mjd') assert np.all(ta == tc1) c2 = Column(np.arange(50000., 50010.), name='mjd', unit='day') tc2 = Time(c2, format='mjd') assert np.all(ta == tc2) c3 = Column(np.arange(50000., 50010.), name='mjd', unit='m') with pytest.raises(u.UnitsError): Time(c3, format='mjd') def test_no_quantity_input_allowed(self): """Time formats that are not allowed to take Quantity input.""" qy = 1990. * u.yr for fmt in ('iso', 'yday', 'datetime', 'byear', 'byear_str', 'jyear_str'): with pytest.raises(ValueError): Time(qy, format=fmt, scale='utc') def test_valid_quantity_operations(self): """Check that adding a time-valued quantity to a Time gives a Time""" t0 = Time(100000., format='cxcsec') q1 = 10. * u.second t1 = t0 + q1 assert isinstance(t1, Time) assert t1.value == t0.value + q1.to_value(u.second) q2 = 1. * u.day t2 = t0 - q2 assert allclose_sec(t2.value, t0.value - q2.to_value(u.second)) # check broadcasting q3 = np.arange(15.).reshape(3, 5) * u.hour t3 = t0 - q3 assert t3.shape == q3.shape assert allclose_sec(t3.value, t0.value - q3.to_value(u.second)) def test_invalid_quantity_operations(self): """Check that comparisons of Time with quantities does not work (even for time-like, since we cannot compare Time to TimeDelta)""" with pytest.raises(TypeError): Time(100000., format='cxcsec') > 10. * u.m with pytest.raises(TypeError): Time(100000., format='cxcsec') > 10. * u.second class TestTimeDeltaQuantity: """Test interaction of TimeDelta with Quantities""" def test_valid_quantity_input(self): """Test that TimeDelta can take quantity input.""" q = 500.25 * u.day dt1 = TimeDelta(q, format='jd') assert dt1.value == q.value dt2 = TimeDelta(q, format='sec') assert dt2.value == q.to_value(u.second) dt3 = TimeDelta(q) assert dt3.value == q.value def test_invalid_quantity_input(self): with pytest.raises(u.UnitsError): TimeDelta(2450000. * u.m, format='jd') with pytest.raises(u.UnitsError): Time(2450000. * u.dimensionless_unscaled, format='jd', scale='utc') with pytest.raises(TypeError): TimeDelta(100, format='sec') > 10. * u.m def test_quantity_output(self): q = 500.25 * u.day dt = TimeDelta(q) assert dt.to(u.day) == q assert dt.to_value(u.day) == q.value assert dt.to_value('day') == q.value assert dt.to(u.second).value == q.to_value(u.second) assert dt.to_value(u.second) == q.to_value(u.second) assert dt.to_value('s') == q.to_value(u.second) # Following goes through "format", but should be the same. assert dt.to_value('sec') == q.to_value(u.second) def test_quantity_output_errors(self): dt = TimeDelta(250., format='sec') with pytest.raises(u.UnitsError): dt.to(u.m) with pytest.raises(u.UnitsError): dt.to_value(u.m) with pytest.raises(u.UnitsError): dt.to_value(unit=u.m) with pytest.raises(ValueError, match=("not one of the known formats.*" "failed to parse as a unit")): dt.to_value('parrot') with pytest.raises(TypeError): dt.to_value('sec', unit=u.s) with pytest.raises(TypeError): # TODO: would be nice to make this work! dt.to_value(u.s, subfmt='str') def test_valid_quantity_operations1(self): """Check adding/substracting/comparing a time-valued quantity works with a TimeDelta. Addition/subtraction should give TimeDelta""" t0 = TimeDelta(106400., format='sec') q1 = 10. * u.second t1 = t0 + q1 assert isinstance(t1, TimeDelta) assert t1.value == t0.value + q1.to_value(u.second) q2 = 1. * u.day t2 = t0 - q2 assert isinstance(t2, TimeDelta) assert allclose_sec(t2.value, t0.value - q2.to_value(u.second)) # now comparisons assert t0 > q1 assert t0 < 1. * u.yr # and broadcasting q3 = np.arange(12.).reshape(4, 3) * u.hour t3 = t0 + q3 assert isinstance(t3, TimeDelta) assert t3.shape == q3.shape assert allclose_sec(t3.value, t0.value + q3.to_value(u.second)) def test_valid_quantity_operations2(self): """Check that TimeDelta is treated as a quantity where possible.""" t0 = TimeDelta(100000., format='sec') f = 1. / t0 assert isinstance(f, u.Quantity) assert f.unit == 1. / u.day g = 10. * u.m / u.second**2 v = t0 * g assert isinstance(v, u.Quantity) assert u.allclose(v, t0.sec * g.value * u.m / u.second) q = np.log10(t0 / u.second) assert isinstance(q, u.Quantity) assert q.value == np.log10(t0.sec) s = 1. * u.m v = s / t0 assert isinstance(v, u.Quantity) assert u.allclose(v, 1. / t0.sec * u.m / u.s) t = 1. * u.s t2 = t0 * t assert isinstance(t2, u.Quantity) assert u.allclose(t2, t0.sec * u.s ** 2) t3 = [1] / t0 assert isinstance(t3, u.Quantity) assert u.allclose(t3, 1 / (t0.sec * u.s)) # broadcasting t1 = TimeDelta(np.arange(100000., 100012.).reshape(6, 2), format='sec') f = np.array([1., 2.]) * u.cycle * u.Hz phase = f * t1 assert isinstance(phase, u.Quantity) assert phase.shape == t1.shape assert u.allclose(phase, t1.sec * f.value * u.cycle) q = t0 * t1 assert isinstance(q, u.Quantity) assert np.all(q == t0.to(u.day) * t1.to(u.day)) q = t1 / t0 assert isinstance(q, u.Quantity) assert np.all(q == t1.to(u.day) / t0.to(u.day)) def test_valid_quantity_operations3(self): """Test a TimeDelta remains one if possible.""" t0 = TimeDelta(10., format='jd') q = 10. * u.one t1 = q * t0 assert isinstance(t1, TimeDelta) assert t1 == TimeDelta(100., format='jd') t2 = t0 * q assert isinstance(t2, TimeDelta) assert t2 == TimeDelta(100., format='jd') t3 = t0 / q assert isinstance(t3, TimeDelta) assert t3 == TimeDelta(1., format='jd') q2 = 1. * u.percent t4 = t0 * q2 assert isinstance(t4, TimeDelta) assert abs(t4 - TimeDelta(0.1, format='jd')) < 1. * u.ns q3 = 1. * u.hr / (36. * u.s) t5 = q3 * t0 assert isinstance(t4, TimeDelta) assert abs(t5 - TimeDelta(1000., format='jd')) < 1. * u.ns # Test multiplication with a unit. t6 = t0 * u.one assert isinstance(t6, TimeDelta) assert t6 == TimeDelta(10., format='jd') t7 = u.one * t0 assert isinstance(t7, TimeDelta) assert t7 == TimeDelta(10., format='jd') t8 = t0 * '' assert isinstance(t8, TimeDelta) assert t8 == TimeDelta(10., format='jd') t9 = '' * t0 assert isinstance(t9, TimeDelta) assert t9 == TimeDelta(10., format='jd') t10 = t0 / u.one assert isinstance(t10, TimeDelta) assert t6 == TimeDelta(10., format='jd') t11 = t0 / '' assert isinstance(t11, TimeDelta) assert t11 == TimeDelta(10., format='jd') t12 = t0 / [1] assert isinstance(t12, TimeDelta) assert t12 == TimeDelta(10., format='jd') t13 = [1] * t0 assert isinstance(t13, TimeDelta) assert t13 == TimeDelta(10., format='jd') def test_invalid_quantity_operations(self): """Check comparisons of TimeDelta with non-time quantities fails.""" with pytest.raises(TypeError): TimeDelta(100000., format='sec') > 10. * u.m def test_invalid_quantity_operations2(self): """Check that operations with non-time/quantity fail.""" td = TimeDelta(100000., format='sec') with pytest.raises(TypeError): td * object() with pytest.raises(TypeError): td / object() def test_invalid_quantity_broadcast(self): """Check broadcasting rules in interactions with Quantity.""" t0 = TimeDelta(np.arange(12.).reshape(4, 3), format='sec') with pytest.raises(ValueError): t0 + np.arange(4.) * u.s class TestDeltaAttributes: def test_delta_ut1_utc(self): t = Time('2010-01-01 00:00:00', format='iso', scale='utc', precision=6) t.delta_ut1_utc = 0.3 * u.s assert t.ut1.iso == '2010-01-01 00:00:00.300000' t.delta_ut1_utc = 0.4 / 60. * u.minute assert t.ut1.iso == '2010-01-01 00:00:00.400000' with pytest.raises(u.UnitsError): t.delta_ut1_utc = 0.4 * u.m # Also check that a TimeDelta works. t.delta_ut1_utc = TimeDelta(0.3, format='sec') assert t.ut1.iso == '2010-01-01 00:00:00.300000' t.delta_ut1_utc = TimeDelta(0.5 / 24. / 3600., format='jd') assert t.ut1.iso == '2010-01-01 00:00:00.500000' def test_delta_tdb_tt(self): t = Time('2010-01-01 00:00:00', format='iso', scale='tt', precision=6) t.delta_tdb_tt = 20. * u.second assert t.tdb.iso == '2010-01-01 00:00:20.000000' t.delta_tdb_tt = 30. / 60. * u.minute assert t.tdb.iso == '2010-01-01 00:00:30.000000' with pytest.raises(u.UnitsError): t.delta_tdb_tt = 0.4 * u.m # Also check that a TimeDelta works. t.delta_tdb_tt = TimeDelta(40., format='sec') assert t.tdb.iso == '2010-01-01 00:00:40.000000' t.delta_tdb_tt = TimeDelta(50. / 24. / 3600., format='jd') assert t.tdb.iso == '2010-01-01 00:00:50.000000' @pytest.mark.parametrize('q1, q2', ((5e8 * u.s, None), (5e17 * u.ns, None), (4e8 * u.s, 1e17 * u.ns), (4e14 * u.us, 1e17 * u.ns))) def test_quantity_conversion_rounding(q1, q2): """Check that no rounding errors are incurred by unit conversion. This occurred before as quantities in seconds were converted to days before trying to split them into two-part doubles. See gh-7622. """ t = Time('2001-01-01T00:00:00.', scale='tai') expected = Time('2016-11-05T00:53:20.', scale='tai') if q2 is None: t0 = t + q1 else: t0 = t + q1 + q2 assert abs(t0 - expected) < 20 * u.ps dt1 = TimeDelta(q1, q2) t1 = t + dt1 assert abs(t1 - expected) < 20 * u.ps dt2 = TimeDelta(q1, q2, format='sec') t2 = t + dt2 assert abs(t2 - expected) < 20 * u.ps