fix: add local timezone awareness for datetime fields
this likely still needs improvement, but a good starting point
This commit is contained in:
parent
f943d65f1c
commit
286c683c93
2 changed files with 74 additions and 3 deletions
|
|
@ -31,6 +31,7 @@ import colander
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from wuttjamaican.conf import parse_list
|
from wuttjamaican.conf import parse_list
|
||||||
|
from wuttjamaican.util import localtime, make_utc
|
||||||
|
|
||||||
from wuttaweb.db import Session
|
from wuttaweb.db import Session
|
||||||
from wuttaweb.forms import widgets
|
from wuttaweb.forms import widgets
|
||||||
|
|
@ -48,6 +49,18 @@ class WuttaDateTime(colander.DateTime):
|
||||||
the Buefy datepicker + timepicker widgets.
|
the Buefy datepicker + timepicker widgets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if not appstruct:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
if appstruct.tzinfo is None:
|
||||||
|
appstruct = localtime(appstruct)
|
||||||
|
|
||||||
|
if self.format:
|
||||||
|
return appstruct.strftime(self.format)
|
||||||
|
|
||||||
|
return appstruct.isoformat()
|
||||||
|
|
||||||
def deserialize( # pylint: disable=inconsistent-return-statements,empty-docstring
|
def deserialize( # pylint: disable=inconsistent-return-statements,empty-docstring
|
||||||
self, node, cstruct
|
self, node, cstruct
|
||||||
):
|
):
|
||||||
|
|
@ -62,7 +75,9 @@ class WuttaDateTime(colander.DateTime):
|
||||||
|
|
||||||
for fmt in formats:
|
for fmt in formats:
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(cstruct, fmt)
|
dt = datetime.datetime.strptime(cstruct, fmt)
|
||||||
|
dt = dt.astimezone()
|
||||||
|
return make_utc(dt)
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,52 @@ from wuttaweb.testing import WebTestCase
|
||||||
|
|
||||||
class TestWuttaDateTime(TestCase):
|
class TestWuttaDateTime(TestCase):
|
||||||
|
|
||||||
|
def test_serialize(self):
|
||||||
|
typ = mod.WuttaDateTime()
|
||||||
|
node = colander.SchemaNode(typ)
|
||||||
|
|
||||||
|
result = typ.serialize(node, colander.null)
|
||||||
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
result = typ.serialize(node, None)
|
||||||
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
result = typ.serialize(node, "")
|
||||||
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
# naive, UTC
|
||||||
|
# TODO: must override local timezone for a complete test
|
||||||
|
result = typ.serialize(node, datetime.datetime(2024, 12, 11, 22, 33))
|
||||||
|
self.assertTrue(result.startswith("2024-12-"))
|
||||||
|
|
||||||
|
# aware, UTC
|
||||||
|
result = typ.serialize(
|
||||||
|
node, datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc)
|
||||||
|
)
|
||||||
|
self.assertEqual(result, "2024-12-11T22:33:00+00:00")
|
||||||
|
|
||||||
|
# aware, local
|
||||||
|
result = typ.serialize(
|
||||||
|
node,
|
||||||
|
datetime.datetime(
|
||||||
|
2024,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
22,
|
||||||
|
33,
|
||||||
|
tzinfo=datetime.timezone(-datetime.timedelta(hours=5)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(result, "2024-12-11T22:33:00-05:00")
|
||||||
|
|
||||||
|
# custom format
|
||||||
|
typ = mod.WuttaDateTime(format="%Y-%m-%d %I:%M %p")
|
||||||
|
node = colander.SchemaNode(typ)
|
||||||
|
result = typ.serialize(
|
||||||
|
node, datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc)
|
||||||
|
)
|
||||||
|
self.assertEqual(result, "2024-12-11 10:33 PM")
|
||||||
|
|
||||||
def test_deserialize(self):
|
def test_deserialize(self):
|
||||||
typ = mod.WuttaDateTime()
|
typ = mod.WuttaDateTime()
|
||||||
node = colander.SchemaNode(typ)
|
node = colander.SchemaNode(typ)
|
||||||
|
|
@ -26,14 +72,24 @@ class TestWuttaDateTime(TestCase):
|
||||||
result = typ.deserialize(node, colander.null)
|
result = typ.deserialize(node, colander.null)
|
||||||
self.assertIs(result, colander.null)
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
result = typ.deserialize(node, None)
|
||||||
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
result = typ.deserialize(node, "")
|
||||||
|
self.assertIs(result, colander.null)
|
||||||
|
|
||||||
|
# TODO: must override local timezone for a complete test
|
||||||
result = typ.deserialize(node, "2024-12-11T10:33 PM")
|
result = typ.deserialize(node, "2024-12-11T10:33 PM")
|
||||||
self.assertIsInstance(result, datetime.datetime)
|
self.assertIsInstance(result, datetime.datetime)
|
||||||
self.assertEqual(result, datetime.datetime(2024, 12, 11, 22, 33))
|
dt = datetime.datetime(2024, 12, 11, 22, 33)
|
||||||
|
self.assertLess(abs((result - dt).total_seconds()), 60 * 60 * 24)
|
||||||
self.assertIsNone(result.tzinfo)
|
self.assertIsNone(result.tzinfo)
|
||||||
|
|
||||||
|
# TODO: must override local timezone for a complete test
|
||||||
result = typ.deserialize(node, "2024-12-11T22:33:00")
|
result = typ.deserialize(node, "2024-12-11T22:33:00")
|
||||||
self.assertIsInstance(result, datetime.datetime)
|
self.assertIsInstance(result, datetime.datetime)
|
||||||
self.assertEqual(result, datetime.datetime(2024, 12, 11, 22, 33))
|
dt = datetime.datetime(2024, 12, 11, 22, 33)
|
||||||
|
self.assertLess(abs((result - dt).total_seconds()), 60 * 60 * 24)
|
||||||
self.assertIsNone(result.tzinfo)
|
self.assertIsNone(result.tzinfo)
|
||||||
|
|
||||||
self.assertRaises(colander.Invalid, typ.deserialize, node, "bogus")
|
self.assertRaises(colander.Invalid, typ.deserialize, node, "bogus")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue