Django 1.8.2.dev20150513143415 documentation

时区

概览

当时区支持开启时,Django将时间用UTC格式存储到数据库中,在内部使用时区相关的对象,并且在模板(templates)与表单(forms)中将时间转换为终端用户所在时区的时间

当你的用户生活在多个时区,并且你希望根据他们所在的位置显示当地时间时很有用

即便你的网站仅能在一个时区内访问,在数据库中存储UTC 时间依然一种很好的做法。一个主要的原因是夏令时。 许多国家都拥有自己的一套夏令时系统,在这套系统里,春季的时间会提前,而秋季的时间便会后延。如果你只以当前时间为标准来开发,每年都会因为夏令时而引起两次错误(The pytz documentation discusses these issues in greater detail.) 这个对于你的博客可能没有什么影响,但是如果涉及到按年,按月,按小时来收费的话,那么就会是一个问题,解决这个问题的方法便是在代码中使用UTC时间,仅在与最终用户进行交互的时候使用本地时间。

Django默认关闭时区支持,如欲开启时区支持,则需在settings中设置USE_TZ = True强烈推荐安装 pytz , 但根据所使用的数据库引擎、操作系统及时区的不同并不强制安装。如果在查询日期或时间时出现异常,请在检查bug之前先尝试安装pytz。可执行下述命令安装pytz:

$ pip install pytz

注意:

为方便起见,在由django-admin startproject 创建的settings.py文件中已设置 USE_TZ = True

注意:

另外在settings中,还有一个 USE_L10N设置选项,使用它可控制Django是否激活格式本地化。更多细节请参见格式本地化

如果你正为某个与时区相关的特殊问题而纠结, 请阅读时区FAQ

概念

Naive和aware类型的datetime对象

Python的 datetime.datetime 对象有一个 tzinfo 属性,该属性是datetime.tzinfo子类的一个实例,它被用来存储时区信息。 当某个datetime对象的tzinfo属性被设置并给出一个时间偏移量时,我们称该datetime对象是aware(已知)的。否则称其为 naive(原生)的。

可用is_aware()is_naive() 函数来判断某个datetime对象是aware类型或naive类型。

当关闭时区支持时(USE_TZ=False), Django使用原生的datetime对象保存本地时间。在许多应用中,这是最简单的方式,并且足以满足要求。在这种情况下,可使用下列代码获取当前时间:I

import datetime

now = datetime.datetime.now()

当开启时区支持时 (USE_TZ=True), Django使用已知(aware)的datetime对象存储本地时间。如果在代码中创建了datetime对象, 那么它们也应该是aware类型的datetime对象。在此情况下,上述例子变成:

from django.utils import timezone

now = timezone.now()

警告:

对aware类型的datetime对象的处理并非总是非常直观。例如:对DST时区来说,标准datetime对象构造器的tzinfo 参数并不能可靠地工作。此时,使用UTC一般来说是安全的;如果你的项目使用了其他的时区,那么应该认真阅读pytz的文档。

注意:

Python的 datetime.time 对象也具有包含一个 tzinfo属性的特点 , 并且在PostgreSQL数据库引擎中,有一个time with time zone 类型与该属性相匹配。但是,正如PostgreSQL的文档所指出的那样,该类型 “禁止使用那些可能导致出现问题的属性“。

Django仅支持naive(原生)类型的time对象, 并且,当试图保存一个aware类型的time对象时将会触发一个异常。这是因为,对于时区来说,如果保存的时间没有带有相关日期信息那就没有什么意义。.

 naive datetime 对象的解释

USE_TZTrue, Django 为了保持向后兼容性依旧接受 naive datetime 对象 . When the database layer receives one, it attempts to make it aware by interpreting it in the 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 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 timedelta arithmetic. The only datetime that’s often created in application code is the current time, and timezone.now() automatically does the right thing.

Default time zone and current time zone

The default time zone is the time zone defined by the 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 activate(). Otherwise, the default time zone is used.

Note

As explained in the documentation of TIME_ZONE, Django sets environment variables so that its process runs in the default time zone. This happens regardless of the value of USE_TZ and of the current time zone.

When USE_TZ is True, this is useful to preserve backwards-compatibility with applications that still rely on local time. However, 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 utcfromtimestamp() instead of fromtimestamp() – and don’t forget to set tzinfo to utc.

Selecting the current time zone

The current time zone is the equivalent of the current 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 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 MIDDLEWARE_CLASSES:

import pytz

from django.utils import timezone

class TimezoneMiddleware(object):
    def process_request(self, request):
        tzname = request.session.get('django_timezone')
        if tzname:
            timezone.activate(pytz.timezone(tzname))
        else:
            timezone.deactivate()

Create a view that can set the current timezone:

from django.shortcuts import redirect, render

def set_timezone(request):
    if request.method == 'POST':
        request.session['django_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:

{% load tz %}
{% get_current_timezone as TIME_ZONE %}
<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 zone aware input in forms

When you enable time zone support, Django interprets datetimes entered in forms in the 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 zone aware output in templates

When you enable time zone support, Django converts aware datetime objects to the current time zone when they’re rendered in templates. This behaves very much like format localization.

Warning

Django不转换naive datetime对象,因为它们可能是模糊的,并且因为您的代码不应该产生幼稚的数据时间,当时区支持启用。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

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 USE_TZ setting as far as the template engine is concerned. 它允许更精确的转换控制。

To activate or deactivate conversion for a template block, use:

{% load tz %}

{% localtime on %}
    {{ value }}
{% endlocaltime %}

{% localtime off %}
    {{ value }}
{% endlocaltime %}

Note

在 {% localtime %} 内不遵守 USE_TZ 。

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 %}

get_current_timezone

You can get the name of the current time zone using the get_current_timezone tag:

{% get_current_timezone as TIME_ZONE %}

If you enable the django.template.context_processors.tz context processor, each RequestContext will contain a TIME_ZONE variable with the value of get_current_timezone().

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.

localtime

Forces conversion of a single value to the current time zone.

For example:

{% load tz %}

{{ value|localtime }}

utc

Forces conversion of a single value to UTC.

For example:

{% load tz %}

{{ value|utc }}

timezone

Forces conversion of a single value to an arbitrary timezone.

The argument must be an instance of a 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" }}

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 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 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. django.utils.timezone defines some handy helpers for compatibility code: now(), is_aware(), is_naive(), make_aware(), and 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 ModelName.field_name 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"

对于naive datetime,它显然不是:

"2011-09-01T13:20:30"

对于具有DateTimeField的模型,此差异使得无法编写同时支持和不支持时区的混合用法。

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 RuntimeWarnings when you load them. To get rid of the warnings, you must convert your fixtures to the “aware” format.

You can regenerate fixtures with loaddata then dumpdata. Or, if they’re small enough, you can simply edit them to add the UTC offset that matches your TIME_ZONE to each serialized datetime.

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 = timezone.now()
    >>> 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 USE_TZ, you may find 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 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 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 date() method. You also consider that a date is a lot like a 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 datetime to date. For instance, you can use the 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)
    
  4. I get an error Are time zone definitions for your database and pytz installed?pytz is installed, so I guess the problem is my database?

    If you are using MySQL, see the Time zone definitions section of the MySQL notes for instructions on loading time zone definitions.

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 tzinfo API. Also, you may want to catch 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 filters and tags 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 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.