.. _time-zones: ========== Time zones ========== .. versionadded:: 1.4 Overview ======== When support for time zones is enabled, Django stores date and time information in UTC in the database, uses time zone-aware datetime objects internally, and translates them to the end user's time zone in templates and forms. This is handy if your users live in more than one time zone and you want to display date and time information according to each user's wall clock. Even if your website is available in only one time zone, it's still a good practice to store data in UTC in your database. Here is why. Many countries have a system of daylight saving time (DST), where clocks are moved forwards in spring and backwards in autumn. If you're working in local time, you're likely to encounter errors twice a year, when the transitions happen. The pytz_ documentation discusses `these issues`_ in greater detail. It probably doesn't matter for your blog, but it's more annoying if you over-bill or under-bill your customers by one hour, twice a year, every year. The solution to this problem is to use UTC in the code and local time only when interacting with end users. Time zone support is disabled by default. To enable it, set :setting:`USE_TZ = True ` in your settings file. Installing pytz_ is highly recommended, but not mandatory. .. note:: The default :file:`settings.py` file created by :djadmin:`django-admin.py startproject ` includes :setting:`USE_TZ = True ` for convenience. .. note:: There is also an independent but related :setting:`USE_L10N` setting that controls if Django should activate format localization. See :doc:`/topics/i18n/formatting` for more details. Concepts ======== Naive and aware datetime objects -------------------------------- Python's :class:`datetime.datetime` objects have a ``tzinfo`` attribute that can be used to store time zone information, represented as an instance of a subclass of :class:`datetime.tzinfo`. When this attribute is set and describes an offset, a datetime object is **aware**; otherwise, it's **naive**. You can use :func:`~django.utils.timezone.is_aware` and :func:`~django.utils.timezone.is_naive` to determine if datetimes are aware or naive. When time zone support is disabled, Django uses naive datetime objects in local time. This is simple and sufficient for many use cases. In this mode, to obtain the current time, you would write:: import datetime now = datetime.datetime.now() When time zone support is enabled, Django uses time zone aware datetime objects. If your code creates datetime objects, they should be aware too. In this mode, the example above becomes:: import datetime from django.utils.timezone import utc now = datetime.datetime.utcnow().replace(tzinfo=utc) .. note:: :mod:`django.utils.timezone` provides a :func:`~django.utils.timezone.now()` function that returns a naive or aware datetime object according to the value of :setting:`USE_TZ`. .. warning:: Dealing with aware datetime objects isn't always intuitive. For instance, the ``tzinfo`` argument of the standard datetime constructor doesn't work reliably for time zones with DST. Using UTC is generally safe; if you're using other time zones, you should review the `pytz`_ documentation carefully. .. note:: Python's :class:`datetime.time` objects also feature a ``tzinfo`` attribute, and PostgreSQL has a matching ``time with time zone`` type. However, as PostgreSQL's docs put it, this type "exhibits properties which lead to questionable usefulness". Django only supports naive time objects and will raise an exception if you attempt to save an aware time object. .. _naive-datetime-objects: Interpretation of naive datetime objects ---------------------------------------- When :setting:`USE_TZ` is ``True``, Django still accepts naive datetime objects, in order to preserve backwards-compatibility. When the database layer receives one, it attempts to make it aware by interpreting it in the :ref:`default time zone ` and raises a warning. Unfortunately, during DST transitions, some datetimes don't exist or are ambiguous. In such situations, pytz_ raises an exception. Other :class:`~datetime.tzinfo` implementations, such as the local time zone used as a fallback when pytz_ isn't installed, may raise an exception or return inaccurate results. That's why you should always create aware datetime objects when time zone support is enabled. In practice, this is rarely an issue. Django gives you aware datetime objects in the models and forms, and most often, new datetime objects are created from existing ones through :class:`~datetime.timedelta` arithmetic. The only datetime that's often created in application code is the current time, and :func:`timezone.now() ` automatically does the right thing. .. _default-current-time-zone: Default time zone and current time zone --------------------------------------- The **default time zone** is the time zone defined by the :setting:`TIME_ZONE` setting. When pytz_ is available, Django loads the definition of the default time zone from the `tz database`_. This is the most accurate solution. Otherwise, it relies on the difference between local time and UTC, as reported by the operating system, to compute conversions. This is less reliable, especially around DST transitions. The **current time zone** is the time zone that's used for rendering. You should set it to the end user's actual time zone with :func:`~django.utils.timezone.activate`. Otherwise, the default time zone is used. .. note:: As explained in the documentation of :setting:`TIME_ZONE`, Django sets environment variables so that its process runs in the default time zone. This happens regardless of the value of :setting:`USE_TZ` and of the current time zone. When :setting:`USE_TZ` is ``True``, this is useful to preserve backwards-compatibility with applications that still rely on local time. However, :ref:`as explained above `, this isn't entirely reliable, and you should always work with aware datetimes in UTC in your own code. For instance, use :meth:`~datetime.datetime.utcfromtimestamp` instead of :meth:`~datetime.datetime.fromtimestamp` -- and don't forget to set ``tzinfo`` to :data:`~django.utils.timezone.utc`. Selecting the current time zone ------------------------------- The current time zone is the equivalent of the current :term:`locale ` for translations. However, there's no equivalent of the ``Accept-Language`` HTTP header that Django could use to determine the user's time zone automatically. Instead, Django provides :ref:`time zone selection functions `. Use them to build the time zone selection logic that makes sense for you. Most websites who care about time zones just ask users in which time zone they live and store this information in the user's profile. For anonymous users, they use the time zone of their primary audience or UTC. pytz_ provides helpers, like a list of time zones per country, that you can use to pre-select the most likely choices. Here's an example that stores the current timezone in the session. (It skips error handling entirely for the sake of simplicity.) Add the following middleware to :setting:`MIDDLEWARE_CLASSES`:: from django.utils import timezone class TimezoneMiddleware(object): def process_request(self, request): tz = request.session.get('django_timezone') if tz: timezone.activate(tz) Create a view that can set the current timezone:: import pytz from django.shortcuts import redirect, render def set_timezone(request): if request.method == 'POST': request.session[session_key] = pytz.timezone(request.POST['timezone']) return redirect('/') else: return render(request, 'template.html', {'timezones': pytz.common_timezones}) Include a form in ``template.html`` that will ``POST`` to this view: .. code-block:: html+django {% load tz %}{% load url from future %}
{% csrf_token %}
Time zone aware input in forms ============================== When you enable time zone support, Django interprets datetimes entered in forms in the :ref:`current time zone ` and returns aware datetime objects in ``cleaned_data``. If the current time zone raises an exception for datetimes that don't exist or are ambiguous because they fall in a DST transition (the timezones provided by pytz_ do this), such datetimes will be reported as invalid values. .. _time-zones-in-templates: Time zone aware output in templates =================================== When you enable time zone support, Django converts aware datetime objects to the :ref:`current time zone ` when they're rendered in templates. This behaves very much like :doc:`format localization `. .. warning:: Django doesn't convert naive datetime objects, because they could be ambiguous, and because your code should never produce naive datetimes when time zone support is enabled. However, you can force conversion with the template filters described below. Conversion to local time isn't always appropriate -- you may be generating output for computers rather than for humans. The following filters and tags, provided by the ``tz`` template tag library, allow you to control the time zone conversions. Template tags ------------- .. templatetag:: localtime localtime ~~~~~~~~~ Enables or disables conversion of aware datetime objects to the current time zone in the contained block. This tag has exactly the same effects as the :setting:`USE_TZ` setting as far as the template engine is concerned. It allows a more fine grained control of conversion. To activate or deactivate conversion for a template block, use:: {% load tz %} {% localtime on %} {{ value }} {% endlocaltime %} {% localtime off %} {{ value }} {% endlocaltime %} .. note:: The value of :setting:`USE_TZ` isn't respected inside of a ``{% localtime %}`` block. .. templatetag:: timezone timezone ~~~~~~~~ Sets or unsets the current time zone in the contained block. When the current time zone is unset, the default time zone applies. :: {% load tz %} {% timezone "Europe/Paris" %} Paris time: {{ value }} {% endtimezone %} {% timezone None %} Server time: {{ value }} {% endtimezone %} .. note:: In the second block, ``None`` resolves to the Python object ``None`` because it isn't defined in the template context, not because it's the string ``None``. .. templatetag:: get_current_timezone get_current_timezone ~~~~~~~~~~~~~~~~~~~~ When the :func:`django.core.context_processors.tz` context processor is enabled -- by default, it is -- each :class:`~django.template.RequestContext` contains a ``TIME_ZONE`` variable that provides the name of the current time zone. If you don't use a :class:`~django.template.RequestContext`, you can obtain this value with the ``get_current_timezone`` tag:: {% get_current_timezone as TIME_ZONE %} Template filters ---------------- These filters accept both aware and naive datetimes. For conversion purposes, they assume that naive datetimes are in the default time zone. They always return aware datetimes. .. templatefilter:: localtime localtime ~~~~~~~~~ Forces conversion of a single value to the current time zone. For example:: {% load tz %} {{ value|localtime }} .. templatefilter:: utc utc ~~~ Forces conversion of a single value to UTC. For example:: {% load tz %} {{ value|utc }} .. templatefilter:: timezone timezone ~~~~~~~~ Forces conversion of a single value to an arbitrary timezone. The argument must be an instance of a :class:`~datetime.tzinfo` subclass or a time zone name. If it is a time zone name, pytz_ is required. For example:: {% load tz %} {{ value|timezone:"Europe/Paris" }} .. _time-zones-migration-guide: Migration guide =============== Here's how to migrate a project that was started before Django supported time zones. Database -------- PostgreSQL ~~~~~~~~~~ The PostgreSQL backend stores datetimes as ``timestamp with time zone``. In practice, this means it converts datetimes from the connection's time zone to UTC on storage, and from UTC to the connection's time zone on retrieval. As a consequence, if you're using PostgreSQL, you can switch between ``USE_TZ = False`` and ``USE_TZ = True`` freely. The database connection's time zone will be set to :setting:`TIME_ZONE` or ``UTC`` respectively, so that Django obtains correct datetimes in all cases. You don't need to perform any data conversions. Other databases ~~~~~~~~~~~~~~~ Other backends store datetimes without time zone information. If you switch from ``USE_TZ = False`` to ``USE_TZ = True``, you must convert your data from local time to UTC -- which isn't deterministic if your local time has DST. Code ---- The first step is to add :setting:`USE_TZ = True ` to your settings file and install pytz_ (if possible). At this point, things should mostly work. If you create naive datetime objects in your code, Django makes them aware when necessary. However, these conversions may fail around DST transitions, which means you aren't getting the full benefits of time zone support yet. Also, you're likely to run into a few problems because it's impossible to compare a naive datetime with an aware datetime. Since Django now gives you aware datetimes, you'll get exceptions wherever you compare a datetime that comes from a model or a form with a naive datetime that you've created in your code. So the second step is to refactor your code wherever you instantiate datetime objects to make them aware. This can be done incrementally. :mod:`django.utils.timezone` defines some handy helpers for compatibility code: :func:`~django.utils.timezone.now`, :func:`~django.utils.timezone.is_aware`, :func:`~django.utils.timezone.is_naive`, :func:`~django.utils.timezone.make_aware`, and :func:`~django.utils.timezone.make_naive`. Finally, in order to help you locate code that needs upgrading, Django raises a warning when you attempt to save a naive datetime to the database:: RuntimeWarning: DateTimeField received a naive datetime (2012-01-01 00:00:00) while time zone support is active. During development, you can turn such warnings into exceptions and get a traceback by adding the following to your settings file:: import warnings warnings.filterwarnings( 'error', r"DateTimeField received a naive datetime", RuntimeWarning, r'django\.db\.models\.fields') Fixtures -------- When serializing an aware datetime, the UTC offset is included, like this:: "2011-09-01T13:20:30+03:00" For a naive datetime, it obviously isn't:: "2011-09-01T13:20:30" For models with :class:`~django.db.models.DateTimeField`\ s, this difference makes it impossible to write a fixture that works both with and without time zone support. Fixtures generated with ``USE_TZ = False``, or before Django 1.4, use the "naive" format. If your project contains such fixtures, after you enable time zone support, you'll see :exc:`RuntimeWarning`\ s when you load them. To get rid of the warnings, you must convert your fixtures to the "aware" format. You can regenerate fixtures with :djadmin:`loaddata` then :djadmin:`dumpdata`. Or, if they're small enough, you can simply edit them to add the UTC offset that matches your :setting:`TIME_ZONE` to each serialized datetime. .. _pytz: http://pytz.sourceforge.net/ .. _these issues: http://pytz.sourceforge.net/#problems-with-localtime .. _tz database: http://en.wikipedia.org/wiki/Tz_database