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

77

目前我能想到的最好的方法就是这个巨兽:

>>> 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()),因为那会使用服务器的时区而不是用户的时区。

我感觉自己漏掉了什么,有什么想法吗?

6个回答

115

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

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

但是...你代码中存在比美学更大的问题:在夏令时转换日或从夏令时转换回来的那天,它将会给出错误的结果。

原因是datetime构造函数和replace()都没有考虑到夏令时的变化。

例如:

>>> 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() - jfs
@J.F.Sebastian:有趣!你确定吗?你有例子吗?这完全有可能。 - user3850
@hop:是的。你的代码在2012年4月1日出了问题。请参见我的答案 - jfs
1
当前版本在巴西/东部时区对于2014年10月19日返回错误结果,因为您没有像我的答案中使用is_dst=None - jfs
回复第一条评论:没有毫秒属性,只有微秒。 - Egirus Ornila

36

@hop's answer在夏令时转换日(例如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)        
相同的情况也适用于注释:
#!/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)

我有点困惑。夏令时切换发生在凌晨3点,因此那天的午夜应该仍然是在14:00 UTC,而不是13:00。对吧? - user3850
@hop:将2012年3月31日13:00 UTC转换为墨尔本时区并自行查看(它仍然是+11时区(夏令时),而不是+10(标准))。 - jfs
2
time()time(0, 0)相同,但更短。 - mik

4
这个更容易理解使用dateutil.tz而不是pytz:

这比使用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文档,自Python 3.6以来推荐使用dateutil.tz。与pytz不同,dateutil.tz的tzinfo对象在处理DST等异常情况时不需要本地化功能。参考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

0
值得注意的是,我们可以根据@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))

0

每个时区都有一个数字,例如美国中部 = -6。这被定义为与UTC相差的小时偏移量。由于0000是午夜,您可以使用此偏移量来查找任何时区在UTC午夜时的时间。要访问它,我认为您可以使用

time.timezone

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

time.timezone

本地(非DST)时区相对于UTC的秒偏移量(在大多数西欧地区为负,在美国为正,在英国为零)。

因此,如果该数字为正,则可以将该数字用于小时时间(即,如果在芝加哥(具有+6时区值)为午夜,则为6000 = UTC上午6点)。

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


我认为你走在正确的轨道上,但是你怎么知道从哪个日期开始呢?例如,几个小时之前,在墨尔本这里是17号,而在UTC仍然是16号。 - Tom
问题是关于当地的午夜时间。日期关系由时区的UTC偏移固定 - 在当地的午夜时间。 - S.Lott
手动添加/减去时区差可能会在夏令时的切换时出现问题。 - user3850

0

设置 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 变量来控制这个问题之外,它并没有告诉我如何找到午夜而只是显示当前时间。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接