From 055f35406a063eb0b2d49933b3d10c71d4b333a7 Mon Sep 17 00:00:00 2001 From: Raphael Gaschignard Date: Thu, 5 Apr 2018 15:14:52 +0900 Subject: [PATCH] Add a setting to set a default timezone for datetime representations DRF, by default, uses Django's timezone settings for deciding on how to represent datetimes if timezone support is active. This setting allows the user to instead point to a different timezone for their API endpoint in particular. --- docs/api-guide/settings.md | 8 ++++++++ rest_framework/fields.py | 8 +++++++- rest_framework/settings.py | 1 + tests/test_fields.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index caa065389..7012240a3 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -306,6 +306,14 @@ May be a list including the string `'iso-8601'` or Python [strftime format][strf Default: `['iso-8601']` +#### DATETIME_TZ + +If set, used as the canonical timezone for `DateTimeField` serialization. If this is not set but Django timezone support is active, Django's timezone is instead used + +May be any of `None`, or a `datetime.tzinfo` object (such as `pytz.timezone('Asia/Kolkata')`) + +Default: None + #### DATE_FORMAT A format string that should be used by default for rendering the output of `DateField` serializer fields. If `None`, then `DateField` serializer fields will return Python `date` objects, and the date encoding will be determined by the renderer. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a710df7b4..c5fd65c75 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1174,7 +1174,13 @@ class DateTimeField(Field): return value def default_timezone(self): - return timezone.get_current_timezone() if settings.USE_TZ else None + if settings.USE_TZ: + if api_settings.DATETIME_TZ: + return api_settings.DATETIME_TZ + else: + return timezone.get_current_timezone() + else: + return None def to_internal_value(self, value): input_formats = getattr(self, 'input_formats', api_settings.DATETIME_INPUT_FORMATS) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index db92b7a7b..0da46a395 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -107,6 +107,7 @@ DEFAULTS = { 'DATETIME_FORMAT': ISO_8601, 'DATETIME_INPUT_FORMATS': (ISO_8601,), + 'DATETIME_TZ': None, 'TIME_FORMAT': ISO_8601, 'TIME_INPUT_FORMATS': (ISO_8601,), diff --git a/tests/test_fields.py b/tests/test_fields.py index fc9ce192a..37f2f09e8 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -14,6 +14,7 @@ from django.utils.timezone import activate, deactivate, utc import rest_framework from rest_framework import compat, serializers from rest_framework.fields import DjangoImageField, is_simple_callable +from rest_framework.settings import api_settings try: import pytz @@ -1273,6 +1274,42 @@ class TestTZWithDateTimeField(FieldValues): cls.field = serializers.DateTimeField(default_timezone=kolkata) +@pytest.mark.skipif(pytz is None, reason='pytz not installed') +@override_settings(TIME_ZONE='Asia/Kolkata', USE_TZ=True) +class TestDateTimeWithOverrideField(FieldValues, TestCase): + """ + Valid and invalid values for `DateTimeField` when not using UTC as the timezone. + """ + @classmethod + def setup_class(cls): + # use class setup method, as class-level attribute will still be evaluated even if test is skipped + kolkata = pytz.timezone('Asia/Kolkata') + paris = pytz.timezone('Europe/Paris') + # set the timezone to a specific timezone that isn't the django-level setting + cls.old_setting = api_settings.DATETIME_TZ + + api_settings.DATETIME_TZ = paris + + + cls.valid_inputs = { + '2016-12-19T10:00:00': paris.localize(datetime.datetime(2016, 12, 19, 10)), + # datetimes are reworked into the paris tz + '2016-12-19T10:00:00+05:30': paris.localize(datetime.datetime(2016, 12, 19, 5, 30)), + # naive datetimes are assumed to be in the paris tz + datetime.datetime(2016, 12, 19, 10): paris.localize(datetime.datetime(2016, 12, 19, 10)), + } + cls.invalid_inputs = {} + cls.outputs = { + datetime.datetime(2016, 12, 19, 10): '2016-12-19T10:00:00+01:00', + datetime.datetime(2016, 12, 19, 4, 30, tzinfo=utc): '2016-12-19T05:30:00+01:00', + } + cls.field = serializers.DateTimeField() + + @classmethod + def teardown_class(cls): + api_settings.DATETIME_TZ = cls.old_setting + + @pytest.mark.skipif(pytz is None, reason='pytz not installed') @override_settings(TIME_ZONE='UTC', USE_TZ=True) class TestDefaultTZDateTimeField(TestCase):