Fixed #17738 -- Extended the time zone documentation with a FAQ. Thanks Anssi for the questions and Jannis for the review.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17645 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
149e541034
commit
af3791fa7f
|
@ -6,6 +6,8 @@ Time zones
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
.. _time-zones-overview:
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -30,7 +32,11 @@ interacting with end users.
|
||||||
|
|
||||||
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
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,
|
True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended,
|
||||||
but not mandatory.
|
but not mandatory. It's as simple as::
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
$ sudo pip install pytz
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -44,6 +50,9 @@ but not mandatory.
|
||||||
controls if Django should activate format localization. See
|
controls if Django should activate format localization. See
|
||||||
:doc:`/topics/i18n/formatting` for more details.
|
:doc:`/topics/i18n/formatting` for more details.
|
||||||
|
|
||||||
|
If you're stumbling on a particular problem, start with the :ref:`time zone
|
||||||
|
FAQ <time-zones-faq>`.
|
||||||
|
|
||||||
Concepts
|
Concepts
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -132,15 +141,9 @@ Default time zone and current time zone
|
||||||
The **default time zone** is the time zone defined by the :setting:`TIME_ZONE`
|
The **default time zone** is the time zone defined by the :setting:`TIME_ZONE`
|
||||||
setting.
|
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.
|
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
|
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
|
:func:`~django.utils.timezone.activate`. Otherwise, the default time zone is
|
||||||
used.
|
used.
|
||||||
|
|
||||||
|
@ -173,7 +176,7 @@ selection logic that makes sense for you.
|
||||||
Most websites who care about time zones just ask users in which time zone they
|
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,
|
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
|
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
|
helpers_, like a list of time zones per country, that you can use to pre-select
|
||||||
the most likely choices.
|
the most likely choices.
|
||||||
|
|
||||||
Here's an example that stores the current timezone in the session. (It skips
|
Here's an example that stores the current timezone in the session. (It skips
|
||||||
|
@ -217,6 +220,8 @@ Include a form in ``template.html`` that will ``POST`` to this view:
|
||||||
<input type="submit" value="Set" />
|
<input type="submit" value="Set" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
.. _time-zones-in-forms:
|
||||||
|
|
||||||
Time zone aware input in forms
|
Time zone aware input in forms
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
@ -464,6 +469,241 @@ 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
|
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.
|
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. Remember that your website runs 24/7!
|
||||||
|
|
||||||
|
In this regard, time zones is 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 webservice, 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 substract_one_year(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?**
|
||||||
|
|
||||||
|
First, don't panic. Then, let's reproduce this error, simply 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:
|
||||||
|
|
||||||
|
- a datetime provided by Django, for instance a value read from a form or
|
||||||
|
a model field: since you enabled time zone support, it is 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 substract
|
||||||
|
: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 it 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 representend. But the real problem is more
|
||||||
|
fundamental.
|
||||||
|
|
||||||
|
A datetime represents a **point in time**. It's absolute: it doesn't depend
|
||||||
|
on anything (barring relativistic effects). 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 correct for the user.
|
||||||
|
|
||||||
|
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 this 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)
|
||||||
|
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 current time in the local 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 shake of completeness, if you really wanted the current time in the
|
||||||
|
local time zone, here's how you would obtain it::
|
||||||
|
|
||||||
|
>>> import datetime
|
||||||
|
>>> from django.utils import timezone
|
||||||
|
>>> datetime.datetime.now(tz=timezone.get_default_timezone())
|
||||||
|
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 :setting:`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/
|
.. _pytz: http://pytz.sourceforge.net/
|
||||||
|
.. _more examples: http://pytz.sourceforge.net/#example-usage
|
||||||
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
||||||
|
.. _helpers: http://pytz.sourceforge.net/#helpers
|
||||||
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
|
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
|
||||||
|
|
Loading…
Reference in New Issue