如何在 Python 中查找同一小时的第二天 Unix 时间戳,包括 DST?
Posted
技术标签:
【中文标题】如何在 Python 中查找同一小时的第二天 Unix 时间戳,包括 DST?【英文标题】:How to find next day's Unix timestamp for same hour, including DST, in Python? 【发布时间】:2013-12-15 02:29:59 【问题描述】:在 Python 中,我可以找到本地时间的 Unix 时间戳,知道时区,就像这样(使用 pytz):
>>> import datetime as DT
>>> import pytz
>>> mtl = pytz.timezone('America/Montreal')
>>> naive_time3 = DT.datetime.strptime('2013/11/03', '%Y/%m/%d')
>>> naive_time3
datetime.datetime(2013, 11, 3, 0, 0)
>>> localized_time3 = mtl.localize(naive_time3)
>>> localized_time3
datetime.datetime(2013, 11, 3, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
>>> localized_time3.timestamp()
1383451200.0
到目前为止,一切都很好。 naive_time
不知道时区,而 localized_time
知道它在 2013/11/03 蒙特利尔的午夜,因此 (UTC) Unix 时间戳很好。这个时区也是我的本地时区,这个时间戳似乎是正确的:
$ date -d @1383451200
Sun Nov 3 00:00:00 EDT 2013
现在,蒙特利尔的时钟已于 11 月 3 日 2:00 提前一小时调整,因此我们当天多出了一个小时。这意味着在 2013/11/03 和 2013/11/04 之间有 25 小时。这表明它:
>>> naive_time4 = DT.datetime.strptime('2013/11/04', '%Y/%m/%d')
>>> localized_time4 = mtl.localize(naive_time4)
>>> localized_time4
datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EST-1 day, 19:00:00 STD>)
>>> (localized_time4.timestamp() - localized_time3.timestamp()) / 3600
25.0
现在,我正在寻找一种从localized_time3
获取localized_time4
对象的简单方法,因为我知道我想在同一时间(此处为午夜)获取下一个本地化日期.我试过timedelta
,但我相信它不知道时区或夏令时:
>>> localized_time4td = localized_time3 + DT.timedelta(1)
>>> localized_time4td
datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
>>> (localized_time4td.timestamp() - localized_time3.timestamp()) / 3600
24.0
我的目的是获取有关每个本地日期的日志条目的信息,这些条目与它们的 Unix 时间戳一起存储。当然,如果我使用localized_time3.timestamp()
并在此处添加24 * 3600
(与localized_time4td.timestamp()
相同),我将错过localized_time4td.timestamp()
和localized_time4td.timestamp() + 3600
之间发生的所有日志条目。
换句话说,我正在寻找的函数或方法应该知道何时将 25 小时、24 小时或 23 小时添加到 Unix 时间戳,具体取决于 DST 转换发生的时间。
【问题讨论】:
必须是std lib模块还是可以使用pip模块? 如果轻量级 PyPI 模块提供了非常简单的解决方案,我愿意使用它们。例如,我打算使用pytz
。
我建议你看看优秀的 Arrow (crsmithdev.com/arrow)。
@rdodev:Arrow 似乎效果很好(刚刚尝试过)!您想通过工作示例提供答案(我会接受),还是应该使用 Arrow 回答我自己的问题?还是谢谢你。
很高兴为您提供帮助!来吧,自己回答。我的时间有点短。
【参考方案1】:
(感谢@rdodev 将我指向Arrow)。
使用箭头,这个操作变得简单:
>>> import arrow
>>> import datetime as DT
>>> lt3 = arrow.get(DT.datetime(2013, 11, 3), 'America/Montreal')
>>> lt3
<Arrow [2013-11-03T00:00:00-04:00]>
>>> lt4 = arrow.get(DT.datetime(2013, 11, 4), 'America/Montreal')
>>> lt4
<Arrow [2013-11-04T00:00:00-05:00]>
>>> lt4.timestamp - (lt3.replace(days=1).timestamp)
0
>>> (lt3.replace(days=1).timestamp - lt3.timestamp) / 3600
25.0
使用 Arrow 的 replace
方法,单数单位名称替换该属性,而复数添加它。所以lt3.replace(days=1)
是 2013 年 11 月 4 日,而 lt3.replace(day=1)
是 2013 年 11 月 1 日。
【讨论】:
Arrow
如何处理ambiguous or non-existent times?
嗯,2013 年 11 月 3 日 1:30 在蒙特利尔似乎默认为无 DST,因为它输出 <Arrow [2013-11-03T01:30:00-05:00]>
(而 0:59 有 DST:<Arrow [2013-11-03T00:59:00-04:00]>
)。
2014-03-09 02:45:00
等不存在的时间呢?【参考方案2】:
不使用新包:
def add_day(x):
d = x.date()+DT.timedelta(1)
return mtl.localize(x.replace(year=d.year, month=d.month, day=d.day, tzinfo=None))
完整脚本:
import datetime as DT
import pytz
import calendar
mtl = pytz.timezone('America/Montreal')
naive_time3 = DT.datetime.strptime('2013/11/03', '%Y/%m/%d')
print repr(naive_time3)
#datetime.datetime(2013, 11, 3, 0, 0)
localized_time3 = mtl.localize(naive_time3)
print repr(localized_time3)
#datetime.datetime(2013, 11, 3, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
print calendar.timegm(localized_time3.utctimetuple())
#1383451200.0
def add_day(x):
d = x.date()+DT.timedelta(1)
return mtl.localize(x.replace(year=d.year, month=d.month, day=d.day, tzinfo=None))
print repr(add_day(localized_time3))
#datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EST-1 day, 19:00:00 STD>)
(calendar
用于 Python2。)
【讨论】:
我真的很喜欢这个解决方案!它不像使用 Arrow 那样简单,它确实内置了这个功能,但它仍然可以在没有外部部门的情况下完成工作。谢谢! 它不处理不明确或不存在的时间。你可以fix it using.dst(),
.normalize()` methods
感谢您指出这一点,J.F.塞巴斯蒂安。但是,在我的具体情况下,我只需要支持午夜(即用户只指定日期,而不是日期/时间)。因此,模棱两可/不存在的时间永远不会成为问题。有趣的是,如果有人需要它,也有一个解决方案,在你对我的问题的回答中给出。
@eepp:时区 UTC 偏移量可能会在午夜发生变化(这并不常见,除非您在巴西利亚,但 it happens)。如果你想断言你的代码没有处理它,那么使用is_dst=None
来检测它。要获得午夜:datetime.combine(some_date, time(0, 0))
可以用来代替 x.replace(year=d.year, ...
。【参考方案3】:
这里是基于dateutil
的替代方案:
>>> # In Spain we changed DST 10/26/2013
>>> import datetime
>>> import dateutil.tz
>>> # tzlocal gets the timezone of the computer
>>> dt1 = datetime.datetime(2013, 10, 26, 14, 00).replace(tzinfo=dateutil.tz.tzlocal())
>>> print dt1
2013-10-26 14:00:00+02:00
>>> dt2 = dt1 + datetime.timedelta(1)
>>> print dt2
2013-10-27 14:00:00+01:00
# see if we hace 25 hours of difference
>>> import time
>>> (time.mktime(dt2.timetuple()) - time.mktime(dt1.timetuple())) / 3600.0
25.0
>>> (float(dt2.strftime('%s')) - float(dt1.strftime('%s'))) / 3600 # the same
25.0
【讨论】:
dateutil
might fail during DST transitions。如果操作系统的历史时区数据库未使用或不存在(Windows),它可能会因过去的日期而失败。它不处理模棱两可、不存在的时间。
我用我的时区 (CEST -> CET) 测试了 DST 转换,如示例所示。我只将它用于最近和未来的日期。感谢您的提示。
时区信息一直在变化。有recent changes in 2013。我相信这些变化在未来不会停止。【参考方案4】:
在这个答案的最后,我逐渐提供了几种解决方案,其中最强大的解决方案试图处理以下问题:
由于 DST 造成的 UTC 偏移 由于与 DST 无关的原因,本地时区可能具有不同 UTC 偏移量的过去日期。dateutil
和 stdlib 解决方案在某些系统上失败,尤其是 Windows
夏令时时间不明确(不知道Arrow
是否提供接口处理)
DST 期间不存在的时间(相同)
要在给定时区查找明天午夜(或其他固定时间)的 POSIX 时间戳,您可以使用来自 How do I get the UTC time of “midnight” for a given timezone? 的代码:
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
tomorrow = datetime(2013, 11, 3).date() + DAY
midnight = tz.localize(datetime.combine(tomorrow, time(0, 0)), is_dst=None)
timestamp = (midnight - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
dt.date()
方法为幼稚和时区感知 dt
对象返回相同的幼稚日期。
时间戳的显式公式用于支持 Python 3.3 之前的 Python 版本。否则.timestamp()
方法可以在 Python 3.3+ 中使用。
为了避免在 .localize()
方法无法避免的 DST 转换期间解析输入日期时出现歧义,除非您知道 is_dst
参数,您可以使用与日期一起存储的 Unix 时间戳:
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
local_dt = datetime.fromtimestamp(timestamp_from_the_log, tz)
tomorrow = local_dt.date() + DAY
midnight = tz.localize(datetime.combine(tomorrow, time(0, 0)), is_dst=None)
timestamp = (midnight - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
支持其他固定时间(不仅是午夜):
tomorrow = local_dt.replace(tzinfo=None) + DAY # tomorrow, same time
dt_plus_day = tz.localize(tomorrow, is_dst=None)
timestamp = dt_plus_day.timestamp() # use the explicit formula before Python 3.3
is_dst=None
如果结果日期不明确或不存在,则会引发异常。为避免异常,您可以选择与昨天的前一个日期最接近的时间(相同的 DST 状态,即is_dst=local_dt.dst()
):
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
local_dt = datetime.fromtimestamp(timestamp_from_the_log, tz)
tomorrow = local_dt.replace(tzinfo=None) + DAY
dt_plus_day = tz.localize(tomorrow, is_dst=local_dt.dst())
dt_plus_day = tz.normalize(dt_plus_day) # to detect non-existent times
timestamp = (dt_plus_day - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
.localize()
尊重给定的时间,即使它不存在,因此需要.normalize()
来修复时间。如果normalize()
方法更改其输入(在这种情况下检测到不存在时间)以与其他代码示例保持一致,您可以在此处引发异常。
【讨论】:
您为什么要使用is_dst=local_dt.dst()
来确保“距离昨天最接近上一个日期的时间”? localize
的 is_dst
参数接受 True
、False
或 None
,而 datetime.datetime
的 dst()
方法返回 datetime.timedelta
。因此,mtl.localize(DT.datetime(2013, 11, 4)).dst() == True
是 False
,mtl.localize(DT.datetime(2013, 11, 4)).dst() == True
也是,而第一个有夏令时,第二个没有。
@eepp:避免使用some_python == True
,改用bool(some_python_obj)
。阅读Truth value testing in Python
很高兴知道!我对 Python 还很陌生,但是感谢像你这样的人,我变得更好了。文档是这样说的:在布尔上下文中,timedelta
对象被认为是真当且仅当它不等于 timedelta(0).
。现在这是有道理的。但是,您确实对某些事情有所了解,谢谢。
@eepp: 明确一点:你很少需要显式调用bool()
,if some_python_obj: ...
照常工作。以上是关于如何在 Python 中查找同一小时的第二天 Unix 时间戳,包括 DST?的主要内容,如果未能解决你的问题,请参考以下文章