【问题标题】:Python: Given the current time in UTC, how do you determine the start and end time of the day in a particular timezone?Python:给定当前 UTC 时间,你如何确定特定时区一天的开始和结束时间?
【发布时间】:2011-11-05 08:42:53
【问题描述】:

我正在使用 Google App Engine,我了解到时区固定为 UTC。我想确定用户本地时区当天的开始和结束时间。所以基本上,给定 UTC 的当前时间,您如何确定当天的开始和结束时间,同时考虑夏令时转换。

我有一些笨拙的示例代码。请注意,我意识到如果我手动指定日期,我也可以指定明天的日期,但它们只是示例,我想以编程方式确定它。我的主要问题是,如果我将 timedelta 添加到带有时区的日期时间,然后对其进行规范化(就像 pytz 文档中建议的那样),我会得到一个在夏令时切换期间休息一小时的日期时间。

代码中未提及,但最终目的是将这些时间转换回 UTC,这就是了解时区的重要性。

#!/usr/bin/python

import datetime
from pytz.gae import pytz

hobart_tz = pytz.timezone('Australia/Hobart')

utc_dt = pytz.utc.localize(datetime.datetime.utcnow())
hobart_dt = utc_dt.astimezone(hobart_tz)

# create a new datetime for the start of the day and add a day to it to get tomorrow.
today_start = datetime.datetime(hobart_dt.year, hobart_dt.month, hobart_dt.day)
today_start = hobart_tz.localize(today_start)
today_end = hobart_tz.normalize(today_start + datetime.timedelta(days=1))
print 'today:', today_start
print ' next:', today_end
print
# gives:
# today: 2011-08-28 00:00:00+10:00
# next: 2011-08-29 00:00:00+10:00

# but say today is a daylight savings changeover.
# after normalisation, we are off by an hour.

dst_finish_2011 = datetime.datetime(2011, 4, 3)  # this would come from hobart_dt
dst_finish_2011 = hobart_tz.localize(dst_finish_2011)
next = hobart_tz.normalize(dst_finish_2011 + datetime.timedelta(days=1))
print '2011-04-03:', dst_finish_2011
print '2011-04-04:', next   # expect 2011-04-04 00:00:00+10:00
print
# gives
# 2011-04-03: 2011-04-03 00:00:00+11:00
# 2011-04-04: 2011-04-03 23:00:00+10:00 (wrong)

dst_start_2011 = datetime.datetime(2011, 10, 2)  # this would come from hobart_dt
dst_start_2011 = hobart_tz.localize(dst_start_2011)
next = hobart_tz.normalize(dst_start_2011 + datetime.timedelta(days=1))
print '2011-10-02:', dst_start_2011
print '2011-10-03:', next   # expect 2011-10-03 00:00:00+11:00
print
# gives
# 2011-10-02: 2011-10-02 00:00:00+10:00
# 2011-10-03: 2011-10-03 01:00:00+11:00 (wrong)

# I guess we could ignore the timezone and localise *after* ?

dst_finish_2011 = datetime.datetime(2011, 4, 3)  # this would come from hobart_dt
next = dst_finish_2011 + datetime.timedelta(days=1)
# now localise
dst_finish_2011 = hobart_tz.localize(dst_finish_2011)
next = hobart_tz.localize(next)
print '2011-04-03:', dst_finish_2011
print '2011-04-04:', next   # expect 2011-04-04 00:00:00+10:00
print
# gives
# 2011-04-03: 2011-04-03 00:00:00+11:00
# 2011-04-04: 2011-04-04 00:00:00+10:00

【问题讨论】:

标签: python datetime timezone utc


【解决方案1】:

在知道 UTC 时间的情况下,找出当地时区一天的开始时间(午夜)和一天的结束时间(明天):

#!/usr/bin/env python
from datetime import datetime, time, timedelta
import pytz # $ pip install pytz
from tzlocal import get_localzone # $ pip install tzlocal

tz = get_localzone() # get the local timezone as pytz.timezone
now = datetime.now(pytz.utc) # some UTC time
dt = now.astimezone(tz) # the same time in the local timezone
today = dt.date() # today in the local timezone (naive date object)
midnight = datetime.combine(today, time()) # midnight in the local timezone
aware_midnight = tz.localize(midnight, is_dst=None) # raise exception
                                                    # for ambiguous or
                                                    # non-existing
                                                    # times
tomorrow = midnight + timedelta(1)
aware_tomorrow = tz.localize(tomorrow, is_dst=None)

def print_time(aware_dt, fmt="%Y-%m-%d %H:%M:%S %Z%z"):
    print(aware_dt.strftime(fmt))
    utc_dt = aware_dt.astimezone(pytz.utc) # the same time in UTC
    print(utc_dt.strftime(fmt))

print_time(aware_midnight)
print_time(aware_tomorrow)

输出

2014-09-01 00:00:00 EST+1000
2014-08-31 14:00:00 UTC+0000
2014-09-02 00:00:00 EST+1000
2014-09-01 14:00:00 UTC+0000

另见,

【讨论】:

  • 这段代码在什么情况下会引发异常?
  • @goblin:“为不明确或不存在的时间引发异常”,例如,在 DST 转换期间。
【解决方案2】:

以下代码尝试获取午夜时间;如果时区调整失败,它会使用新的时区偏移重新调整回午夜。

def DayStartEnd(localized_dt):
    tz = localized_dt.tzinfo
    start = tz.normalize(datetime.datetime(localized_dt.year,localized_dt.month,localized_dt.day,0,0,0,0,tz))
    after_midnight = start.hour*60*60 + start.minute*60 + start.second
    if start.day != localized_dt.day:
        start += datetime.timedelta(seconds = 24*60*60 - after_midnight)
    elif after_midnight != 0:
        start -= datetime.timedelta(seconds = after_midnight)
    end = tz.normalize(start + datetime.timedelta(hours=24))
    after_midnight = end.hour*60*60 + end.minute*60 + end.second
    if end.day == localized_dt.day:
        end += datetime.timedelta(seconds = 24*60*60 - after_midnight)
    elif after_midnight != 0:
        end -= datetime.timedelta(seconds = after_midnight)
    return start,end

>>> hobart_tz = pytz.timezone('Australia/Hobart')
>>> dst_finish_2011 = datetime.datetime(2011, 4, 3)
>>> dst_finish_2011 = hobart_tz.localize(dst_finish_2011)
>>> start,end = DayStartEnd(dst_finish_2011)
>>> print start,end
2011-04-03 00:00:00+11:00 2011-04-04 00:00:00+10:00
>>> dst_start_2011 = datetime.datetime(2011, 10, 2)
>>> dst_start_2011 = hobart_tz.localize(dst_start_2011)
>>> start,end = DayStartEnd(dst_start_2011)
>>> print start,end
2011-10-02 00:00:00+10:00 2011-10-03 00:00:00+11:00

【讨论】:

  • 它似乎为2014-10-20 00:00:00ZBrazil/East 时区产生了错误的答案。
【解决方案3】:

经过一些实验和思考,我相信我有一个适合你的解决方案。正如您所指出的,我之前的回答不正确; days=1 的 timedelta 对象与 seconds=86400 的基本相同(除了闰秒)。

我推荐的一种方法是增加日期而不考虑一天中的时间是使用datetime.date 对象而不是datetime.datetime 对象:

>>> oneday = datetime.timedelta(days=1)
>>> d = datetime.date(2011,4,3)
>>> str(d + oneday)
'2011-04-04'

然后可以添加一天中的时间以形成一个完整的 datetime.datetime 对象,您可以在其中知道一天中的时间字段与您的原始值保持不变。

另一种我觉得安全的方法是暂时使用“幼稚”的日期。这样,在添加 timedelta 时无需应用时区策略。

>>> hob = pytz.timezone('Australia/Hobart')
>>> dstlast = datetime.datetime(2011,4,3)
>>> str(dstlast)
'2011-04-03 00:00:00'
>>> dstlasthob = hob.localize(dstlast)
>>> str(dstlasthob)
'2011-04-03 00:00:00+11:00'
>>> oneday = datetime.timedelta(days=1)
>>> str(hob.normalize(dstlasthob + oneday))
'2011-04-03 23:00:00+10:00'
>>> nextday = hob.localize(dstlasthob.replace(tzinfo=None) + oneday)
>>> str(nextday)
'2011-04-04 00:00:00+10:00'

我针对包含闰秒的日期(例如 2008 年 12 月 31 日)测试了这种方法,结果是一天中的时间为 00:00:00。这实际上可能是错误的,我不确定,但这是你想要的:-)

【讨论】:

  • 答案可能会产生错误甚至不存在的时间——hob.localize() 尊重输入日期时间,即使给定时区中不存在时间。为了避免这种情况,可以使用is_dst=None 而不是默默地返回错误答案。答案证明了这一点,但值得重复 hob.normalize() 可能会将时间从午夜开始。
【解决方案4】:

我相信您会得到这个结果,因为您添加的是一天而不是 86400 秒。天和秒之间没有统一的、始终正确的等价物。例如,如果pytz 强制一天“真正”为 86400 秒,那么将一天添加到 12 月 31 日或 6 月 30 日的日期有时会导致结果的 seconds 字段“减少一秒”,因为在某些年份,那些日子有 86401 秒。 (未来他们可能有 86402 甚至 86399 秒。)

因此,添加一天的意思是简单地将一天增加一,必要时延续到月和年,但不更改时间字段。尝试添加 86400 秒,看看是否得到所需的结果。

【讨论】:

  • 感谢您的回复。显然,POSIX 标准将一天定义为 86400 秒,但这可能仅适用于 GMT 不需要担心夏令时。我认为 posix 实际上并不理解闰秒的概念,而是更愿意将时间跟踪为自 1970 年 1 月 1 日以来的秒数。我明确使用了“天”,因为我希望它会考虑到夏令时的差异。不幸的是,以“秒”为单位定义时间增量似乎没有帮助。
  • @Nick Sonneveld 根据维基百科,Unix 时间值可以在闰秒期间重复或跳过。见en.wikipedia.org/wiki/Unix_time。通过这种方式,POSIX 支持 UTC。相比之下,格林威治标准时间没有闰秒,因此多年来一直在缓慢偏离 UTC。
  • datetime 不知道闰秒。问题中的错误与一天是86400还是86401秒无关。 timedelta(days=1) == timedelta(seconds=86400) 即使用 OPs 代码添加 84600 秒不会修复它。 My answer demonstrates how to get start/end times properly.
猜你喜欢
  • 2013-02-05
  • 1970-01-01
  • 2019-07-17
  • 2022-12-03
  • 1970-01-01
  • 2016-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多