Skip to content

Commit 24e7a06

Browse files
committed
Add support for midnight at the end of day
Fix arrow-py#703
1 parent f063b5f commit 24e7a06

File tree

2 files changed

+96
-1
lines changed

2 files changed

+96
-1
lines changed

arrow/parser.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,21 @@ def _build_datetime(parts):
427427
elif am_pm == "am" and hour == 12:
428428
hour = 0
429429

430+
# Support for midnight at the end of day
431+
if hour == 24:
432+
if parts.get("minute", 0) != 0:
433+
raise ParserError("Midnight at the end of day must not contain minutes")
434+
if parts.get("second", 0) != 0:
435+
raise ParserError("Midnight at the end of day must not contain seconds")
436+
if parts.get("microsecond", 0) != 0:
437+
raise ParserError(
438+
"Midnight at the end of day must not contain microseconds"
439+
)
440+
hour = 0
441+
day_increment = 1
442+
else:
443+
day_increment = 0
444+
430445
# account for rounding up to 1000000
431446
microsecond = parts.get("microsecond", 0)
432447
if microsecond == 1000000:
@@ -435,7 +450,7 @@ def _build_datetime(parts):
435450
else:
436451
second_increment = 0
437452

438-
increment = timedelta(seconds=second_increment)
453+
increment = timedelta(days=day_increment, seconds=second_increment)
439454

440455
return (
441456
datetime(

tests/parser_tests.py

+80
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,48 @@ def test_parse_DDDD_only(self):
586586
with self.assertRaises(ParserError):
587587
self.parser.parse("145", "DDDD")
588588

589+
def test_parse_HH_24(self):
590+
self.assertEqual(
591+
self.parser.parse("2019-10-30T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
592+
datetime(2019, 10, 31, 0, 0, 0, 0),
593+
)
594+
self.assertEqual(
595+
self.parser.parse("2019-10-30T24:00", "YYYY-MM-DDTHH:mm"),
596+
datetime(2019, 10, 31, 0, 0, 0, 0),
597+
)
598+
self.assertEqual(
599+
self.parser.parse("2019-10-30T24", "YYYY-MM-DDTHH"),
600+
datetime(2019, 10, 31, 0, 0, 0, 0),
601+
)
602+
self.assertEqual(
603+
self.parser.parse("2019-10-30T24:00:00.0", "YYYY-MM-DDTHH:mm:ss.S"),
604+
datetime(2019, 10, 31, 0, 0, 0, 0),
605+
)
606+
self.assertEqual(
607+
self.parser.parse("2019-10-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
608+
datetime(2019, 11, 1, 0, 0, 0, 0),
609+
)
610+
self.assertEqual(
611+
self.parser.parse("2019-12-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
612+
datetime(2020, 1, 1, 0, 0, 0, 0),
613+
)
614+
self.assertEqual(
615+
self.parser.parse("2019-12-31T23:59:59.9999999", "YYYY-MM-DDTHH:mm:ss.S"),
616+
datetime(2020, 1, 1, 0, 0, 0, 0),
617+
)
618+
619+
with self.assertRaises(ParserError):
620+
self.parser.parse("2019-12-31T24:01:00", "YYYY-MM-DDTHH:mm:ss")
621+
622+
with self.assertRaises(ParserError):
623+
self.parser.parse("2019-12-31T24:00:01", "YYYY-MM-DDTHH:mm:ss")
624+
625+
with self.assertRaises(ParserError):
626+
self.parser.parse("2019-12-31T24:00:00.1", "YYYY-MM-DDTHH:mm:ss.S")
627+
628+
with self.assertRaises(ParserError):
629+
self.parser.parse("2019-12-31T24:00:00.999999", "YYYY-MM-DDTHH:mm:ss.S")
630+
589631

590632
class DateTimeParserRegexTests(Chai):
591633
def setUp(self):
@@ -1176,6 +1218,44 @@ def test_iso8601_basic_format(self):
11761218
with self.assertRaises(ParserError):
11771219
self.parser.parse_iso("20180517T1055213Z")
11781220

1221+
def test_midnight_end_day(self):
1222+
self.assertEqual(
1223+
self.parser.parse_iso("2019-10-30T24:00:00"),
1224+
datetime(2019, 10, 31, 0, 0, 0, 0),
1225+
)
1226+
self.assertEqual(
1227+
self.parser.parse_iso("2019-10-30T24:00"),
1228+
datetime(2019, 10, 31, 0, 0, 0, 0),
1229+
)
1230+
self.assertEqual(
1231+
self.parser.parse_iso("2019-10-30T24:00:00.0"),
1232+
datetime(2019, 10, 31, 0, 0, 0, 0),
1233+
)
1234+
self.assertEqual(
1235+
self.parser.parse_iso("2019-10-31T24:00:00"),
1236+
datetime(2019, 11, 1, 0, 0, 0, 0),
1237+
)
1238+
self.assertEqual(
1239+
self.parser.parse_iso("2019-12-31T24:00:00"),
1240+
datetime(2020, 1, 1, 0, 0, 0, 0),
1241+
)
1242+
self.assertEqual(
1243+
self.parser.parse_iso("2019-12-31T23:59:59.9999999"),
1244+
datetime(2020, 1, 1, 0, 0, 0, 0),
1245+
)
1246+
1247+
with self.assertRaises(ParserError):
1248+
self.parser.parse_iso("2019-12-31T24:01:00")
1249+
1250+
with self.assertRaises(ParserError):
1251+
self.parser.parse_iso("2019-12-31T24:00:01")
1252+
1253+
with self.assertRaises(ParserError):
1254+
self.parser.parse_iso("2019-12-31T24:00:00.1")
1255+
1256+
with self.assertRaises(ParserError):
1257+
self.parser.parse_iso("2019-12-31T24:00:00.999999")
1258+
11791259

11801260
class TzinfoParserTests(Chai):
11811261
def setUp(self):

0 commit comments

Comments
 (0)