我不想使用pytz
库,因为我正在进行的项目需要文书工作来引入依赖关系。如果我能在没有第三方图书馆的情况下实现这一点,我会更高兴。
当日期为夏令时时,我在转换CET和UTC之间的日期时遇到问题。这一小时与我预期的不同:
>>> print from_cet_to_utc(year=2017, month=7, day=24, hour=10, minute=30)
2017-07-24T09:30Z
2017-07-24T08:30Z # expected
CET比UTC提前一个小时,在夏季则提前两个小时。所以我预计仲夏中欧的10:30实际上是UTC的8:30。
功能是:
from datetime import datetime, tzinfo, timedelta
def from_cet_to_utc(year, month, day, hour, minute):
cet = datetime(year, month, day, hour, minute, tzinfo=CET())
utc = cet.astimezone(tz=UTC())
return '{:%Y-%m-%d:T%H:%MZ}'.format(utc)
我使用两个时区信息对象:
class CET(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=1)
def dst(self, dt):
return timedelta(hours=2)
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)
def dst(self, dt):
return timedelta(0)
只是对@Sergey的回答的补充。
要知道何时应该应用夏令时,您应该获取夏令时开始和结束的日期/时间的历史数据,并检查是否必须将其应用于指定的datetime
。您可以从IANA数据库中获取这些信息,也可以在许多在线资源中查阅,如时间和日期网站。
另一个细节是CET是一个不明确的名称,因为它被多个时区使用。实时区域名称使用IANA数据库定义的名称(始终采用Region/City
格式,如Europe/Paris
或Europe/Berlin
)。
间隙和重叠
我将以Europe/Berlin
时区为例。今年(2017年),柏林夏令时于3月26日开始:凌晨2点,时钟向前移动1小时至凌晨3点,偏移量从+01:00
变为+02:00
。
你也可以认为时钟直接从凌晨1点59分跳到了凌晨3点,这意味着在那个时区,凌晨2点到2点59分之间的所有当地时间都不存在。这被称为间隔,您应该检查这种情况(一种常见的方法是将凌晨2点调整为下一个有效小时——在这种情况下,夏令时为凌晨3点)。
在同一时区,2017年夏令时将于10月29日结束:凌晨3点,时钟将向后移动1小时至凌晨2点,偏移量将从+02:00
变为+01:00
。
这意味着凌晨2点到凌晨2点59分之间的所有本地时间将存在两次:一次在夏令时(+02:00
),一次在非夏令时(+01:00
)。这被称为重叠,在这种情况下创建datetime
时,您必须决定要创建哪一个(应该是夏令时中的凌晨2点还是非夏令时偏移?由您决定!)。
PS:现在每个使用CET的欧洲国家都在不同的年份采用它,所以如果你在处理旧日期,你也必须考虑这一点。
另一个细节是,DST是由政府定义的,不能保证规则会永远这样,你必须相应地更新规则——使用pytz
的另一个优点是,它的更新是从IANA发布的版本中生成的,并在PyPI中发布(感谢@Matt Johnson提供此信息)。
你确定文书工作比手工编码这些规则更糟糕吗?只要检查一下如何阅读IANA tz文件,也许你会改变主意。
您应该仔细阅读tzinfo手册,正如那里所解释的那样。
首先,dst()
通常应返回timedelta(0)
或timedelta(hours=1)
,从不超过2小时,也很少介于两者之间(例如30分钟)。这只是相对于正常TZ偏移的dst偏移,而不是dst模式中的完整TZ偏移。
其次,utcoffset()
必须已经包含dst校正。dst()
方法本身也用于手册中描述的某些情况(例如,用于检测dst是否有效),但除非您这样做,否则它不会自动应用于TZ偏移。
你的代码应该是这样的:
from datetime import datetime, tzinfo, timedelta
class CET(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=1) + self.dst(dt)
def dst(self, dt):
dston = datetime(year=dt.year, month=3, day=20)
dstoff = datetime(year=dt.year, month=10, day=20)
if dston <= dt.replace(tzinfo=None) < dstoff:
return timedelta(hours=1)
else:
return timedelta(0)
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)
def dst(self, dt):
return timedelta(0)
def from_cet_to_utc(year, month, day, hour, minute):
cet = datetime(year, month, day, hour, minute, tzinfo=CET())
utc = cet.astimezone(tz=UTC())
return '{:%Y-%m-%d:T%H:%MZ}'.format(utc)
print from_cet_to_utc(year=2017, month=7, day=24, hour=10, minute=30)
# 2017-07-24:T08:30Z
在这里,我勇敢地假设DST有效期为20.03.YYYY-19.10.YYYY,其中YYYY是日期/日期时间对象的年份。
通常这种on&休息日期要复杂得多:这些变化只发生在周日晚上,有时发生在一个月的最后一个周日,而且随着年份的不同而变化,有时发生国家主权的变化(边界线也会随着年份的变化而变化),夏令时和时区每隔几年就会变化一次(你好,俄罗斯)。