【问题标题】:Difference of two timedelta objects, with timezones具有时区的两个 timedelta 对象的差异
【发布时间】:2016-08-31 12:04:06
【问题描述】:

我想计算一个日期间隔中有多少小时:例如“2014.03.29-30”应该给出 47,因为夏令时。

我的方法是制作两个日期时间对象,在下面的例子中:

datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
datetime.datetime(2014, 3, 30, 23, 59, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
return (date2-date1) + timedelta(minutes=1)

但是,它给出了“2 天,0:00:00”,这是不正确的。如何制作一个考虑时区和 dst 的 timedelta 对象?另外,如果整个问题有更简单的解决方案,我愿意接受。

谢谢!

【问题讨论】:

    标签: python datetime timezone timedelta tzinfo


    【解决方案1】:

    在 1901-12-13 20:45:52 UTC 之前,'Europe/Budapest' 时区是 LMT+1:16:00 STD。 目前,截至 2016 年 5 月 5 日,'Europe/Budapest' 时区为 CET+2:00:00 DST。

    如果您使用 pytz 的 localize method,那么 pytz 将为 'Europe/Budapest' 选择适合给定的原始日期时间的时区(utcoffset 和 dstoffset):

    import datetime as DT
    import pytz
    
    tzone = pytz.timezone('Europe/Budapest')
    date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
    # datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' CET+1:00:00 STD>)
    

    相比之下,如果您将tzinfo=tzone 直接提供给datetime.datetime,如下所示:

    wrong_date1 = datetime.datetime(2014, 3, 29, 0, 0, tzinfo=tzone)
    # datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
    

    那么datetime.datetime 错误地选择了与'Europe/Budapest' 关联的第一个时区 无论该时区是否是 2014 年 3 月 29 日生效的时区 .

    因此,在使用 pytz 时,请始终使用tzone.localize 来使天真的日期时间时区感知:

    import datetime as DT
    import pytz
    tzone = pytz.timezone('Europe/Budapest')
    date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
    date2 = tzone.localize(DT.datetime(2014, 3, 30, 23, 59), is_dst=None)
    print(((date2-date1) + DT.timedelta(minutes=1)).total_seconds()/3600.)
    # 47.0
    

    Do not use tzinfo=tzone 除非 tzonepytz.utc(或在其历史上始终相同的时区。)


    1901-12-13 20:45:52 UTC 的日期从何而来?

    您可以使用其 tzone._utc_transition_timestzone._transition_info 私有属性查看 pytz 时区的 utc 转换时间(以及相关的转换信息):

    In [43]: [(utcdate, utcoffset, dstoffset, tzabbrev) for utcdate, (utcoffset, dstoffset, tzabbrev) in zip(tzone._utc_transition_times, tzone._transition_info)][:2]
    Out[43]: 
    [(datetime.datetime(1, 1, 1, 0, 0),
      datetime.timedelta(0, 4560),
      datetime.timedelta(0),
      'LMT'),
     (datetime.datetime(1901, 12, 13, 20, 45, 52),
      datetime.timedelta(0, 3600),
      datetime.timedelta(0),
      'CET')]
    

    这表明从日期1-1-1 UTC1901-12-13 20:45:52 UTC,时区缩写为LMT,utcoffset 为4560 秒,等于1 小时16 分钟:

    In [47]: print(DT.timedelta(0, 4560))
    1:16:00
    

    因此与'Europe/Budapest' 关联的第一个时区是LMT+1:16:00 STD

    【讨论】:

    • 谢谢!最后一件事:is_dst=None 部分是做什么的?我不小心跳过了它,但它也可以正常工作。
    • is_dst=None 告诉 tzone.localize 在夏令时结束时的模棱两可的时间引发 AmbiguousTimeError ——“回退”导致相同的幼稚时间出现两次。否则它会做适当的事情。它是出于“非常谨慎”而使用的,作为一种良好的一般习惯,即使它在这里没有效果。默认情况下,is_dst=False,这意味着 tzone.localize 将通过假设来解析不明确的时间,即天真日期时间指的是夏令时无效时的日期时间。
    • is_dst=None 也可以在夏令时开始时引发NonExistentTimeError。例如,如果pacific = pytz.timezone('US/Pacific')date = DT.datetime(2008, 3, 9, 2, 0, 0),则pacific.localize(date, is_dst=None) 引发NonExistentTimeError,即使pacific.normalize(pacific.localize(date, is_dst=True))(或与is_dst=False 相同)返回一个日期(1 点或3 点,但永远不会是 2 点钟)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-29
    • 1970-01-01
    • 2017-03-08
    • 2012-01-15
    • 1970-01-01
    相关资源
    最近更新 更多