如何在 CEST 和 UTC 时区之间转换日期时间对象

How to convert a datetime object between CEST and UTC timezones

我不想使用 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 早 1 小时,夏令时早 2 小时。所以我预计仲夏的中欧 10:30 实际上是 8:30 UTC。

函数是:

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)

你应该仔细阅读tzinfo manual,因为那里有解释。

首先,dst() 通常 return 一个 timedelta(0)timedelta(hours=1),从不超过 2 小时,很少介于两者之间(例如 30 分钟)。这只是相对于正常 TZ 偏移的 dst 偏移,而不是 dst 模式下的完整 TZ 偏移。

其次,utcoffset() 必须已经包含夏令时校正。 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 是 date/datetime 对象的年份。

通常这种开启和关闭日期的逻辑要复杂得多:轮班仅在星期日晚上发生,有时在每月的最后一个星期日,并且在年份之间变化,有时在各州的主权之间变化(这些年来边界线也在发生变化),并且夏令时法和时区每隔几年就会发生变化(你好,俄罗斯)。

只是补充 .

要知道什么时候应该应用夏令时,你应该获取date/times夏令时开始和结束的历史数据,并检查是否必须应用到指定的datetime.您可以从 IANA database or consult in many online sources, such as timeanddate website.

获取这些信息

另一个细节是 CETambiguous name, because it's used by more than one timezone. Real timezones names use the ones defined by IANA database(格式总是 Region/City,如 Europe/ParisEurope/Berlin ).


差距和重叠

我将以 Europe/Berlin timezone 为例。今年(2017年),柏林夏令时开始于3月26日:凌晨2点,时钟向前拨动1小时至凌晨3点,并且偏移量从 +01:00 更改为 +02:00

你也可以认为时钟从1:59AM直接跳到了凌晨3点,这意味着当天凌晨2点到2:59AM之间的所有本地时间都不存在,在那个时区。这称为间隙,您应该检查这种情况(一种常见的方法是将凌晨 2 点调整为下一个有效时间 - 在本例中为夏令时凌晨 3 点)。

在同一时区,2017 年夏令时将在 10 月 29 日:凌晨 3 点,时钟将后移 1 小时到凌晨 2 点,偏移量将从 +02:00 变为 +01:00

这意味着凌晨 2 点到 2:59 AM 之间的所有本地时间将存在两次:一次是夏令时 (+02:00) 一次是非夏令时 (+01:00)。这称为重叠,在创建属于这种情况的 datetime 时,您必须决定选择创建哪一个( 应该是夏令时凌晨 2 点还是非夏令时偏移?你来决定!).


PS: 现在每个使用 CET 的欧洲国家都在不同的年份采用它,所以如果你要处理旧日期,你也必须考虑到这一点。

另一个细节是 DST 是由政府定义的,不能保证规则永远都是这样,你必须相应地更新规则 - 使用 pytz 的另一个好处,因为它的更新是从 IANA 发布和 published in PyPI (thanks @Matt Johnson for ).

生成的

您确定文书工作比手工编写这些规则更糟糕吗?只需检查 how to read IANA tz files,也许您会改变主意。