Skip to content

Commit

Permalink
Improve Python’s datetime ValueError messages
Browse files Browse the repository at this point in the history
Fixes #957 and #992.
  • Loading branch information
brandon-rhodes committed Dec 2, 2024
1 parent 28b5fb1 commit 42377bf
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 3 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ Changelog
Released versions
-----------------

v1.50 — Not yet released
------------------------

* The time methods :meth:`~skyfield.timelib.Time.utc_datetime()` and
:meth:`~skyfield.timelib.Time.utc_datetime_and_leap_second()` now
intercept the ``ValueError`` that Python raises for a negative year or
a Julian-only leap day, and replace the generic error message with a
more specific one.
`#957 <https://github.com/skyfielders/python-skyfield/issues/957>`_
`#992 <https://github.com/skyfielders/python-skyfield/issues/992>`_

v1.49 — 2024 June 13
--------------------

Expand Down
28 changes: 28 additions & 0 deletions skyfield/tests/test_timelib.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ def test_utc_datetime_and_leap_second(ts):
assert dt == datetime(1969, 7, 20, 20, 18, 0, 0, utc)
assert leap_second == 0

t = ts.utc(1969, 7, 20, 20, [18, 19])
dt, leap_second = t.utc_datetime_and_leap_second()
assert list(dt) == [datetime(1969, 7, 20, 20, 18, 0, 0, utc),
datetime(1969, 7, 20, 20, 19, 0, 0, utc)]
assert list(leap_second) == [0,0]

def test_utc_datetime_microseconds_round_trip(ts):
dt = datetime(2020, 5, 10, 11, 50, 9, 727799, tzinfo=utc)
t = ts.from_datetime(dt)
Expand All @@ -390,6 +396,28 @@ def test_utc_datetime_agrees_with_public_utc_tuple(ts):
assert t.utc[:5] == (2021, 1, 1, 23, 59)
assert t.utc_strftime("%j") == '001'

def test_utc_datetime_exception_for_negative_year():
ts = api.load.timescale()
ts.julian_calendar_cutoff = GREGORIAN_START
t = ts.utc(-1, 1, 1)
with assert_raises(ValueError, 'negative years like the year -1'):
t.utc_datetime()

t = ts.utc([-2, -3, -1], 1, 1)
with assert_raises(ValueError, 'negative years like the year -2'):
t.utc_datetime()

def test_utc_datetime_exception_for_julian_leap_day():
ts = api.load.timescale()
ts.julian_calendar_cutoff = GREGORIAN_START
t = ts.utc(700, 2, 29)
with assert_raises(ValueError, 'Julian leap days like 700 February 29'):
t.utc_datetime()

t = ts.utc(700, 2, [28, 29, 30])
with assert_raises(ValueError, 'Julian leap days like 700 February 29'):
t.utc_datetime()

def test_iso_of_decimal_that_rounds_up(ts):
t = ts.utc(1915, 12, 2, 3, 4, 5.6786786)
assert t.utc_iso(places=0) == '1915-12-02T03:04:06Z'
Expand Down
29 changes: 26 additions & 3 deletions skyfield/timelib.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,10 +554,23 @@ def utc_datetime_and_leap_second(self):
if self.shape:
zone = [utc] * self.shape[0]
argsets = zip(year, month, day, hour, minute, second, micro, zone)
dt = array([datetime(*args) for args in argsets])
d = []
a = d.append
try: # placed outside the loop for efficiency
for args in argsets:
a(datetime(*args))
except ValueError as e:
_upgrade_datetime_exception(args, e)
raise
d = array(d)
else:
dt = datetime(year, month, day, hour, minute, second, micro, utc)
return dt, leap_second
args = year, month, day, hour, minute, second, micro, utc
try:
d = datetime(*args)
except ValueError as e:
_upgrade_datetime_exception(args, e)
raise
return d, leap_second

def utc_iso(self, delimiter='T', places=0):
"""Convert to an ISO 8601 string like ``2014-01-18T01:35:38Z`` in UTC.
Expand Down Expand Up @@ -1245,6 +1258,16 @@ def _strftime(format, year, month, day, hour, minute, second,
return [strftime(format, item) for item in zip(*tup)]
return strftime(format, tup)

def _upgrade_datetime_exception(args, e):
year, month, day, hour, minute, second, micro, zone = args
if year < 0:
e.args = ("Python's datetime does not support negative"
' years like the year {}'.format(year),)
elif month == 2 and day == 29:
e.args = ("Python's datetime does not support Julian leap"
' days like {} February 29 that are missing from'
' the Gregorian calendar'.format(year),)

_naive_complaint = """cannot interpret a datetime that lacks a timezone
You must either specify that your datetime is in UTC:
Expand Down

0 comments on commit 42377bf

Please sign in to comment.