寻找下一个早上 7 点
您可以使用python-dateutil 的relativedelta module 轻松做到这一点:
from dateutil.relativedelta import relativedelta
def next_7am(dt):
relative_days = (dt.hour >= 7)
absolute_kwargs = dict(hour=7, minute=0, second=0, microsecond=0)
return dt + relativedelta(days=relative_days, **absolute_kwargs)
它的工作方式是relativedelta 采用absolute 参数(表示为单数,例如month、year、day)和相对 em> 参数(表示为复数形式,例如months、years、days)。如果将relativedelta 对象添加到datetime,它将替换datetime 中的absolute 值,然后添加relative 值,所以我在上面所做的是指定relative_days 应该是 1 如果它已经是早上 7 点,否则它应该是 0,并且绝对参数说“用早上 7 点替换时间”。将其添加到您的日期时间,它将为您提供下一个早上 7 点。
处理时区
下一步取决于您使用的时区。如果您使用的是dateutil 时区,那么您可以使用上面定义的函数:
dt_next_7am = next_7am(dt)
如果您使用pytz 时区,则应将其剥离并作为原始日期时间进行计算,然后重新本地化时区,如下所示:
dt_next_7am = tz.localize(next_7am(dt.replace(tzinfo=None)))
如果你想得到这两次之间的绝对小时数,你应该用 UTC 做算术:
time_between = dt_next_7am.astimezone(tz=UTC) - dt.astimezone(tz=UTC)
其中UTC 已定义为dateutil.tz.tzutc() 或pytz.UTC 或等效项。
DST 转换示例
这是一个使用dateutil 的示例(结果在评论中):
from datetime import datetime
from dateutil.tz import gettz, tzutc
LA = gettz('America/Los_Angeles')
dt = datetime(2011, 11, 5, 12, 30, tzinfo=LA)
dt7 = next_7am(dt)
print(dt7.astimezone(tzutc()) - dt.astimezone(tzutc())) # 19:30:00
还有一个示例显示了使用pytz 执行此操作的正确方法:
from datetime import datetime
import pytz
LA = pytz.timezone('America/Los_Angeles')
UTC = pytz.UTC
dt = LA.localize(datetime(2011, 11, 5, 12, 30))
dt7_bad = next_7am(dt) # pytz won't like this
dt7_good = LA.localize(next_7am(dt.replace(tzinfo=None)))
dt_utc = dt.astimezone(pytz.UTC)
print(dt7_bad.astimezone(pytz.UTC) - dt_utc) # 18:30:00 (Wrong)
print(dt7_good.astimezone(pytz.UTC) - dt_utc) # 19:30:00 (Right)
模糊/不存在 7 AM
如果您要处理某些区域的某些日期,特别是那些会导致时间不明确的日期,请参见以下列表(截至 2016 年 4 月):
1901-12-13 07:00:00 (/Pacific/Fakaofo)
1901-12-14 07:00:00 (/Asia/Kamchatka)
1901-12-14 07:00:00 (/Asia/Ust-Nera)
1901-12-14 07:00:00 (/Pacific/Bougainville)
1901-12-14 07:00:00 (/Pacific/Kosrae)
1901-12-14 07:00:00 (/Pacific/Majuro)
1917-03-25 07:00:00 (/Antarctica/Macquarie)
1918-03-31 07:00:00 (/EST5EDT)
1919-03-31 07:00:00 (/Antarctica/Macquarie)
1952-01-13 07:00:00 (/Antarctica/DumontDUrville)
1954-02-13 07:00:00 (/Antarctica/Mawson)
1957-01-13 07:00:00 (/Antarctica/Davis)
1969-01-01 07:00:00 (/Antarctica/Casey)
1969-02-01 07:00:00 (/Antarctica/Davis)
1969-09-29 07:00:00 (/Kwajalein)
1969-09-29 07:00:00 (/Pacific/Kwajalein)
1979-09-30 07:00:00 (/Pacific/Enderbury)
1979-09-30 07:00:00 (/Pacific/Kiritimati)
2009-10-18 07:00:00 (/Antarctica/Casey)
2011-09-23 07:00:00 (/Pacific/Apia)
2011-10-28 07:00:00 (/Antarctica/Casey)
那么生成的 7AM 值将不明确或不存在。如果您想处理这些边缘情况,请参阅this answer。可能值得注意的是,在实现PEP495 之后,处理模棱两可的时间可能会略有不同。
下面是使用python-dateutil 的rrule 模块生成重复规则和使用pytz 区域的方法的替代实现(请注意,这将适用于非pytz 区域,但它不会解决歧义/不存在的时间正确):
from datetime import datetime
from dateutil import rrule
import pytz
def next_real_7am(dt):
tzi = dt.tzinfo
dt_naive = dt.replace(tzinfo=None)
rr = rrule.rrule(freq=rrule.DAILY, byhour=7, dtstart=dt_naive)
for ndt in rr:
localize = getattr(tzi, 'localize', None)
if tzi is not None and localize is not None:
try:
ndt = localize(ndt, is_dst=None)
except pytz.AmbiguousTimeError:
return min([localize(ndt, is_dst=True),
localize(ndt, is_dst=False)])
except pytz.NonExistentTimeError:
continue
else:
ndt = ndt.replace(tzinfo=tzi)
return ndt
KWA = pytz.timezone('Pacific/Kwajalein')
dtstart = KWA.localize(datetime(1969, 9, 29, 18))
dt7 = next_real_7am(dtstart)
print(dt7.tzname()) # Should be MHT, before the transition
dtstart = KWA.localize(datetime(1993, 8, 19, 18)) # There was no 8/20 in this zone
dt7 = next_real_7am(dtstart)
print(dt7) # Should be 1993-8-21 07:00:00