如何获取给定时区的“午夜”UTC 时间?

Posted

技术标签:

【中文标题】如何获取给定时区的“午夜”UTC 时间?【英文标题】:How do I get the UTC time of "midnight" for a given timezone? 【发布时间】:2010-09-27 06:23:15 【问题描述】:

我现在能想到的最好的就是这个怪物:

>>> datetime.utcnow() \
...   .replace(tzinfo=pytz.UTC) \
...   .astimezone(pytz.timezone("Australia/Melbourne")) \
...   .replace(hour=0,minute=0,second=0,microsecond=0) \
...   .astimezone(pytz.UTC) \
...   .replace(tzinfo=None)
datetime.datetime(2008, 12, 16, 13, 0)

即,用英语,获取当前时间(UTC),将其转换为其他时区,将时间设置为午夜,然后转换回 UTC。

我不只是使用 now() 或 localtime(),因为这将使用服务器的时区,而不是用户的时区。

我不禁觉得我错过了什么,有什么想法吗?

【问题讨论】:

【参考方案1】:

值得注意的是,我们可以调整@jfs 给出的答案以找到明天的午夜或昨天的午夜等。诀窍是在感知时区添加一定天数。这是可行的,因为虽然这通常会增加 24 小时,但有时可能会根据 DST 问题增加 23 或 25。

from datetime import datetime, time, timedelta
import pytz

def midnight_UTC(offset):

    # Construct a timezone object
    tz = pytz.timezone('Australia/Melbourne')

    # Work out today/now as a timezone-aware datetime
    today = datetime.now(tz)

    # Adjust by the offset. Note that that adding 1 day might actually move us 23 or 25
    # hours into the future, depending on daylight savings. This works because the today
    # variable is timezone aware
    target_day = today + timedelta(days=1) * offset

    # Discard hours, minutes, seconds and microseconds
    midnight_aware = tz.localize(
        datetime.combine(target_day, time(0, 0, 0, 0)), is_dst=None)

    # Convert to UTC
    midnight_UTC = midnight_aware.astimezone(pytz.utc)

    return midnight_UTC

print("The UTC time of the previous midnight is:", midnight_UTC(0))
print("The UTC time of the upcoming midnight is:", midnight_UTC(1))

【讨论】:

【参考方案2】:

使用 dateutil.tz 比使用 pytz 更直接:

>>>import datetime
>>>import dateutil.tz
>>>midnight=(datetime.datetime
             .now(dateutil.tz.gettz('Australia/Melbourne'))
             .replace(hour=0, minute=0, second=0, microsecond=0)
             .astimezone(dateutil.tz.tzutc()))
>>>print(midnight)
2019-04-26 14:00:00+00:00

tzinfo documentation 从 Python 3.6 开始推荐使用 dateutil.tz。 dateutil.tz 中的 tzinfo 对象对 DST 等异常没有问题,无需 pytz 的本地化功能。使用来自 user3850 的示例:

>>> now = (datetime.datetime(2012, 4, 1, 5,  
...         tzinfo = dateutil.tz.gettz('Australia/Melbourne'))) 
>>> print(now.replace(hour = 0).astimezone(dateutil.tz.tzutc()))
2012-03-31 13:00:00+00:00

【讨论】:

【参考方案3】:

如果你这样做,我认为你可以减少一些方法调用:

>>> from datetime import datetime
>>> datetime.now(pytz.timezone("Australia/Melbourne")) \
            .replace(hour=0, minute=0, second=0, microsecond=0) \
            .astimezone(pytz.utc)

但是……在您的代码中存在比美学更大的问题:它会在切换到夏令时的当天给出错误的结果。

原因是 datetime 构造函数和 replace() 都没有考虑 DST 更改。

例如:

>>> now = datetime(2012, 4, 1, 5, 0, 0, 0, tzinfo=pytz.timezone("Australia/Melbourne"))
>>> print now
2012-04-01 05:00:00+10:00
>>> print now.replace(hour=0)
2012-04-01 00:00:00+10:00 # wrong! midnight was at 2012-04-01 00:00:00+11:00
>>> print datetime(2012, 3, 1, 0, 0, 0, 0, tzinfo=tz)
2012-03-01 00:00:00+10:00 # wrong again!

但是,tz.localize() 的文档指出:

这个方法应该用于构建本地时间,而不是 而不是将 tzinfo 参数传递给 datetime 构造函数。

这样,你的问题就这样解决了:

>>> import pytz
>>> from datetime import datetime, date, time

>>> tz = pytz.timezone("Australia/Melbourne")
>>> the_date = date(2012, 4, 1) # use date.today() here

>>> midnight_without_tzinfo = datetime.combine(the_date, time())
>>> print midnight_without_tzinfo
2012-04-01 00:00:00

>>> midnight_with_tzinfo = tz.localize(midnight_without_tzinfo)
>>> print midnight_with_tzinfo
2012-04-01 00:00:00+11:00

>>> print midnight_with_tzinfo.astimezone(pytz.utc)
2012-03-31 13:00:00+00:00

但不保证 1582 年之前的日期。

【讨论】:

它似乎忽略了夏令时。在某个地方.localize()/.normalize() 可能是必要的。 @J.F.Sebastian:有趣!你确定吗?你有一个例子吗?完全有可能。 @hop:是的。您的代码在 2012 年 4 月 1 日失败。请参阅 my answer 当前版本在巴西/东部时区为 2014/10/19 静默返回错误结果,因为您没有像 my answer 那样使用 is_dst=None 至第一条评论:没有属性毫秒,只有微秒【参考方案4】:

@hop's answer 在从夏令时 (DST) 过渡的那天出错,例如 2012 年 4 月 1 日。要修复它,可以使用 tz.localize()

tz = pytz.timezone("Australia/Melbourne")
today = datetime.now(tz).date()
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)
utc_dt = midnight.astimezone(pytz.utc)        

与 cmets 相同:

#!/usr/bin/env python
from datetime import datetime, time
import pytz # pip instal pytz

tz = pytz.timezone("Australia/Melbourne") # choose timezone

# 1. get correct date for the midnight using given timezone.
today = datetime.now(tz).date()

# 2. get midnight in the correct timezone (taking into account DST)
#NOTE: tzinfo=None and tz.localize()
# assert that there is no dst transition at midnight (`is_dst=None`)
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)

# 3. convert to UTC (no need to call `utc.normalize()` due to UTC has no 
#    DST transitions)
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
print midnight.astimezone(pytz.utc).strftime(fmt)

【讨论】:

我有点困惑。 DST 切换发生在凌晨 3 点,因此当天的午夜应该仍然是 14:00 UTC,而不是 13:00。没有? @hop: 将 2012 年 3 月 31 日 13:00 UTC 转换为墨尔本时区并亲自查看(它仍然是 +11 时区(DST),而不是 +10(标准)) time()time(0, 0) 相同,但更短【参考方案5】:

每个时区都有一个数字,例如 US/Central = -6。这被定义为从 UTC 开始的小时偏移量。由于 0000 是午夜,您可以简单地使用此偏移量来查找任何时区的时间,当它是午夜 UTC 时。要访问它,我相信您可以使用

 time.timezone

根据The Python Docs,time.timezone实际上给出了这个数字的负值:

时间.时区

本地(非 DST)时区的偏移量,以 UTC 以西的秒数为单位(西欧大部分地区为负数,美国为正数,英国为零)。

因此,如果该数字为正数(即,如果是芝加哥的午夜(其时区值为 +6),那么您只需使用该数字以小时为单位,那么它就是 6000 = UTC 上午 6 点)。

如果数字是负数,则从 24 中减去。例如,柏林会给出 -1,因此 24 - 1 => 2300 = 晚上 11 点。

【讨论】:

我认为你在正确的轨道上,但你怎么知道从什么日期开始?即几个小时前,墨尔本是第 17 次,而 UTC 仍然是第 16 次。 问题是关于本地午夜的。日期关系由时区的 UTC 偏移量固定——在当地午夜。 手动添加/减去 tz 差异可能会在切换到夏令时出现问题【参考方案6】:

设置 TZ 环境变量会修改 Python 的日期和时间函数使用的时区。

>>> time.gmtime()
(2008, 12, 17, 1, 16, 46, 2, 352, 0)
>>> time.localtime()
(2008, 12, 16, 20, 16, 47, 1, 351, 0)
>>> os.environ['TZ']='Australia/Melbourne'
>>> time.localtime()
(2008, 12, 17, 12, 16, 53, 2, 352, 1)

【讨论】:

除了不想使用 TZ 变量来控制它之外,它实际上并没有告诉我如何找到午夜,只是当前时间。

以上是关于如何获取给定时区的“午夜”UTC 时间?的主要内容,如果未能解决你的问题,请参考以下文章

ruby 获取给定时区的时间

如何确保保存在数据库中的UTC日期等于Django中指定时区的午夜[重复]

如何在python中获取时区感知午夜日期时间? [复制]

从 UTC 偏移量获取时区名称

给定 UTC 时间戳和 UTC 偏移量,是不是可以在 Python 中获取时区?

在特定时区的一天开始时获取时间对象