mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-01-25 16:38:09 +00:00
Add support for parsing partial isoformats back
This commit is contained in:
parent
4b47646e28
commit
9752819bdb
2 changed files with 83 additions and 0 deletions
|
@ -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):
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue