Skip to content

Commit a1a19e5

Browse files
fix: tz-aware tastypie datetimes (#9330)
* fix: tz-aware tastypie datetimes * chore: comment * chore: clarify comment
1 parent 32e1fe7 commit a1a19e5

File tree

1 file changed

+33
-0
lines changed

1 file changed

+33
-0
lines changed

ietf/api/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import datetime
66
import re
7+
import sys
78

89
from urllib.parse import urlencode
910

@@ -25,6 +26,9 @@
2526

2627
OMITTED_APPS_APIS = ["ietf.status"]
2728

29+
# Pre-py3.11, fromisoformat() does not handle Z or +HH tz offsets
30+
HAVE_BROKEN_FROMISOFORMAT = sys.version_info < (3, 11, 0, "", 0)
31+
2832
def populate_api_list():
2933
_module_dict = globals()
3034
for app_config in django_apps.get_app_configs():
@@ -58,6 +62,35 @@ def generate_cache_key(self, *args, **kwargs):
5862
# Use a list plus a ``.join()`` because it's faster than concatenation.
5963
return "%s:%s:%s:%s" % (self._meta.api_name, self._meta.resource_name, ':'.join(args), smooshed)
6064

65+
def _z_aware_fromisoformat(self, value):
66+
"""datetime.datetie.fromisoformat replacement that works with python < 3.11"""
67+
if HAVE_BROKEN_FROMISOFORMAT:
68+
if value.upper().endswith("Z"):
69+
value = value[:-1] + "+00:00" # Z -> UTC
70+
elif re.match(r"[+-][0-9][0-9]$", value[-3:]):
71+
value = value + ":00" # -04 -> -04:00
72+
return value
73+
74+
def filter_value_to_python(
75+
self, value, field_name, filters, filter_expr, filter_type
76+
):
77+
py_value = super().filter_value_to_python(
78+
value, field_name, filters, filter_expr, filter_type
79+
)
80+
if isinstance(
81+
self.fields[field_name], tastypie.fields.DateTimeField
82+
) and isinstance(py_value, str):
83+
# Ensure datetime values are TZ-aware, using UTC by default
84+
try:
85+
dt = self._z_aware_fromisoformat(py_value)
86+
except ValueError:
87+
pass # let tastypie deal with the original value
88+
else:
89+
if dt.tzinfo is None:
90+
dt = dt.replace(tzinfo=datetime.timezone.utc)
91+
py_value = dt.isoformat()
92+
return py_value
93+
6194

6295
TIMEDELTA_REGEX = re.compile(r'^(?P<days>\d+d)?\s?(?P<hours>\d+h)?\s?(?P<minutes>\d+m)?\s?(?P<seconds>\d+s?)$')
6396

0 commit comments

Comments
 (0)