Repository: modoboa/modoboa

Issue:
modo check_mx timezone problem
# Impacted versions

* Modoboa: 1.6.2
* installer used: No 
* Webserver: Nginx

# Steps to reproduce

This week end in France We switched from winter time to summer time, but it seem between the switch, the date 2017-03-26 02:30:01.875287 must be valid.

# Current behavior

$PYTHON $INSTANCE/manage.py modo check_mx
See traceback below

# Expected behavior

No erreor must be appear

# Traceback
```
Traceback (most recent call last):
  File "/srv/modoboa/instance/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/__init__.py", line 353, in execute_from_command_line
    utility.execute()
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/__init__.py", line 345, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/django_subcommand2-0.1.1-py2.7.egg/subcommand/base.py", line 53, in run_from_argv
    return super(SubcommandCommand, self).run_from_argv(argv)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/base.py", line 348, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/base.py", line 399, in execute
    output = self.handle(*args, **options)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/django_subcommand2-0.1.1-py2.7.egg/subcommand/base.py", line 84, in handle
    return command.run_from_argv(argv)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/base.py", line 348, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/core/management/base.py", line 399, in execute
    output = self.handle(*args, **options)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/modoboa/admin/management/commands/subcommands/_mx.py", line 262, in handle
    self.check_domain(domain, **options)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/modoboa/admin/management/commands/subcommands/_mx.py", line 218, in check_domain
    mx_list = list(self.get_mx_records_for_domain(domain, ttl=ttl))
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/modoboa/admin/management/commands/subcommands/_mx.py", line 109, in get_mx_records_for_domain
    updated=now + delta)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/query.py", line 401, in create
    obj.save(force_insert=True, using=self.db)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/base.py", line 708, in save
    force_update=force_update, update_fields=update_fields)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/base.py", line 736, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/base.py", line 820, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/base.py", line 859, in _do_insert
    using=using, raw=raw)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/query.py", line 1039, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/sql/compiler.py", line 1059, in execute_sql
    for sql, params in self.as_sql():
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/sql/compiler.py", line 1019, in as_sql
    for obj in self.query.objs
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/sql/compiler.py", line 958, in prepare_value
    value = field.get_db_prep_save(value, connection=self.connection)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/fields/__init__.py", line 728, in get_db_prep_save
    prepared=False)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/fields/__init__.py", line 1461, in get_db_prep_value
    value = self.get_prep_value(value)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/db/models/fields/__init__.py", line 1455, in get_prep_value
    value = timezone.make_aware(value, default_timezone)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/Django-1.9.12-py2.7.egg/django/utils/timezone.py", line 364, in make_aware
    return timezone.localize(value, is_dst=is_dst)
  File "/srv/modoboa/env/local/lib/python2.7/site-packages/pytz/tzinfo.py", line 327, in localize
    raise NonExistentTimeError(dt)
pytz.exceptions.NonExistentTimeError: 2017-03-26 02:30:01.875287
```


Discussion:
see pytz http://pytz.sourceforge.net/#problems-with-localtime
@kryskool can you check that `USE_TZ = True` in `settings.py` this resolves the problem by storing all dates/times in UTC.

When `USE_TZ = False` dates/times are stored using the time zone specified in `TIME_ZONE` which can result in the issue you have encountered during summer/winter time clock changes.

Resolving patch:
diff --git a/modoboa/core/apps.py b/modoboa/core/apps.py
--- a/modoboa/core/apps.py
+++ b/modoboa/core/apps.py
@@ -29,6 +29,8 @@ class CoreConfig(AppConfig):
     def ready(self):
         load_core_settings()
 
+        # Import these to force registration of checks and signals
+        from . import checks  # noqa:F401
         from . import handlers
 
         signals.post_migrate.connect(handlers.create_local_config, sender=self)
diff --git a/modoboa/core/checks/__init__.py b/modoboa/core/checks/__init__.py
new file mode 100644
--- /dev/null
+++ b/modoboa/core/checks/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+# Import these to force registration of checks
+from . import settings_checks  # noqa:F401
diff --git a/modoboa/core/checks/settings_checks.py b/modoboa/core/checks/settings_checks.py
new file mode 100644
--- /dev/null
+++ b/modoboa/core/checks/settings_checks.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.core.checks import Warning, register
+from django.utils.translation import ugettext as _
+
+
+W001 = Warning(
+    _("You have USE_TZ set to False, this may result in issues during "
+      "transitions between summer/winter time (ie the same local time occuring "
+      "twice due to clock change)."),
+    hint=_("Set `USE_TZ = True` in settings.py"),
+    id="modoboa.W001",
+)
+
+
+@register(deploy=True)
+def check_use_tz_enabled(app_configs, **kwargs):
+    """Ensure USE_TZ is enabled in settings.py
+
+    When USE_TZ is enabled all date/times are stored in UTC.
+    Fixes #1086 - https://github.com/modoboa/modoboa/issues/1086
+    """
+    errors = []
+    if not settings.USE_TZ:
+        errors.append(W001)
+    return errors


Test patch:
diff --git a/modoboa/core/tests/test_checks.py b/modoboa/core/tests/test_checks.py
new file mode 100644
--- /dev/null
+++ b/modoboa/core/tests/test_checks.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+
+from django.test import SimpleTestCase
+from django.test.utils import override_settings
+
+from ..checks import settings_checks
+
+
+class CheckSessionCookieSecureTest(SimpleTestCase):
+    @override_settings(USE_TZ=False)
+    def test_use_tz_false(self):
+        """If USE_TZ is off provide one warning."""
+        self.assertEqual(
+            settings_checks.check_use_tz_enabled(None),
+            [settings_checks.W001]
+        )
+
+    @override_settings(USE_TZ=True)
+    def test_use_tz_true(self):
+        """If USE_TZ is on, there's no warning about it."""
+        self.assertEqual(settings_checks.check_use_tz_enabled(None), [])