当启用对时区的支持时,Django会在数据库中以UTC形式存储日期时间信息,在内部使用时区感知日期时间对象,并将其转换为表格中的最终用户的时区。模板将使用 default time zone ,但这可以通过使用 filters and tags 。
如果您的用户居住在多个时区,并且您希望根据每个用户的挂钟显示日期时间信息,那么这很方便。
即使你的网站只在一个时区可用,在你的数据库中以UTC格式存储数据仍然是一个很好的做法。主要原因是夏令时(DST)。许多国家都有夏令时制度,时钟在春天向前拨,在秋天向后拨。如果您在当地时间工作,您可能一年会遇到两次错误,也就是转换发生的时候。这对你的博客可能并不重要,但如果你每年多付或少付一小时,每年两次,这对你的博客来说是个问题。此问题的解决方案是在代码中使用UTC,并仅在与最终用户交互时使用本地时间。
默认情况下启用时区支持。要禁用它,请设置 USE_TZ = False
在您的设置文件中。
时区支持使用 zoneinfo
,它是来自Python3.9的Python标准库的一部分。
如果你要处理一个特定的问题,从 time zone FAQ .
Python 的 datetime.datetime
对象有一个 tzinfo
可用于存储时区信息的属性,表示为子类的实例 datetime.tzinfo
. 设置此属性并描述偏移量时,日期时间对象为 意识到的 . 否则,它的 天真的 .
你可以使用 is_aware()
和 is_naive()
确定日期时间是知道的还是天真的。
当禁用时区支持时,Django使用本地时间的原始日期时间对象。这对于许多用例来说已经足够了。在这种模式下,要获取当前时间,您需要写::
import datetime
now = datetime.datetime.now()
启用时区支持时 (USE_TZ=True
,django使用时区感知的日期时间对象。如果代码创建了日期时间对象,它们也应该知道。在此模式下,上面的示例变为:
from django.utils import timezone
now = timezone.now()
警告
处理可感知的DateTime对象并不总是直观的。例如, tzinfo
对于使用DST的时区,标准DateTime构造函数的参数不能可靠工作。使用UTC通常是安全的;如果您使用的是其他时区,则应查看 zoneinfo
仔细记录。
备注
Python 的 datetime.time
对象还具有 tzinfo
属性,PostgreSQL具有匹配的 time with time zone
类型。然而,正如PostgreSQL的文档所说,这种类型“展示了导致有用性有问题的属性”。
Django只支持幼稚的时间对象,如果试图保存一个感知的时间对象,则会引发异常,因为没有关联日期的时间的时区没有意义。
什么时候? USE_TZ
是 True
,django仍然接受幼稚的datetime对象,以保持向后兼容性。当数据库层接收到一个时,它试图通过在 default time zone 并发出警告。
不幸的是,在DST转换期间,一些日期时间不存在或不明确。这就是在启用时区支持时应始终创建可识别的DateTime对象的原因。(请参阅 Using ZoneInfo section of the zoneinfo docs
有关使用 fold
属性以指定在DST转换期间应应用于日期时间的偏移量。)
实际上,这很少是一个问题。Django为您提供了模型和表单中的日期时间对象,并且通常,新的日期时间对象是从现有的对象到 timedelta
算术。通常在应用程序代码中创建的唯一日期时间是当前时间,并且 timezone.now()
自动做正确的事情。
这个 默认时区 时区是由 TIME_ZONE
设置。
这个 当前时区 是用于呈现的时区。
您应该将当前时区设置为最终用户的实际时区 activate()
. 否则,将使用默认时区。
备注
如文件所述 TIME_ZONE
,django设置环境变量,使其进程在默认时区内运行。不管 USE_TZ
以及当前时区。
什么时候 USE_TZ
是 True
,这对于保持与仍然依赖本地时间的应用程序的向后兼容性很有用。然而, as explained above ,这并不完全可靠,您应该始终在自己的代码中使用UTC中的Aware DateTime。例如,使用 fromtimestamp()
并将 tz
参数设置为 utc
。
当前时区等于当前时区 locale 用于翻译。然而,没有等价的 Accept-Language
Django可以用来自动确定用户时区的HTTP头。相反,Django提供 time zone selection functions . 使用它们构建对您有意义的时区选择逻辑。
大多数关心时区的网站会询问用户他们住在哪个时区,并将这些信息存储在用户的个人资料中。对于匿名用户,他们使用主要受众或UTC的时区。 zoneinfo.available_timezones()
提供一组可用时区,可用于构建从可能的位置到时区的映射。
下面是一个在会话中存储当前时区的示例。(为了简单起见,它跳过了错误处理。)
将以下中间件添加到 MIDDLEWARE
::
import zoneinfo
from django.utils import timezone
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tzname = request.session.get("django_timezone")
if tzname:
timezone.activate(zoneinfo.ZoneInfo(tzname))
else:
timezone.deactivate()
return self.get_response(request)
创建可设置当前时区的视图:
from django.shortcuts import redirect, render
# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
"London": "Europe/London",
"Paris": "Europe/Paris",
"New York": "America/New_York",
}
def set_timezone(request):
if request.method == "POST":
request.session["django_timezone"] = request.POST["timezone"]
return redirect("/")
else:
return render(request, "template.html", {"timezones": common_timezones})
在中包含表单 template.html
那将 POST
对此观点:
{% 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 city, tz in timezones %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
{% endfor %}
</select>
<input type="submit" value="Set">
</form>
当启用时区支持时,Django将解释表单中输入的日期时间 current time zone 并返回中的感知日期时间对象 cleaned_data
.
转换后的日期时间不存在或不明确,因为它们处于DST转换中,将被报告为无效值。
启用时区支持时,Django将感知的日期时间对象转换为 current time zone 当它们在模板中呈现时。这很像 format localization .
警告
Django不转换naive datetime对象,因为它们可能不明确,而且当启用时区支持时,代码不应生成naive datetime。但是,可以使用下面描述的模板过滤器强制转换。
转换到本地时间并不总是合适的——您可能正在为计算机而不是为人类生成输出。以下过滤器和标签由 tz
模板标记库,允许您控制时区转换。
这些过滤器接受有意识和天真的日期时间。出于转换的目的,它们假定原始日期时间处于默认时区。他们总是返回已知的日期时间。
localtime
¶强制将单个值转换为当前时区。
例如:
{% load tz %}
{{ value|localtime }}
utc
¶强制将单个值转换为UTC。
例如:
{% load tz %}
{{ value|utc }}
timezone
¶强制将单个值转换为任意时区。
参数必须是 tzinfo
子类或时区名称。
例如:
{% load tz %}
{{ value|timezone:"Europe/Paris" }}
下面介绍如何迁移在Django支持时区之前启动的项目。
PostgreSQL后端将日期时间存储为 timestamp with time zone
. 实际上,这意味着它在存储时将日期时间从连接的时区转换为UTC,在检索时将日期时间从UTC转换为连接的时区。
因此,如果您使用的是PostgreSQL,则可以在 USE_TZ = False
和 USE_TZ = True
自由自在。数据库连接的时区将设置为 DATABASE-TIME_ZONE
或 UTC
以便Django在所有情况下都获得正确的日期时间。您不需要执行任何数据转换。
其他后端存储没有时区信息的日期时间。如果你从 USE_TZ = False
到 USE_TZ = True
,您必须将您的数据从本地时间转换为UTC——如果您的本地时间具有DST,则这不是确定性的。
第一步是添加 USE_TZ = True
到您的设置文件。在这一点上,事情应该基本上是可行的。如果您在代码中创建幼稚的日期时间对象,Django会在必要时让它们知道。
但是,这些转换可能会在DST转换中失败,这意味着您还没有获得时区支持的全部好处。此外,您可能会遇到一些问题,因为无法将一个天真的日期时间与一个已知的日期时间进行比较。因为django现在提供了可感知的日期时间,所以当您将来自模型或窗体的日期时间与在代码中创建的原始日期时间进行比较时,都会得到异常。
因此,第二步是在实例化datetime对象的任何位置重构代码,以使它们知道。这可以逐步完成。 django.utils.timezone
为兼容性代码定义一些方便的助手: now()
, is_aware()
, is_naive()
, make_aware()
和 make_naive()
.
最后,为了帮助您找到需要升级的代码,Django会在您尝试将一个简单的日期时间保存到数据库时发出警告:
RuntimeWarning: DateTimeField ModelName.field_name received a naive
datetime (2012-01-01 00:00:00) while time zone support is active.
在开发过程中,可以将这些警告转换为异常,并通过将以下内容添加到设置文件中来获取跟踪信息:
import warnings
warnings.filterwarnings(
"error",
r"DateTimeField .* received a naive datetime",
RuntimeWarning,
r"django\.db\.models\.fields",
)
序列化已知的日期时间时,包括UTC偏移量,如下所示:
"2011-09-01T13:20:30+03:00"
而对于一个天真的约会时间来说,它不是::
"2011-09-01T13:20:30"
对于模型 DateTimeField
S,这一区别使得写一个既有时区支持又没有时区支持的夹具是不可能的。
夹具生成 USE_TZ = False
或者在django 1.4之前,使用“naive”格式。如果您的项目包含这样的设备,那么在启用时区支持之后,您将看到 RuntimeWarning
当你装载它们的时候。要消除警告,必须将设备转换为“aware”格式。
您可以用以下方式再生固定装置 loaddata
然后 dumpdata
.或者,如果它们足够小,您可以编辑它们以添加与您的 TIME_ZONE
到每个序列化的日期时间。
我不需要多个时区。我应该启用时区支持吗?
是。启用时区支持后,Django使用更精确的本地时间模型。这可以保护您免受夏令时(DST)转换过程中出现的细微且不可重现的错误的影响。
当您启用时区支持时,您会遇到一些错误,因为您使用的是朴素的日期时间,而Django期望已知的日期时间。运行测试时会出现此类错误。您将很快了解如何避免无效操作。
另一方面,由于缺乏时区支持而导致的错误更难预防、诊断和修复。任何涉及计划任务或日期时间算法的内容都可能存在一些细微的错误,这些错误一年只会咬你一两次。
由于这些原因,在新项目中默认启用时区支持,除非您有非常好的理由不启用,否则您应该保留时区支持。
我启用了时区支持。我安全吗?
也许吧。你最好避免与DST相关的错误,但是你仍然可以通过不小心地将幼稚的约会时间转变成有意识的约会时间,而反之亦然。
如果您的应用程序连接到其他系统--例如,如果它查询Web服务--请确保正确指定了日期时间。为了安全地传输日期时间,它们的表示形式应该包括UTC偏移量,或者它们的值应该是UTC(或者两者都是!)。
最后,我们的日历系统包含有趣的边缘案例。例如,您不能总是从给定日期中直接减去一年:
>>> import datetime
>>> def one_year_before(value): # Wrong example.
... 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
为了正确实现这样的功能,您必须决定2012-02-29减去一年是2011-02-28还是2011-03-01,这取决于您的业务需求。
如何与以本地时间存储日期时间的数据库交互?
设置 TIME_ZONE
为该数据库选择适当的时区 DATABASES
设置。
这对于连接不支持时区且不由Django管理的数据库非常有用,当 USE_TZ
是 True
.
我的应用程序崩溃 TypeError: can't compare offset-naive
and offset-aware datetimes
-- what's wrong?
让我们通过比较一个朴素的和一个有意识的日期时间来重现这个错误:
>>> from django.utils import timezone
>>> aware = timezone.now()
>>> naive = timezone.make_naive(aware)
>>> naive == aware
Traceback (most recent call last):
...
TypeError: can't compare offset-naive and offset-aware datetimes
如果遇到此错误,很可能您的代码正在比较这两个方面:
django提供的日期时间——例如,从窗体或模型字段中读取的值。因为您启用了时区支持,所以它知道。
由代码生成的日期时间,这是幼稚的(或者您不会读这个)。
通常,正确的解决方案是将代码更改为使用已知的日期时间。
如果您正在编写一个可插拔的应用程序,该应用程序可以独立于 USE_TZ
你可能会发现 django.utils.timezone.now()
有用的。此函数将当前日期和时间作为原始日期时间返回,条件是 USE_TZ = False
作为一个意识到的日期时间 USE_TZ = True
. 你可以加减 datetime.timedelta
根据需要。
我看到很多 RuntimeWarning: DateTimeField received a naive datetime
(YYYY-MM-DD HH:MM:SS)
while time zone support is active
-- is that bad?
启用时区支持时,数据库层只希望从代码中接收已知的日期时间。此警告在收到幼稚的日期时间时发生。这表示您尚未完成时区支持代码的移植。请参阅 migration guide 有关此过程的提示。
同时,为了向后兼容,日期时间被认为是在默认时区,这通常是您所期望的。
now.date()
就是昨天!(或明天)
如果您一直使用幼稚的日期时间,那么您可能认为可以通过调用日期时间 date()
方法。你也认为 date
很像 datetime
但不太准确。
在支持时区的环境中,这一切都不成立:
>>> import datetime
>>> import zoneinfo
>>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
>>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
>>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
# This is the correct way to convert between time zones.
>>> new_york = 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=zoneinfo.ZoneInfo(key='Europe/Paris'))
>>> new_york
datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))
如本例所示,同一个日期时间具有不同的日期,具体取决于表示日期的时区。但真正的问题更为根本。
日期时间表示 时间点 . 它是绝对的:它不依赖任何东西。相反,约会是 日历概念 . 这是一段时间,其界限取决于考虑日期的时区。正如您所看到的,这两个概念本质上是不同的,将日期时间转换为日期并不是确定性操作。
这在实践中意味着什么?
通常,应避免转换 datetime
到 date
. 例如,您可以使用 date
模板筛选器,仅显示日期时间的日期部分。此筛选器将在格式化日期时间之前将其转换为当前时区,以确保结果正确显示。
如果您确实需要自己进行转换,则必须首先确保日期时间转换为适当的时区。通常,这将是当前时区:
>>> from django.utils import timezone
>>> timezone.activate(zoneinfo.ZoneInfo("Asia/Singapore"))
# For this example, we 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()
>>> local = paris.astimezone(current_tz)
>>> local
datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore'))
>>> local.date()
datetime.date(2012, 3, 3)
我得到一个错误 “``是否安装了数据库的时区定义?''
如果您使用的是MySQL,请参见 时区定义 有关加载时区定义的说明,请参阅MySQL注释的部分。
我有一个字符串 "2012-02-21 10:28:45"
我知道它在 "Europe/Helsinki"
时区。如何将其转换为一个感知的日期时间?
在这里,您需要创建所需的 ZoneInfo
实例并将其附加到朴素的DateTime:
>>> import zoneinfo
>>> from django.utils.dateparse import parse_datetime
>>> naive = parse_datetime("2012-02-21 10:28:45")
>>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
如何获取当前时区的本地时间?
好吧,第一个问题是,你真的需要吗?
当您与人类交互时,应该只使用本地时间,模板层提供 filters and tags 将日期时间转换为您选择的时区。
此外,python知道如何比较已知的日期时间,必要时考虑到UTC偏移。用UTC编写所有模型和视图代码要容易得多(甚至更快)。因此,在大多数情况下,用UTC返回的日期时间 django.utils.timezone.now()
就足够了。
不过,为了完整起见,如果您确实想要当前时区的本地时间,以下是获取该时间的方法:
>>> from django.utils import timezone
>>> timezone.localtime(timezone.now())
datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
在本例中,当前时区是 "Europe/Paris"
.
如何查看所有可用时区?
zoneinfo.available_timezones()
提供您的系统可用的IANA时区的所有有效密钥集。有关使用注意事项,请参阅文档。
7月 22, 2024