Add support for parsing partial isoformats back

This commit is contained in:
Adeodato Simó 2023-10-21 20:27:23 -03:00
parent 4b47646e28
commit 9752819bdb
No known key found for this signature in database
GPG key ID: CDF447845F1A986F
2 changed files with 83 additions and 0 deletions

View file

@ -18,16 +18,76 @@ class SealedDateTest(unittest.TestCase):
sealed = sealed_date.SealedDate.from_datetime(self.dt) sealed = sealed_date.SealedDate.from_datetime(self.dt)
self.assertEqual(self.dt, sealed) self.assertEqual(self.dt, sealed)
self.assertEqual("2023-10-20", sealed.partial_isoformat()) self.assertEqual("2023-10-20", sealed.partial_isoformat())
self.assertTrue(sealed.has_day)
self.assertTrue(sealed.has_month)
def test_month_seal(self): def test_month_seal(self):
sealed = sealed_date.MonthSeal.from_datetime(self.dt) sealed = sealed_date.MonthSeal.from_datetime(self.dt)
self.assertEqual(self.dt, sealed) self.assertEqual(self.dt, sealed)
self.assertEqual("2023-10", sealed.partial_isoformat()) self.assertEqual("2023-10", sealed.partial_isoformat())
self.assertFalse(sealed.has_day)
self.assertTrue(sealed.has_month)
def test_year_seal(self): def test_year_seal(self):
sealed = sealed_date.YearSeal.from_datetime(self.dt) sealed = sealed_date.YearSeal.from_datetime(self.dt)
self.assertEqual(self.dt, sealed) self.assertEqual(self.dt, sealed)
self.assertEqual("2023", sealed.partial_isoformat()) self.assertEqual("2023", sealed.partial_isoformat())
self.assertFalse(sealed.has_day)
self.assertFalse(sealed.has_month)
def test_parse_year_seal(self):
parsed = sealed_date.from_partial_isoformat("1995")
expected = datetime.date(1995, 1, 1)
self.assertEqual(expected, parsed.date())
self.assertFalse(parsed.has_day)
self.assertFalse(parsed.has_month)
def test_parse_year_errors(self):
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "995")
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "1995x")
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "1995-")
def test_parse_month_seal(self):
expected = datetime.date(1995, 5, 1)
test_cases = [
("parse_month", "1995-05"),
("parse_month_lenient", "1995-5"),
]
for desc, value in test_cases:
with self.subTest(desc):
parsed = sealed_date.from_partial_isoformat(value)
self.assertEqual(expected, parsed.date())
self.assertFalse(parsed.has_day)
self.assertTrue(parsed.has_month)
def test_parse_month_dash_required(self):
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "20056")
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "200506")
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "1995-7-")
def test_parse_day_seal(self):
expected = datetime.date(1995, 5, 6)
test_cases = [
("parse_day", "1995-05-06"),
("parse_day_lenient1", "1995-5-6"),
("parse_day_lenient2", "1995-05-6"),
]
for desc, value in test_cases:
with self.subTest(desc):
parsed = sealed_date.from_partial_isoformat(value)
self.assertEqual(expected, parsed.date())
self.assertTrue(parsed.has_day)
self.assertTrue(parsed.has_month)
def test_partial_isoformat_no_time_allowed(self):
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "2005-06-07 ")
self.assertRaises(ValueError, sealed_date.from_partial_isoformat, "2005-06-07T")
self.assertRaises(
ValueError, sealed_date.from_partial_isoformat, "2005-06-07T00:00:00"
)
self.assertRaises(
ValueError, sealed_date.from_partial_isoformat, "2005-06-07T00:00:00-03"
)
class SealedDateFormFieldTest(unittest.TestCase): class SealedDateFormFieldTest(unittest.TestCase):

View file

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime, timedelta from datetime import datetime, timedelta
import re
from typing import Any, Optional, Type, TypeVar, cast from typing import Any, Optional, Type, TypeVar, cast
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -11,6 +12,12 @@ from django.forms.widgets import SelectDateWidget
from django.utils import timezone from django.utils import timezone
__all__ = [
"SealedDate",
"from_partial_isoformat",
]
_partial_re = re.compile(r"(\d{4})(?:-(\d\d?))?(?:-(\d\d?))?$")
_westmost_tz = timezone.get_fixed_timezone(timedelta(hours=-12)) _westmost_tz = timezone.get_fixed_timezone(timedelta(hours=-12))
Sealed = TypeVar("Sealed", bound="SealedDate") # TODO: use Self in Python >= 3.11 Sealed = TypeVar("Sealed", bound="SealedDate") # TODO: use Self in Python >= 3.11
@ -63,6 +70,22 @@ class YearSeal(SealedDate):
return self.strftime("%Y") return self.strftime("%Y")
def from_partial_isoformat(value: str) -> SealedDate:
match = _partial_re.match(value)
if not match:
raise ValueError
year, month, day = [val and int(val) for val in match.groups()]
if month is None:
return YearSeal.from_date_parts(year, 1, 1)
elif day is None:
return MonthSeal.from_date_parts(year, month, 1)
else:
return SealedDate.from_date_parts(year, month, day)
class SealedDateFormField(DateField): class SealedDateFormField(DateField):
"""date form field with support for SealedDate""" """date form field with support for SealedDate"""