Skip to content

Commit

Permalink
Implement an ISO 8601-aware DateTimeField
Browse files Browse the repository at this point in the history
`ISODateTimeField` is a specialized `DateTimeField` which understands the ISO
8601 format. Unfortunately, Django's built-in field is not able to do so, even
if a custom input format is specified, mostly because Python 2 doesn't support
`%z` as a timezone marker.

This field is useful for forms dealing with strings coming from the client-side,
as they will conform to a simplification of the ISO 8601 format (defined in
ECMA-262).
This is what's produced by the `Date` object's `toJSON` method, as well as what
Django's (and hence Pootle's) JSON serializer generates. Note the `datetime`
object as parsed by the field  will add padding zeros to conform to 6-digit
milliseconds. Serializing will strip this down to 3 digits again.

Refs. http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
Refs. https://code.djangoproject.com/ticket/11385
  • Loading branch information
julen committed Sep 14, 2016
1 parent 6ffb3b3 commit 6850675
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
27 changes: 27 additions & 0 deletions pootle/core/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django import forms
from django.utils.dateparse import parse_datetime
from django.utils.encoding import force_str


class ISODateTimeField(forms.DateTimeField):
"""A `DateTimeField` which understands timezone-aware ISO 8601 strings.
Django's built-in `DateTimeField` relies on Python's `strptime`, and the
format strings it parses against do not include the `%z` timezone marker.
But even if they did (and one can always specify a `input_formats` parameter
to the form field constructor), `%z` is not supported in Python 2:
http://bugs.python.org/issue6641.
Refs. https://code.djangoproject.com/ticket/11385.
"""

def strptime(self, value, format):
return parse_datetime(force_str(value))
57 changes: 57 additions & 0 deletions tests/core/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

import datetime

import pytz

from pootle.core.fields import ISODateTimeField
from pootle.core.utils.timezone import aware_datetime


def test_iso_datetime_field_no_timezone():
"""Tests parsing ISO date times without timezone information."""
field = ISODateTimeField()
reference_datetime = aware_datetime(2016, 9, 6, 14, 19, 52, 985000)
parsed_datetime = field.clean('2016-09-06T14:19:52.985')
assert isinstance(parsed_datetime, datetime.datetime)
assert parsed_datetime == reference_datetime


def test_iso_datetime_field_utc_timezone():
"""Tests parsing ISO date times with UTC timezone information."""
field = ISODateTimeField()
reference_datetime = aware_datetime(2016, 9, 6, 14, 19, 52, 985000,
tz=pytz.UTC)
parsed_datetime = field.clean('2016-09-06T14:19:52.985Z')
assert isinstance(parsed_datetime, datetime.datetime)
assert parsed_datetime == reference_datetime


def test_iso_datetime_field_explicit_timezone():
"""Tests parsing ISO date times with a explicit timezone information."""
field = ISODateTimeField()
reference_datetime = aware_datetime(2016, 9, 6, 14, 19, 52, 985000,
tz=pytz.timezone('Europe/Amsterdam'))
parsed_datetime = field.clean('2016-09-06T14:19:52.985+02:00')
assert isinstance(parsed_datetime, datetime.datetime)
assert parsed_datetime == reference_datetime


def test_iso_datetime_field_microseconds_precision():
"""Tests the microseconds' precission after parsing.
Microseconds have 6-digit precision as parsed by the field.
"""
field = ISODateTimeField()
reference_datetime = aware_datetime(2016, 9, 6, 14, 19, 52, 985,
tz=pytz.UTC)
parsed_datetime = field.clean('2016-09-06T14:19:52.985Z')
assert isinstance(parsed_datetime, datetime.datetime)
assert parsed_datetime != reference_datetime
assert parsed_datetime.microsecond == 985000

0 comments on commit 6850675

Please sign in to comment.