702 lines
26 KiB
Plaintext
702 lines
26 KiB
Plaintext
.. _time-zones:
|
|
|
|
==========
|
|
Time zones
|
|
==========
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. _time-zones-overview:
|
|
|
|
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 Web site is available in only one time zone, it's still good
|
|
practice to store data in UTC in your database. One main reason is Daylight
|
|
Saving Time (DST). Many countries have a system of DST, where clocks are moved
|
|
forward in spring and backward 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.) This
|
|
probably doesn't matter for your blog, but it's a problem 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 use local time only when
|
|
interacting with end users.
|
|
|
|
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
|
True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended,
|
|
but not mandatory. It's as simple as:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ sudo pip install pytz
|
|
|
|
.. note::
|
|
|
|
The default :file:`settings.py` file created by :djadmin:`django-admin.py
|
|
startproject <startproject>` includes :setting:`USE_TZ = True <USE_TZ>`
|
|
for convenience.
|
|
|
|
.. note::
|
|
|
|
There is also an independent but related :setting:`USE_L10N` setting that
|
|
controls whether Django should activate format localization. See
|
|
:doc:`/topics/i18n/formatting` for more details.
|
|
|
|
If you're wrestling with a particular problem, start with the :ref:`time zone
|
|
FAQ <time-zones-faq>`.
|
|
|
|
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 whether 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 <default-current-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() <django.utils.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.
|
|
|
|
The **current time zone** is the time zone that's used for rendering.
|
|
|
|
You should set the current time zone 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 <naive-datetime-objects>`, 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 <locale
|
|
name>` 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 <time-zone-selection-functions>`. Use them to build the time zone
|
|
selection logic that makes sense for you.
|
|
|
|
Most Web sites that 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['django_timezone'] = 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 %}
|
|
<form action="{% url 'set_timezone' %}" method="POST">
|
|
{% csrf_token %}
|
|
<label for="timezone">Time zone:</label>
|
|
<select name="timezone">
|
|
{% for tz in timezones %}
|
|
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected="selected"{% endif %}>{{ tz }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<input type="submit" value="Set" />
|
|
</form>
|
|
|
|
.. _time-zones-in-forms:
|
|
|
|
Time zone aware input in forms
|
|
==============================
|
|
|
|
When you enable time zone support, Django interprets datetimes entered in
|
|
forms in the :ref:`current time zone <default-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 <default-current-time-zone>` when they're rendered
|
|
in templates. This behaves very much like :doc:`format localization
|
|
</topics/i18n/formatting>`.
|
|
|
|
.. 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 %}
|
|
|
|
.. 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 <USE_TZ>` 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.
|
|
|
|
.. _time-zones-faq:
|
|
|
|
FAQ
|
|
===
|
|
|
|
Setup
|
|
-----
|
|
|
|
1. **I don't need multiple time zones. Should I enable time zone support?**
|
|
|
|
Yes. When time zone support is enabled, Django uses a more accurate model
|
|
of local time. This shields you from subtle and unreproducible bugs around
|
|
Daylight Saving Time (DST) transitions.
|
|
|
|
In this regard, time zones are comparable to ``unicode`` in Python. At first
|
|
it's hard. You get encoding and decoding errors. Then you learn the rules.
|
|
And some problems disappear -- you never get mangled output again when your
|
|
application receives non-ASCII input.
|
|
|
|
When you enable time zone support, you'll encounter some errors because
|
|
you're using naive datetimes where Django expects aware datetimes. Such
|
|
errors show up when running tests and they're easy to fix. You'll quickly
|
|
learn how to avoid invalid operations.
|
|
|
|
On the other hand, bugs caused by the lack of time zone support are much
|
|
harder to prevent, diagnose and fix. Anything that involves scheduled tasks
|
|
or datetime arithmetic is a candidate for subtle bugs that will bite you
|
|
only once or twice a year.
|
|
|
|
For these reasons, time zone support is enabled by default in new projects,
|
|
and you should keep it unless you have a very good reason not to.
|
|
|
|
2. **I've enabled time zone support. Am I safe?**
|
|
|
|
Maybe. You're better protected from DST-related bugs, but you can still
|
|
shoot yourself in the foot by carelessly turning naive datetimes into aware
|
|
datetimes, and vice-versa.
|
|
|
|
If your application connects to other systems -- for instance, if it queries
|
|
a Web service -- make sure datetimes are properly specified. To transmit
|
|
datetimes safely, their representation should include the UTC offset, or
|
|
their values should be in UTC (or both!).
|
|
|
|
Finally, our calendar system contains interesting traps for computers::
|
|
|
|
>>> import datetime
|
|
>>> def one_year_before(value): # DON'T DO THAT!
|
|
... return value.replace(year=value.year - 1)
|
|
>>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0))
|
|
datetime.datetime(2011, 3, 1, 10, 0)
|
|
>>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0))
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: day is out of range for month
|
|
|
|
(To implement this function, you must decide whether 2012-02-29 minus
|
|
one year is 2011-02-28 or 2011-03-01, which depends on your business
|
|
requirements.)
|
|
|
|
3. **Should I install pytz?**
|
|
|
|
Yes. Django has a policy of not requiring external dependencies, and for
|
|
this reason pytz_ is optional. However, it's much safer to install it.
|
|
|
|
As soon as you activate time zone support, Django needs a definition of the
|
|
default time zone. When pytz is available, Django loads this definition
|
|
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.
|
|
|
|
Furthermore, if you want to support users in more than one time zone, pytz
|
|
is the reference for time zone definitions.
|
|
|
|
Troubleshooting
|
|
---------------
|
|
|
|
1. **My application crashes with** ``TypeError: can't compare offset-naive``
|
|
``and offset-aware datetimes`` **-- what's wrong?**
|
|
|
|
Let's reproduce this error by comparing a naive and an aware datetime::
|
|
|
|
>>> import datetime
|
|
>>> from django.utils import timezone
|
|
>>> naive = datetime.datetime.utcnow()
|
|
>>> aware = naive.replace(tzinfo=timezone.utc)
|
|
>>> naive == aware
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: can't compare offset-naive and offset-aware datetimes
|
|
|
|
If you encounter this error, most likely your code is comparing these two
|
|
things:
|
|
|
|
- a datetime provided by Django -- for instance, a value read from a form or
|
|
a model field. Since you enabled time zone support, it's aware.
|
|
- a datetime generated by your code, which is naive (or you wouldn't be
|
|
reading this).
|
|
|
|
Generally, the correct solution is to change your code to use an aware
|
|
datetime instead.
|
|
|
|
If you're writing a pluggable application that's expected to work
|
|
independently of the value of :setting:`USE_TZ`, you may find
|
|
:func:`django.utils.timezone.now` useful. This function returns the current
|
|
date and time as a naive datetime when ``USE_TZ = False`` and as an aware
|
|
datetime when ``USE_TZ = True``. You can add or subtract
|
|
:class:`datetime.timedelta` as needed.
|
|
|
|
2. **I see lots of** ``RuntimeWarning: DateTimeField received a naive
|
|
datetime`` ``(YYYY-MM-DD HH:MM:SS)`` ``while time zone support is active``
|
|
**-- is that bad?**
|
|
|
|
When time zone support is enabled, the database layer expects to receive
|
|
only aware datetimes from your code. This warning occurs when it receives a
|
|
naive datetime. This indicates that you haven't finished porting your code
|
|
for time zone support. Please refer to the :ref:`migration guide
|
|
<time-zones-migration-guide>` for tips on this process.
|
|
|
|
In the meantime, for backwards compatibility, the datetime is considered to
|
|
be in the default time zone, which is generally what you expect.
|
|
|
|
3. ``now.date()`` **is yesterday! (or tomorrow)**
|
|
|
|
If you've always used naive datetimes, you probably believe that you can
|
|
convert a datetime to a date by calling its :meth:`~datetime.datetime.date`
|
|
method. You also consider that a :class:`~datetime.date` is a lot like a
|
|
:class:`~datetime.datetime`, except that it's less accurate.
|
|
|
|
None of this is true in a time zone aware environment::
|
|
|
|
>>> import datetime
|
|
>>> import pytz
|
|
>>> paris_tz = pytz.timezone("Europe/Paris")
|
|
>>> new_york_tz = pytz.timezone("America/New_York")
|
|
>>> paris = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30))
|
|
# This is the correct way to convert between time zones with pytz.
|
|
>>> new_york = new_york_tz.normalize(paris.astimezone(new_york_tz))
|
|
>>> paris == new_york, paris.date() == new_york.date()
|
|
(True, False)
|
|
>>> paris - new_york, paris.date() - new_york.date()
|
|
(datetime.timedelta(0), datetime.timedelta(1))
|
|
>>> paris
|
|
datetime.datetime(2012, 3, 3, 1, 30, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
|
|
>>> new_york
|
|
datetime.datetime(2012, 3, 2, 19, 30, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
|
|
|
|
As this example shows, the same datetime has a different date, depending on
|
|
the time zone in which it is represented. But the real problem is more
|
|
fundamental.
|
|
|
|
A datetime represents a **point in time**. It's absolute: it doesn't depend
|
|
on anything. On the contrary, a date is a **calendaring concept**. It's a
|
|
period of time whose bounds depend on the time zone in which the date is
|
|
considered. As you can see, these two concepts are fundamentally different,
|
|
and converting a datetime to a date isn't a deterministic operation.
|
|
|
|
What does this mean in practice?
|
|
|
|
Generally, you should avoid converting a :class:`~datetime.datetime` to
|
|
:class:`~datetime.date`. For instance, you can use the :tfilter:`date`
|
|
template filter to only show the date part of a datetime. This filter will
|
|
convert the datetime into the current time zone before formatting it,
|
|
ensuring the results appear correctly.
|
|
|
|
If you really need to do the conversion yourself, you must ensure the
|
|
datetime is converted to the appropriate time zone first. Usually, this
|
|
will be the current timezone::
|
|
|
|
>>> from django.utils import timezone
|
|
>>> timezone.activate(pytz.timezone("Asia/Singapore"))
|
|
# For this example, we just set the time zone to Singapore, but here's how
|
|
# you would obtain the current time zone in the general case.
|
|
>>> current_tz = timezone.get_current_timezone()
|
|
# Again, this is the correct way to convert between time zones with pytz.
|
|
>>> local = current_tz.normalize(paris.astimezone(current_tz))
|
|
>>> local
|
|
datetime.datetime(2012, 3, 3, 8, 30, tzinfo=<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>)
|
|
>>> local.date()
|
|
datetime.date(2012, 3, 3)
|
|
|
|
Usage
|
|
-----
|
|
|
|
1. **I have a string** ``"2012-02-21 10:28:45"`` **and I know it's in the**
|
|
``"Europe/Helsinki"`` **time zone. How do I turn that into an aware
|
|
datetime?**
|
|
|
|
This is exactly what pytz_ is for.
|
|
|
|
>>> from django.utils.dateparse import parse_datetime
|
|
>>> naive = parse_datetime("2012-02-21 10:28:45")
|
|
>>> import pytz
|
|
>>> pytz.timezone("Europe/Helsinki").localize(naive, is_dst=None)
|
|
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=<DstTzInfo 'Europe/Helsinki' EET+2:00:00 STD>)
|
|
|
|
Note that ``localize`` is a pytz extension to the :class:`~datetime.tzinfo`
|
|
API. Also, you may want to catch :exc:`~pytz.InvalidTimeError`. The
|
|
documentation of pytz contains `more examples`_. You should review it
|
|
before attempting to manipulate aware datetimes.
|
|
|
|
2. **How can I obtain the local time in the current time zone?**
|
|
|
|
Well, the first question is, do you really need to?
|
|
|
|
You should only use local time when you're interacting with humans, and the
|
|
template layer provides :ref:`filters and tags <time-zones-in-templates>`
|
|
to convert datetimes to the time zone of your choice.
|
|
|
|
Furthermore, Python knows how to compare aware datetimes, taking into
|
|
account UTC offsets when necessary. It's much easier (and possibly faster)
|
|
to write all your model and view code in UTC. So, in most circumstances,
|
|
the datetime in UTC returned by :func:`django.utils.timezone.now` will be
|
|
sufficient.
|
|
|
|
For the sake of completeness, though, if you really want the local time
|
|
in the current time zone, here's how you can obtain it::
|
|
|
|
>>> from django.utils import timezone
|
|
>>> timezone.localtime(timezone.now())
|
|
datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
|
|
|
|
In this example, pytz_ is installed and the current time zone is
|
|
``"Europe/Paris"``.
|
|
|
|
3. **How can I see all available time zones?**
|
|
|
|
pytz_ provides helpers_, including a list of current time zones and a list
|
|
of all available time zones -- some of which are only of historical
|
|
interest.
|
|
|
|
.. _pytz: http://pytz.sourceforge.net/
|
|
.. _more examples: http://pytz.sourceforge.net/#example-usage
|
|
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
|
.. _helpers: http://pytz.sourceforge.net/#helpers
|
|
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
|