diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index cc96a25c1..1d84a3f4f 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -21,6 +21,7 @@ from netbox.models import ChangeLoggedModel from netbox.models.features import CloningMixin, ExportTemplatesMixin from netbox.search import FieldTypes from utilities import filters +from utilities.datetime import datetime_from_timestamp from utilities.forms.fields import ( CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVMultipleChoiceField, DynamicChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, DynamicMultipleChoiceField, JSONField, LaxURLField, @@ -672,12 +673,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): # Validate date & time elif self.type == CustomFieldTypeChoices.TYPE_DATETIME: if type(value) is not datetime: - # Work around UTC issue for Python < 3.11; see - # https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat - if type(value) is str and value.endswith('Z'): - value = f'{value[:-1]}+00:00' try: - datetime.fromisoformat(value) + datetime_from_timestamp(value) except ValueError: raise ValidationError( _("Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS).") diff --git a/netbox/utilities/datetime.py b/netbox/utilities/datetime.py index 2ec35af98..d54ea7c46 100644 --- a/netbox/utilities/datetime.py +++ b/netbox/utilities/datetime.py @@ -1,7 +1,10 @@ +import datetime + from django.utils import timezone from django.utils.timezone import localtime __all__ = ( + 'datetime_from_timestamp', 'local_now', ) @@ -11,3 +14,15 @@ def local_now(): Return the current date & time in the system timezone. """ return localtime(timezone.now()) + + +def datetime_from_timestamp(value): + """ + Convert an ISO 8601 or RFC 3339 timestamp to a datetime object. + """ + # Work around UTC issue for Python < 3.11; see + # https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat + # TODO: Remove this once Python 3.10 is no longer supported + if type(value) is str and value.endswith('Z'): + value = f'{value[:-1]}+00:00' + return datetime.datetime.fromisoformat(value) diff --git a/netbox/utilities/release.py b/netbox/utilities/release.py index dacec1010..e41e6d2bf 100644 --- a/netbox/utilities/release.py +++ b/netbox/utilities/release.py @@ -6,6 +6,8 @@ from typing import Union from django.core.exceptions import ImproperlyConfigured +from utilities.datetime import datetime_from_timestamp + RELEASE_PATH = 'release.yaml' LOCAL_RELEASE_PATH = 'local/release.yaml' @@ -52,6 +54,6 @@ def load_release_data(): # Convert the published date to a date object if 'published' in data: - data['published'] = datetime.date.fromisoformat(data['published']) + data['published'] = datetime_from_timestamp(data['published']) return ReleaseInfo(**data)