【问题标题】:Quickly parse Python datetime in a non-local timezone, adjusting for daylight savings在非本地时区快速解析 Python 日期时间,调整夏令时
【发布时间】:2015-05-29 22:13:54
【问题描述】:

我需要快速将 ISO 8601 日期时间字符串(字符串中没有时区,但已知位于美国/太平洋时区)转换为 numpy datetime64 对象。

如果我的机器在美国/太平洋时间,我可以简单地运行numpy.datetime64(s)。但是,这假定没有时区的字符串位于本地时区。此外,我不能轻易地以 ISO 8601 格式指定美国/太平洋时区,因为它有时是 -0800,有时是 -0700,具体取决于夏令时。

到目前为止,我最快的解决方案是numpy.datetime64(pandas.Timestamp(s).tz_localize(tz='US/Pacific', ambiguous=True))。这在我的机器上需要 70µs。如果我能把这个速度至少提高一个数量级就好了(numpy.datetime64(s) 在本地时间需要 4 µs,但如上所述是不正确的)。这可能吗?

【问题讨论】:

  • 如果性能是一个问题,您一定要这样做数百万次,对吧?每次的偏移量都一样吗?如果是这样,也许只需在所有这些上使用numpy.datetime64(s),然后使用 numpy 算术将它们全部移动相同的偏移量。
  • @unutbu:不幸的是,偏移量不一定相同:有些日期时间在 DST 期间,有些则不是。此外,似乎即使我自己计算每个日期时间的 DST 调整也容易出错?
  • 我不确定。我还没有找到更快的方法。但无论如何,请注意np.datetime64(pd.Timestamp(s, tz='US/Pacific')) 可能不会给您想要的结果。考虑 ISO 8601 日期时间字符串 '2000-04-02T03:00:00-07:00'。如果没有时区,您将拥有s = '2000-04-02T03:00:00'。但是np.datetime64(pd.Timestamp(s, tz='US/Pacific')) 返回numpy.datetime64('2000-04-02T04:00:00.000000-0700'),所以这不是正确的往返。
  • 你的操作系统是什么? Python版本? numpy 版本? pandas 版本?可以升级吗?
  • @unutbu:很好地抓住了错误!正确的电话是np.datetime64(pd.Timestamp(s).tz_localize(tz=tz, ambiguous=True)),这与您的答案中的更快方法一致,但比原来的方法慢了近 2 倍(因此比您的结果慢 200 倍)。

标签: python performance datetime optimization numpy


【解决方案1】:

首先请注意,没有偏移量的一些本地时间,因此它们的日期时间 字符串不明确。例如,ISO 8601 日期时间字符串

2000-10-29T01:00:00-07:00
2000-10-29T01:00:00-08:00

当偏移量被移除时,两者都映射到相同的字符串2000-10-29T01:00:00

因此,可能并不总是能够重建一个唯一的时区感知 来自没有偏移的日期时间字符串的日期时间。

但是,我们可以在这些模棱两可的情况下做出选择 并接受并非所有模棱两可的日期都会被正确转换。


如果您使用的是 Unix,可以使用time.tzset 更改进程的本地时区:

import os
import time
os.environ['TZ'] = tz
time.tzset()

然后您可以使用

将日期时间字符串转换为 NumPy datetime64
def using_tzset(date_strings, tz):
    os.environ['TZ'] = tz
    time.tzset()
    return np.array(date_strings, dtype='datetime64[ns]')

但请注意,using_tzset 并不总是产生与您建议的方法相同的值:

import os
import time
import numpy as np
import pandas as pd

tz = 'US/Pacific'
N = 10**5
dates = pd.date_range('2000-1-1', periods=N, freq='H', tz=tz)
date_strings_tz = dates.format(formatter=lambda x: x.isoformat())
date_strings = [d.rsplit('-', 1)[0] for d in date_strings_tz]

def orig(date_strings, tz):
    return [np.datetime64(pd.Timestamp(s, tz=tz)) for s in date_strings]

def using_tzset(date_strings, tz):
    os.environ['TZ'] = tz
    time.tzset()
    return np.array(date_strings, dtype='datetime64[ns]')

npdates = dates.asi8.view('datetime64[ns]')
x = np.array(orig(date_strings, tz))
y = using_tzset(date_strings, tz)
df = pd.DataFrame({'dates': npdates, 'str': date_strings_tz, 'orig': x, 'using_tzset': y})

这表示原始方法orig 172 次无法恢复原始日期:

print((df['dates'] != df['orig']).sum())
172

using_tzset 失败了 11 次:

print((df['dates'] != df['using_tzset']).sum())
11  

但是请注意,using_tzset 失败的 11 次是由于 DST 导致本地日期时间不明确。

这显示了一些差异:

mask = df['dates'] != df['using_tzset']
idx = np.where(mask.shift(1) | mask)[0]
print(df[['dates', 'str', 'using_tzset']].iloc[idx]).head(6)

#                     dates                        str         using_tzset
# 7248  2000-10-29 08:00:00  2000-10-29T01:00:00-07:00 2000-10-29 08:00:00
# 7249  2000-10-29 09:00:00  2000-10-29T01:00:00-08:00 2000-10-29 08:00:00
# 15984 2001-10-28 08:00:00  2001-10-28T01:00:00-07:00 2001-10-28 08:00:00
# 15985 2001-10-28 09:00:00  2001-10-28T01:00:00-08:00 2001-10-28 08:00:00
# 24720 2002-10-27 08:00:00  2002-10-27T01:00:00-07:00 2002-10-27 08:00:00
# 24721 2002-10-27 09:00:00  2002-10-27T01:00:00-08:00 2002-10-27 08:00:00

如您所见,当str 列中的日期字符串出现差异时 移除偏移后变得不明确。

所以using_tzset 似乎在不明确的日期时间之前产生了正确的结果。


这是比较 origusing_tzset 的 timeit 基准:

In [95]: %timeit orig(date_strings, tz)
1 loops, best of 3: 5.43 s per loop

In [96]: %timeit using_tzset(date_strings, tz)
10 loops, best of 3: 41.7 ms per loop

所以当 N = 10**5 时,using_tzsetorig 快 100 倍以上。

【讨论】:

  • 太棒了!我没有意识到可以设置时区并让 Numpy 接听它。你说的是“机器的本地时区”,但这实际上是进程的时区,对吧?它不会干扰机器上的其他东西吗?
  • 是的;感谢您的更正。 tzset 改变了进程的本地时区概念。
  • 太棒了。听起来这就是我们要走的路,直到有办法给 numpy 一个语言环境来解析。
  • @BenKuhn: 如果时间戳是有序的,那么pytz 允许handle ambiguous times too
  • @J.F.Sebastian:你给剧本计时了吗?我试着去适应它,但不幸的是,它与我上面概述的 Pandas 方法基本相同。
猜你喜欢
  • 1970-01-01
  • 2017-03-02
  • 2012-04-07
  • 2012-07-09
  • 1970-01-01
  • 2017-08-29
  • 2011-04-29
  • 1970-01-01
  • 2016-02-13
相关资源
最近更新 更多