0

我的用例只是将日期作为 UTC 存储在数据库中,并在计划和查看计划时间时为用户在美国/中部时间计划实现预期结果。以下不一致的行为让我有些头疼,并且在执行某些更新时,预定的日期偏离了一个小时。

是什么导致以下不一致的行为?我可以指望我在尝试在最后两行中达到理智时观察到的行为吗?有没有更好的方法,即。我没有正确使用日期时间吗?坦率地说,我有点困惑,所以任何帮助表示赞赏!

# Instantiate a datetime in December and April and make them timezone aware
decutc = datetime.datetime(2013, 12, 12, 12, 12, 12).replace(tzinfo=pytz.UTC)
aprutc = datetime.datetime(2013, 4, 12, 12, 12, 12,).replace(tzinfo=pytz.UTC)

# Convert both to US/Central, April is STD and December is DST as expected
# NOTE is STD
decutc.astimezone(pytz.timezone('US/Central'))
Out[164]: datetime.datetime(2013, 12, 12, 6, 12, 12, tzinfo=<DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>)
# NOTE is DST
aprutc.astimezone(pytz.timezone('US/Central'))
Out[165]: datetime.datetime(2013, 4, 12, 7, 12, 12, tzinfo=<DstTzInfo 'US/Central' CDT-1 day, 19:00:00 DST>)
# Move an aware datetime to another month with a different daylight savings time
# NOTE This one DOES NOT change from STD to DST
decutc.astimezone(pytz.timezone('US/Central')).replace(month=4).astimezone(
    pytz.timezone('US/Central'))
Out[166]: datetime.datetime(2013, 4, 12, 6, 12, 12, tzinfo=<DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>)
# NOTE This one DOES change from DST to STD
aprutc.astimezone(pytz.timezone('US/Central')).replace(month=12).astimezone(
    pytz.timezone('US/Central'))
Out[167]: datetime.datetime(2013, 12, 12, 6, 12, 12, tzinfo=<DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>)

为了实现一致的行为,我最终做了以下事情:

# NOTE correctly goes from STD to DST
decutc.astimezone(pytz.timezone('US/Central')).replace(month=4).astimezone(
    pytz.timezone('US/Central')).astimezone(pytz.UTC).astimezone(pytz.timezone('US/Central'))
Out[172]: datetime.datetime(2013, 4, 12, 7, 12, 12, tzinfo=<DstTzInfo 'US/Central' CDT-1 day, 19:00:00 DST>)

# NOTE correctly goes from DST to STD
aprutc.astimezone(pytz.timezone('US/Central')).replace(month=12).astimezone(
    pytz.timezone('US/Central')).astimezone(pytz.UTC).astimezone(pytz.timezone('US/Central'))
Out[170]: datetime.datetime(2013, 12, 12, 6, 12, 12, tzinfo=<DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>)
4

2 回答 2

2

通常我可以指望 Python 和 Python 库在开箱即用时保持一致:),但pytz选择提供“规范化”功能,我们作为开发人员在进行时区转换时需要注意这一点。问题在于问题不一致,它迫使我们(开发人员)自己决定如何处理所有的疯狂,导致库作者无法为我们做出决定。

http://pytz.sourceforge.net/#localized-times-and-date-arithmetic似乎是必须阅读的。

请注意,此库与用于 tzinfo 实现的文档化 Python API 不同;如果您想创建本地挂钟时间,您需要使用本文档中记录的 localize() 方法。此外,如果您对跨越 DST 边界的本地时间执行日期算术,则结果可能位于不正确的时区(即,从 2002-10-27 1:00 EST 减去 1 分钟,您会得到 2002-10-27 0: 59 EST 而不是正确的 2002-10-27 1:59 EDT)。提供了一个 normalize() 方法来纠正这个问题。不幸的是,如果不修改 Python 日期时间实现,就无法解决这些问题。

这是非常不合常规的,因为有不止一种方法可以做有效的事情。IE。对于 UTCnormalize并且localize不是必需的,我们可以观察到工作正常,直到我们在使用其他时区时真正跨越 DST - STD 边界,但在这种情况下,我不确定替代方案会更好(破坏使用标准日期时间 API)。我当然更喜欢堆栈跟踪,而不是默默地糟糕的 astimezone 转换。

我真的把错误归咎于datetime文档:

http://docs.python.org/2/library/datetime.html#datetime.datetime.astimezone

其中提到了 pytz 并且这是获取一些 tzinfo 对象的地方,但没有提到您最好阅读他们的文档以获取警告。

于 2013-04-09T17:13:57.290 回答
0

我能够重现您的结果并获得不一致的行为。为了使其保持一致,我所要做的就是删除.astimezone(pytz.timezone('US/Central'))您添加到转换调用上的调用。这样做之后,结果是一致的,即从 STD 到 DST不会改变,反之亦然。

换句话说,改变:

decutc.astimezone(pytz.timezone('US/Central')).replace(month=4).astimezone(
    pytz.timezone('US/Central'))
aprutc.astimezone(pytz.timezone('US/Central')).replace(month=12).astimezone(
    pytz.timezone('US/Central'))

呼吁只是:

decutc.astimezone(pytz.timezone('US/Central')).replace(month=4)
aprutc.astimezone(pytz.timezone('US/Central')).replace(month=12)

更新
要在更改日期时获得正确且一致的 STD <--> DST 转换,只需将调用放在调用.astimezone(pytz.timezone('US/Central')之后.replace()如下所示:

decutc.replace(month=4).astimezone(pytz.timezone('US/Central'))
aprutc.replace(month=12).astimezone(pytz.timezone('US/Central'))

如果您考虑一下,这是有道理的,因为发生的事情是首先更改 UTC 日期时间的月份(不遵守夏令时),然后将中间结果转换为一个时区。

因此,总而言之,我建议您将所有日期时间存储在 UTC 中,在该时区进行所有操作,并且仅在绝对必要时转换为特定的本地时间。

于 2013-04-09T17:20:48.353 回答