在非本地时区快速解析 Python 日期时间,调整夏令时
Quickly parse Python datetime in a non-local timezone, adjusting for daylight savings
我需要快速将 ISO 8601 日期时间字符串——字符串中没有时区,但已知在 US/Pacific 时区内——转换为 numpy datetime64
对象。
如果我的机器在 US/Pacific 时间,我可以简单地 运行 numpy.datetime64(s)
。但是,这假设没有时区的字符串在本地时区。此外,我无法轻松指定 ISO 8601 格式的 US/Pacific 时区,因为它有时是 -0800
,有时是 -0700
,具体取决于夏令时。
到目前为止,我最快的解决方案是 numpy.datetime64(pandas.Timestamp(s).tz_localize(tz='US/Pacific', ambiguous=True))
。这在我的机器上需要 70µs。如果我能至少快一个数量级就更好了(numpy.datetime64(s)
在本地时间需要 4 微秒,但如上所述是不正确的)。这可能吗?
首先请注意,没有偏移一些本地时间,因此它们的日期时间
字符串不明确。例如,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
似乎在不明确的日期时间之前产生了正确的结果。
这是比较 orig
和 using_tzset
的时间基准:
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_tzset
比 orig
快 100 倍以上。
我需要快速将 ISO 8601 日期时间字符串——字符串中没有时区,但已知在 US/Pacific 时区内——转换为 numpy datetime64
对象。
如果我的机器在 US/Pacific 时间,我可以简单地 运行 numpy.datetime64(s)
。但是,这假设没有时区的字符串在本地时区。此外,我无法轻松指定 ISO 8601 格式的 US/Pacific 时区,因为它有时是 -0800
,有时是 -0700
,具体取决于夏令时。
到目前为止,我最快的解决方案是 numpy.datetime64(pandas.Timestamp(s).tz_localize(tz='US/Pacific', ambiguous=True))
。这在我的机器上需要 70µs。如果我能至少快一个数量级就更好了(numpy.datetime64(s)
在本地时间需要 4 微秒,但如上所述是不正确的)。这可能吗?
首先请注意,没有偏移一些本地时间,因此它们的日期时间 字符串不明确。例如,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 datetime64def 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
似乎在不明确的日期时间之前产生了正确的结果。
这是比较 orig
和 using_tzset
的时间基准:
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_tzset
比 orig
快 100 倍以上。