DST周围的日期和时区代码更改

时间:2014-09-24 16:16:15

标签: python datetime timezone pytz

我正在测试如何计算和显示日期(使用时区代码)围绕夏令时变化。

在英国,2014年3月30日凌晨1点,我们进入DST,从GMT到BST。时间从2014-03-30 00:59:59 GMT跳到2014-03-30 02:00:00 BST

我遇到了一个奇怪的问题,用下面的代码复制了这个:

import pytz
from datetime import datetime, time, timedelta

def is_dst(d, tz):
    assert d.tzinfo is None  # we want a naive datetime to localize
    return tz.localize(d).dst() != timedelta(0)

start_datetime = datetime(2014, 03, 30, 0, 0, 0)
tz = pytz.timezone('Europe/London')

# Increment using timedelta
print 'This doesn\'t work:'
d = start_datetime
for i in range(5):
    print str(d) + ' ' + tz.tzname(d, is_dst=is_dst(d, tz))
    d += timedelta(minutes=30)  # Add 30 minutes

# Increment by adding seconds to epoch
print 'This works:'
epoch = datetime.utcfromtimestamp(0)
timestamp = (start_datetime - epoch).total_seconds()
for i in range(5):
    d = datetime.fromtimestamp(timestamp)
    print str(d) + ' ' + tz.tzname(d, is_dst=is_dst(d, tz))
    timestamp += 30 * 60  # Add 30 minutes

输出结果为:

This doesn't work:
2014-03-30 00:00:00 GMT
2014-03-30 00:30:00 GMT
2014-03-30 01:00:00 GMT <- invalid time
2014-03-30 01:30:00 GMT <- invalid time
2014-03-30 02:00:00 BST
This works:
2014-03-30 00:00:00 GMT
2014-03-30 00:30:00 GMT
2014-03-30 02:00:00 BST
2014-03-30 02:30:00 BST
2014-03-30 03:00:00 BST

我在输出上标记了无效时间。那些时间不存在于挂钟上,2014年3月30日凌晨1点或凌晨1点半,所以我不确定为什么要显示它。

相同的过程,但以稍微不同的方式完成,产生正确的结果。这是为什么?

2 个答案:

答案 0 :(得分:1)

此处根据@Matt Johnson's answer修改了my comments

from datetime import datetime, timedelta
import pytz

tz = pytz.timezone('Europe/London')
#NOTE: is_dst=None asserts that the local time exists and unambiguous
start_datetime = tz.localize(datetime(2014, 03, 30, 0, 0, 0), is_dst=None)

# increment using timedelta
print 'This works:'
d = start_datetime.astimezone(pytz.utc) # use UTC to do arithmetic
for _ in range(5):
    local = d.astimezone(tz) # use local timezone for display only
    print("\t{:%F %T %Z%z}".format(local))
    d += timedelta(minutes=30) # works in UTC

# increment by adding seconds to epoch
print 'This works too:'
epoch = datetime(1970, 1, 1, tzinfo=pytz.utc)
timestamp = (start_datetime - epoch).total_seconds()
for i in range(5):
    local = datetime.fromtimestamp(timestamp, tz)
    print("\t{:%F %T %Z%z}".format(local))
    timestamp += 30 * 60  # add 30 minutes

答案 1 :(得分:0)

实际上,两个代码段都不正确。在我的机器上运行您的确切代码(在美国太平洋时区),底部返回:

2014-03-29 17:00:00 GMT
2014-03-29 17:30:00 GMT
2014-03-29 18:00:00 GMT
2014-03-29 18:30:00 GMT
2014-03-29 19:00:00 GMT

这是因为fromtimestamp在未指定时使用计算机的本地时区。如果我只是将fromtimestamp切换为utcfromtimestamp,它将使用一个天真的值 - 然后将结果放入正确的时区,但它会给出与第一部分相同的结果 - 显示两个无效次。

使用pytz normalize实例中的timezone方法可以解决问题。不幸的是,您将d保留为天真日期时间,因此您无法在不首先进行本地化的情况下对其进行规范化,并且在完成后您将不得不制作再次天真。这个可以工作,但会产生一些混乱的代码:

# Increment using timedelta
print 'This works:'
d = start_datetime
for i in range(5):
    print str(d) + ' ' + tz.tzname(d, is_dst=is_dst(d, tz))
    d += timedelta(minutes=30)  # Add 30 minutes
    d = tz.normalize(tz.localize(d)).replace(tzinfo=None)

# Increment by adding seconds to epoch
print 'This works too:'
epoch = datetime.utcfromtimestamp(0)
timestamp = (start_datetime - epoch).total_seconds()
for i in range(5):
    d = tz.normalize(datetime.fromtimestamp(timestamp, pytz.utc).astimezone(tz)).replace(tzinfo=None)
    print str(d) + ' ' + tz.tzname(d, is_dst=is_dst(d, tz))
    timestamp += 30 * 60  # Add 30 minutes

输出:

This works:
2014-03-30 00:00:00 GMT
2014-03-30 00:30:00 GMT
2014-03-30 02:00:00 BST
2014-03-30 02:30:00 BST
2014-03-30 03:00:00 BST
This works too:
2014-03-30 00:00:00 GMT
2014-03-30 00:30:00 GMT
2014-03-30 02:00:00 BST
2014-03-30 02:30:00 BST
2014-03-30 03:00:00 BST

当然,通过早期使用感知日期时间可以简化整个事情:

import pytz
from datetime import datetime, time, timedelta

tz = pytz.timezone('Europe/London')
start_datetime = tz.localize(datetime(2014, 03, 30, 0, 0, 0))

# Increment using timedelta
print 'This works:'
d = start_datetime
for i in range(5):
    print str(d) + ' ' + d.tzname()
    d = tz.normalize(d + timedelta(minutes=30))  # Add 30 minutes

# Increment by adding seconds to epoch
print 'This works too:'
epoch = datetime.fromtimestamp(0, pytz.utc)
timestamp = (start_datetime - epoch).total_seconds()
for i in range(5):
    d = datetime.fromtimestamp(timestamp, pytz.utc).astimezone(tz)
    print str(d) + ' ' + d.tzname()
    timestamp += 30 * 60  # Add 30 minutes

输出:

This works:
2014-03-30 00:00:00+00:00 GMT
2014-03-30 00:30:00+00:00 GMT
2014-03-30 02:00:00+01:00 BST
2014-03-30 02:30:00+01:00 BST
2014-03-30 03:00:00+01:00 BST
This works too:
2014-03-30 00:00:00+00:00 GMT
2014-03-30 00:30:00+00:00 GMT
2014-03-30 02:00:00+01:00 BST
2014-03-30 02:30:00+01:00 BST
2014-03-30 03:00:00+01:00 BST

请注意,您不再需要is_dst功能,因为您现在可以直接从知道的tzname实例获取datetime