【问题标题】:Datetime localization with python/django使用 python/django 进行日期时间本地化
【发布时间】:2015-12-14 17:44:08
【问题描述】:

我正在尝试解析 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.timedelta 操作简单地添加一个小时?
  • 最后,我希望有正确的 UTC 时间。现在离开一个小时(在节假日期间为两个小时)可能是一种方法。不过我还没看过。我想知道是否还有其他方法。我尝试了例如 timezone.activate() 和 timezone.deactivate() ,它们似乎以正确的方式更改了 current_timezone,但这并没有解决问题。
  • 您可以让日期时间感知,或者如果它已经感知但错误,则更改时区。

标签: python django datetime feedparser


【解决方案1】:

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

from datetime import datetime

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

其中utc 是一个tzinfo 对象,例如datetime.timezone.utcpytz.utc,或者只是您的custom tzinfo (for older python versions)

您不应将 utc 时间传递给期望当地时间的 mktime()。同样的错误:Have a correct datetime with correct timezone

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

【讨论】:

    【解决方案2】:

    您是否尝试过使用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>)
    

    【讨论】:

    • 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- 2016-09-21 09-21 13:00:00 13:00:00+00:00 ... 这是 utcfromtimestamp() 的输出。时区已更改,但时间仍然不正确。
    • 第二种解决方案可以工作。我唯一担心的是有许多不同的日期格式。从我们目前所遇到的情况来看,feedparser 对其中的任何一个都没有问题。我想知道您建议的解析器是否同样有效。您是否将它用于许多不同的日期格式?
    • @apiljic:使用 feedparser 解析输入时间字符串(_parsed 属性)。 dateutil 接受的输入时间格式太多,因此可能会静默返回错误结果。
    【解决方案3】:

    使用

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

    Feedparser 可以解析大量的日期格式,你可以找到它们here

    正如您在 feedparser/feedparser/datetimes/__init__.py 中看到的,来自 Feedparser _parse_date 的内置函数执行以下操作:

    在 GMT 中将各种日期格式解析为 9 元组

    这意味着在 parsed_entry.published_parsed 中,您在 GMT 时区有一个 time.struct_time 对象。

    当您使用将其转换为 datetime 对象时

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

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

    您需要用以下内容替换该转换,记住 Feedparser 返回 GMT 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)))
    

    只要保持一致,使用fromtimestamputcfromtimestamp 都没关系。如果你使用fromtimestamp,你需要告诉Python你创建的datetime对象有本地时区。假设您在欧洲/柏林,这也可以:

    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'])
    

    您可以检查以下是否返回True

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

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

    当 USE_TZ 为 True 时,这是 Django 用于在模板中显示日期时间并解释在表单中输入的日期时间的默认时区。

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

    【讨论】:

    • 没有必要复杂。这是simpler solution
    • 我同意,当您需要考虑 dst 标志时,您需要这种复杂性,这是本地时间(即您使用 mktime)的情况,而不是 UTC,它没有.
    • 如果时间不是UTC,那么代码不仅复杂;这是错误的。
    • local_tz.localize(datetime.fromtimestamp(mktime(x))) 有什么问题?
    • 有几处错误,但我说的是您回答中的第一个代码示例(使用timegm())。
    猜你喜欢
    • 2011-07-01
    • 2016-12-18
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多