我正在测试如何计算和显示日期(使用时区代码)围绕夏令时变化。
在英国,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点半,所以我不确定为什么要显示它。
相同的过程,但以稍微不同的方式完成,产生正确的结果。这是为什么?
答案 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
。