Python:给定 UTC 中的当前时间,如何确定特定时区一天的开始和结束时间?

发布于 2024-12-01 18:48:53 字数 2383 浏览 1 评论 0原文

我正在使用 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

I'm playing around with Google App Engine and I learned that the timezone is fixed to UTC. I want to determine the start and end time of the current day for the user's local timezone. So basically, given the current time in UTC, how do you determine the start and end time of the current day, taking into account daylight savings changeovers.

I have some clunky example code. Please note that I realise that if I'm manually specifying a date, I could specify tomorrow's date as well, but they are examples and I want to determinte it programatically. My main problem is that if I add a timedelta to a datetime with a timezone and then normalise it (like it is suggested in the pytz docs) I get a datetime which is an hour off during daylight savings switchovers.

Not mentioned in the code, but ultimately the aim is to convert these times back to UTC which is why it's important to be timezone aware.

#!/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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

Saygoodbye 2024-12-08 18:48:53

要在本地时区中找出一天的开始时间(午夜)和一天的结束时间(明天),并了解 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

另请参阅

To find out the start time of the day (midnight) and the end time of the day (tomorrow) in the local timezone, knowing UTC time:

#!/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)

Output

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

See also,

萌化 2024-12-08 18:48:53

我相信您得到这个结果是因为您添加了一天而不是 86400 秒。天和秒之间不存在统一、始终正确的等价关系。例如,如果 pytz 强制一天“实际上”为 86400 秒,则在 12 月 31 日或 6 月 30 日日期中添加一天有时会导致结果的秒字段为“一”第二关”,因为有些年份那些日子有86401秒。 (未来可能有 86402 秒甚至 86399 秒。)

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

I believe you are getting this result because you are adding one day instead of 86400 seconds. There is no uniform, always-correct equivalence between days and seconds. For example, if pytz were to force one day to "really" be 86400 seconds, then adding one day to a December 31 or June 30 date would sometimes cause the seconds field of the result to be "one second off", because in some years those days have had 86401 seconds. (It is possible they could have 86402 or even 86399 seconds in the future.)

So adding one day is taken to mean simply incrementing the day by one, carrying over to month and year if necessary, but without changing the time-of-day fields. Try adding 86400 seconds and see if you get the desired result.

眼趣 2024-12-08 18:48:53

使用arrow,代码会很简单。

import arrow
from datetime import date

def get_start_end_utc_of_date(date: date | str, timezone="UTC") -> tuple[arrow.Arrow, arrow.Arrow]:
    specific_date = arrow.get(date)
    day = specific_date.day
    local_date = specific_date.to(timezone)
    start_of_day_local = local_date.floor('day')
    # If tz is negative, day floor will be the previous day, so we need to adjust it
    start = start_of_day_local.replace(day=day)
    return start, start.shift(days=1)

>>> print(get_start_end_utc_of_date("2024-03-15", "America/New_York"))
(<Arrow [2024-03-15T00:00:00-04:00]>, <Arrow [2024-03-16T00:00:00-04:00]>)

Use arrow, code will be simple.

import arrow
from datetime import date

def get_start_end_utc_of_date(date: date | str, timezone="UTC") -> tuple[arrow.Arrow, arrow.Arrow]:
    specific_date = arrow.get(date)
    day = specific_date.day
    local_date = specific_date.to(timezone)
    start_of_day_local = local_date.floor('day')
    # If tz is negative, day floor will be the previous day, so we need to adjust it
    start = start_of_day_local.replace(day=day)
    return start, start.shift(days=1)

>>> print(get_start_end_utc_of_date("2024-03-15", "America/New_York"))
(<Arrow [2024-03-15T00:00:00-04:00]>, <Arrow [2024-03-16T00:00:00-04:00]>)
想念有你 2024-12-08 18:48:53

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

我推荐的一种方法是使用 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。我不确定这实际上可能是错误的,但这就是你想要的:-)

After a little experimentation and thinking I believe I have a solution for you. My previous answer was not correct as you pointed out; a timedelta object for days=1 is basically the same as for seconds=86400 (except where leap seconds are concerned).

One way, the way I would recommend, to increment the date without regards to the time of day is to use a datetime.date object instead of a datetime.datetime object:

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

The time of day could then be added to form a complete datetime.datetime object in which you know the time of day fields are unaltered from your original value.

Another method, one which I would feel safe using, is to work with "naive" dates temporarily. This way there is no time zone policy to apply while adding the 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'

I tested this method for dates that have leap seconds in them (one example is 2008-12-31) and the result has time of day 00:00:00. Which may actually be wrong, I'm not sure, but it is what you want :-)

路弥 2024-12-08 18:48:53

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

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

The following code tries to get a time of midnight; if the time zone adjustment makes it fail, it readjusts back to midnight with the new zone offset.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文