python/django 日期时间本地化

Datetime localization with python/django

我正在尝试解析 RSS 提要。提要中的条目具有日期元素,例如:

<dc:date>2016-09-21T16:00:00+02:00</dc:date>

使用 feedparser,我尝试做:

published_time = datetime.fromtimestamp(mktime(entry.published_parsed))

但问题是我在数据库中存储的时间似乎有误。在这种特殊情况下,日期时间存储为:

2016-09-21 13:00:00

...当我期望 14:00 - 正确的 UTC 时间。

我假设问题出在我们的 django 设置中,我们有:

TIME_ZONE = 'Europe/Berlin'

因为当我切换到:

TIME_ZONE = 'UTC'

...数据时间存储为正确的 UTC 时间:

2016-09-21 14:00:00

有什么方法可以保持 django 设置不变,但又能正确解析和存储此日期时间,而不受 django 时区设置的影响?

编辑: 也许这样更清楚...

print entry.published_parsed
published_time = datetime.fromtimestamp(mktime(entry.published_parsed))
print published_time
localized_time = pytz.timezone(settings.TIME_ZONE).localize(published_time, is_dst=None)
print localized_time

time.struct_time(tm_year=2016, tm_mon=9, tm_mday=21, tm_hour=14, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=265, tm_isdst=0)
2016-09-21 15:00:00
2016-09-21 15:00:00+02:00

您是否尝试过使用 datetime.utcfromtimestamp() 而不是 datetime.fromtimestamp()

作为辅助解决方案,您可以获得未解析的数据(我相信它可以作为 entry.published 获得?),只需使用 python-dateutil 解析字符串,然后将其转换为 pytz.utc 这样的时区。

>>> import pytz
>>> from dateutil import parser
>>> dt = parser.parse('2016-09-21T16:00:00+02:00')
>>> dt
datetime.datetime(2016, 9, 21, 16, 0, tzinfo=tzoffset(None, 7200))
>>> dt.astimezone(pytz.utc)
datetime.datetime(2016, 9, 21, 14, 0, tzinfo=<UTC>)

使用

published_time = pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))

Feedparser 可以解析多种日期格式,您可以找到它们 here

如您在 feedparser/feedparser/datetimes/__init__.py 中所见,Feedparser _parse_date 的内置函数执行以下操作:

Parses a variety of date formats into a 9-tuple in GMT

这意味着在 parsed_entry.published_parsed 你有一个 GMT 时区的 time.struct_time 对象。

当您使用

将其转换为 datetime 对象时
published_time = datetime.fromtimestamp(mktime(parsed_entry.published_parsed))

问题是mktime假设传递的元组是当地时间,其实不是,是GMT/UTC!除此之外,您在转换结束时没有正确本地化 datetime 对象。

您需要用以下内容替换该转换,记住 Feedparser returns 格林威治标准时间 struct_time,并将其本地化为您喜欢的时区(为简单起见采用 UTC)。

  • 你使用calendar.timegm,它给出了作为参数传递的纪元和日期之间的秒数,假设传递的对象在UTC/GMT中(我们从Feedparser知道它是)
  • 您使用 utcfromtimestamp 获得一个天真的 datetime 对象(我们知道它表示 UTC 中的日期时间,但 Python 目前还没有)
  • 使用 pytz.utc.localize 您可以在 UTC 中正确定位 datetime 对象。

示例:

import calendar
from datetime import datetime
import pytz
localized_dt = pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))

只要一致,用fromtimestamp还是utcfromtimestamp都无所谓。如果您使用 fromtimestamp,您需要告诉 Python 您创建的 datetime 对象具有本地时区。假设你在 Europe/Berlin,这也可以:

pytz.timezone('Europe/Berlin').localize(datetime.fromtimestamp(calendar.timegm(parsed_entry.published_parsed)))

如果 parsed_entry.published_parsed 也在本地时区,则必须使用 mktime 代替 calendar.timegm

作为替代方案,您可以自己解析从 Feedparser parsed_entry['published']

获得的数据字符串
from dateutil import parser
localized_dt = parser.parse(parsed_entry['published'])

您可以检查以下 returns True:

parser.parse(parsed_entry['published']) == pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))

Django TIME_ZONE 设置实际上并不重要,因为它仅用于可视化目的或自动转换原始日期时间。

When USE_TZ is True, this is the default time zone that Django will use to display datetimes in templates and to interpret datetimes entered in forms.

重要的是始终使用正确本地化的日期时间,无论使用哪个时区。只要它们不是原始格式,它们就会被 Django 正确处理。

feedparser 的 entry.published_parsed 始终是一个 utc 时间元组,无论输入时间字符串是什么。要获得时区感知 datetime 对象:

from datetime import datetime

utc_time = datetime(*entry.published_parsed[:6], tzinfo=utc)

其中 utc 是一个 tzinfo 对象,例如 datetime.timezone.utc, pytz.utc, or just your custom tzinfo (for older python versions)

您不应将 utc 时间传递给需要当地时间的 mktime()。同样的错误:.

确保 USE_TZ=True 以便 django 在任何地方都使用 aware datetime 对象。给定一个时区感知的日期时间对象,django 应该将它正确地保存到 db,无论你的 TIME_ZONE or timezone.get_current_timezone() are 是什么。