NumPy 日期时间与时间差

从 NumPy 1.7 起,核心数组数据类型原生支持日期时间功能。这个类型名为 datetime64,因为 datetime.datetime 已被 Python 标准库使用。

datetime64 的约定与假设

与 Python 的 datetime.date 类似,日期使用现行格里高利历,并无限延伸到过去和未来。但 Python datetime.date 只支持公元1年至9999年,datetime64 还支持公元前日期,按天文年份编号表示:公元前2年为 −1,公元前1年为0,公元1年为1。

16:23:32.234 这样的时刻,是从午夜开始计数的小时、分钟、秒和秒的小数部分:00:00:00.000 为午夜,12:00:00.000 为正午。每个日历日恰好86400秒。这是一种“不带时区”的时间,没有显式时区或 UT1、UTC、TAI 等时间尺度概念。

基本日期时间

最基本的创建方式是解析 ISO 8601 日期或日期时间字符串,也可以把整数解释为相对 Unix 纪元(1970-01-01 00:00:00 UTC)的偏移。内部单位根据字符串形式自动选择,可以是日期单位或时间单位:年 Y、月 M、周 W、日 D;时 h、分 m、秒 s、毫秒 ms,以及其他带 SI 前缀的秒单位。

datetime64 还接受任意大小写组合的 NAT,表示“Not A Time”。now 返回当前 UTC 时间,默认秒精度;可以指定 M、D、h 等单位进行截断。细于秒的单位也支持,例如 ms 和 ns,但小数部分显示为零,实际仍截断到整秒。today 返回当前 UTC 日期,默认日精度,也支持与 now 相同的精度参数。

创建示例

简单 ISO 日期:

>>> import numpy as np

>>> np.datetime64('2005-02-25')
np.datetime64('2005-02-25')

整数加日期单位:Unix 纪元之后一年:

>>> np.datetime64(1, 'Y')
np.datetime64('1971')

以月为单位:

>>> np.datetime64('2005-02')
np.datetime64('2005-02')

只指定月份,但强制日单位:

>>> np.datetime64('2005-02', 'D')
np.datetime64('2005-02-01')

日期与时刻:

>>> np.datetime64('2005-02-25T03:30')
np.datetime64('2005-02-25T03:30')

NaT:

>>> np.datetime64('nat', 'D')
np.datetime64('NaT', 'D')

当前 UTC 时间,默认秒精度,并演示日与毫秒精度:

>>> np.datetime64('now')
np.datetime64('2025-08-05T02:22:14')  # result will depend on the current time

>>> np.datetime64('now', 'D')
np.datetime64('2025-08-05')

>>> np.datetime64('now', 'ms')
np.datetime64('2025-08-05T02:22:14.000')

当前日期;now 与 today 示例的结果取决于执行时间:

>>> np.datetime64('today')
np.datetime64('2025-08-05')  # result will depend on the current date

从字符串创建日期时间数组时,可以使用通用单位的日期时间类型,由输入自动选择单位。

>>> import numpy as np

>>> np.array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype=np.datetime64)
array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype='datetime64[D]')

>>> np.array(['2001-01-01T12:00', '2002-02-03T13:56:03.172'], dtype=np.datetime64)
array(['2001-01-01T12:00:00.000', '2002-02-03T13:56:03.172'],
      dtype='datetime64[ms]')

也可从表示 POSIX 时间戳的整数和指定单位创建数组:

>>> import numpy as np

>>> np.array([0, 1577836800], dtype='datetime64[s]')
array(['1970-01-01T00:00:00', '2020-01-01T00:00:00'],
      dtype='datetime64[s]')

>>> np.array([0, 1577836800000]).astype('datetime64[ms]')
array(['1970-01-01T00:00:00.000', '2020-01-01T00:00:00.000'],
      dtype='datetime64[ms]')

日期时间类型适用于许多常见 NumPy 函数。例如,用 arange 生成一个月的全部日期:

>>> import numpy as np

>>> np.arange('2005-02', '2005-03', dtype='datetime64[D]')
array(['2005-02-01', '2005-02-02', '2005-02-03', '2005-02-04',
       '2005-02-05', '2005-02-06', '2005-02-07', '2005-02-08',
       '2005-02-09', '2005-02-10', '2005-02-11', '2005-02-12',
       '2005-02-13', '2005-02-14', '2005-02-15', '2005-02-16',
       '2005-02-17', '2005-02-18', '2005-02-19', '2005-02-20',
       '2005-02-21', '2005-02-22', '2005-02-23', '2005-02-24',
       '2005-02-25', '2005-02-26', '2005-02-27', '2005-02-28'],
      dtype='datetime64[D]')

日期时间对象表示单个时刻。单位不同的两个日期时间仍可能表示同一时刻。从月等较大单位转换为日等较小单位属于“安全”转换,因为仍能精确表示同一时刻:

>>> import numpy as np

>>> np.datetime64('2005') == np.datetime64('2005-01-01')
True

>>> np.datetime64('2010-03-14T15') == np.datetime64('2010-03-14T15:00:00.00')
True

日期时间与时间差运算

两个日期时间相减会得到带时间单位的数。NumPy 核心没有物理量体系,因此创建 timedelta64 补充 datetime64。它的参数是表示单位数量的数值,以及 D、M、Y、h、m 或 s 等单位;数值也可用 NAT 替代:

>>> import numpy as np

>>> np.timedelta64(1, 'D')
np.timedelta64(1,'D')

>>> np.timedelta64(4, 'h')
np.timedelta64(4,'h')

>>> np.timedelta64('nAt', 'D')
np.timedelta64('NaT', 'D')

两种类型配合完成简单日期运算:

>>> import numpy as np

>>> np.datetime64('2009-01-01') - np.datetime64('2008-01-01')
np.timedelta64(366,'D')

>>> np.datetime64('2009') + np.timedelta64(20, 'D')
np.datetime64('2009-01-21')

>>> np.datetime64('2011-06-15T00:00') + np.timedelta64(12, 'h')
np.datetime64('2011-06-15T12:00')

>>> np.timedelta64(1,'W') / np.timedelta64(1,'D')
7.0

>>> np.timedelta64(1,'W') % np.timedelta64(10,'D')
np.timedelta64(7,'D')

>>> np.datetime64('nat', 'D') - np.datetime64('2009-01-01')
np.timedelta64('NaT','D')

>>> np.datetime64('2009-01-01') + np.timedelta64('nat', 'D')
np.datetime64('NaT', 'D')

年 Y 和月 M 两种时间差单位需特殊处理,因为表示的时间长度取决于使用它们的日期。一天等于24小时,但月和年不能直接转成天,除非使用 unsafe 转换。

numpy.ndarray.astype 可执行这种不安全转换;换算按400年闰年周期的平均值计算:

>>> import numpy as np

>>> a = np.timedelta64(1, 'Y')

>>> np.timedelta64(a, 'M')
numpy.timedelta64(12,'M')

>>> np.timedelta64(a, 'D')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Cannot cast NumPy timedelta64 scalar from metadata [Y] to [D] according to the rule 'same_kind'

日期时间单位

Datetime 与 Timedelta 支持大量时间单位,也支持按输入强制转换为其他单位的通用单位。但通用单位自 NumPy 2.5 起弃用,将来会报错,迁移方法见后文。

日期时间总以1970-01-01T00:00为纪元。支持日期围绕纪元形成对称区间,表中称为“时间跨度”。跨度长度等于64位整数取值范围乘以单位长度。例如周的跨度恰为日的七倍,日的跨度恰为小时的24倍。

日期单位

代码 含义 相对跨度 绝对跨度
Y 年 ±9.2e18年 [9.2e18 BC, 9.2e18 AD]
M 月 ±7.6e17年 [7.6e17 BC, 7.6e17 AD]
W 周 ±1.7e17年 [1.7e17 BC, 1.7e17 AD]
D 日 ±2.5e16年 [2.5e16 BC, 2.5e16 AD]

时间单位

代码 含义 相对跨度 绝对跨度
h 小时 ±1.0e15年 [1.0e15 BC, 1.0e15 AD]
m 分钟 ±1.7e13年 [1.7e13 BC, 1.7e13 AD]
s 秒 ±2.9e11年 [2.9e11 BC, 2.9e11 AD]
ms 毫秒 ±2.9e8年 [2.9e8 BC, 2.9e8 AD]
us / μs 微秒 ±2.9e5年 [290301 BC, 294241 AD]
ns 纳秒 ±292年 [1678 AD, 2262 AD]
ps 皮秒 ±106日 [1969 AD, 1970 AD]
fs 飞秒 ±2.6小时 [1969 AD, 1970 AD]
as 阿秒 ±9.2秒 [1969 AD, 1970 AD]

转换为 Python 对象

把 datetime64 或 timedelta64 转为 tuple、list、datetime.datetime 等 Python 对象时,NumPy 遵循严格协议:

输入类型 datetime64 转换结果 timedelta64 转换结果
NaT None None
ns/ps/fs/as int int
μs/ms/s/m/h datetime.datetime datetime.timedelta
D/W(线性单位) datetime.date datetime.timedelta
Y/M(非线性单位) datetime.date int
通用单位 datetime.date int

时间差通用单位自2.5起弃用,将来会报错。

>>> import numpy as np

>>> type(np.datetime64('NaT', 'D').item())
<class 'NoneType'>

>>> type(np.timedelta64('NaT', 'D').item())
<class 'NoneType'>

>>> type(np.timedelta64(123, 'ns').item())
<class 'int'>

>>> type(np.datetime64('2025-01-01T12:00:00.123456').item())
<class 'datetime.datetime'>

>>> type(np.timedelta64(10, 'D').item())
<class 'datetime.timedelta'>

若转为 int、float、str 等 Python 类型,对应返回 NumPy 标量类型 np.int64、np.float64、np.str_:

>>> import numpy as np

>>> type(np.timedelta64(1, 'D').astype(int))
<class 'numpy.int64'>

>>> type(np.datetime64('2025-01-01T12:00:00.123456').astype(float))
<class 'numpy.float64'>

>>> type(np.timedelta64(123, 'ns').astype(str))
<class 'numpy.str_'>

工作日功能

为让日期时间适用于每周只有某些日子有效的场景,NumPy 提供一组 busday(business day,工作日)函数。默认只有星期一至星期五有效。实现基于包含七个布尔标志的 weekmask,也可自定义其他有效日集合;还能检查特定的无效日期,即 holiday 列表。

busday_offset 对日单位 D 的日期时间应用工作日偏移:

>>> import numpy as np

>>> np.busday_offset('2011-06-23', 1)
np.datetime64('2011-06-24')

>>> np.busday_offset('2011-06-23', 2)
np.datetime64('2011-06-27')

输入落在周末或假日时,函数先根据规则把日期滚动到有效工作日,再应用偏移。默认 raise 直接报错,常用的规则是 forward 与 backward:

>>> import numpy as np

>>> np.busday_offset('2011-06-25', 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Non-business day date in busday_offset

>>> np.busday_offset('2011-06-25', 0, roll='forward')
np.datetime64('2011-06-27')

>>> np.busday_offset('2011-06-25', 2, roll='forward')
np.datetime64('2011-06-29')

>>> np.busday_offset('2011-06-25', 0, roll='backward')
np.datetime64('2011-06-24')

>>> np.busday_offset('2011-06-25', 2, roll='backward')
np.datetime64('2011-06-28')

有些问题需要恰当地组合滚动与偏移。求不早于给定日期的第一个工作日:

>>> import numpy as np

>>> np.busday_offset('2011-03-20', 0, roll='forward')
np.datetime64('2011-03-21')
>>> np.busday_offset('2011-03-22', 0, roll='forward')
np.datetime64('2011-03-22')

求严格晚于给定日期的第一个工作日:

>>> np.busday_offset('2011-03-20', 1, roll='backward')
np.datetime64('2011-03-21')
>>> np.busday_offset('2011-03-22', 1, roll='backward')
np.datetime64('2011-03-23')

函数也可计算特定节日。加拿大和美国的母亲节是五月第二个星期日,可以用自定义 weekmask 计算:

>>> import numpy as np

>>> np.busday_offset('2012-05', 1, roll='forward', weekmask='Sun')
np.datetime64('2012-05-13')

若使用固定 weekmask 与假日表批量计算且关注性能,可用 busdaycalendar,以优化形式保存所需数据。

np.is_busday()

用 is_busday 测试日期是否有效:

>>> import numpy as np

>>> np.is_busday(np.datetime64('2011-07-15'))  # a Friday
True
>>> np.is_busday(np.datetime64('2011-07-16')) # a Saturday
False
>>> np.is_busday(np.datetime64('2011-07-16'), weekmask="Sat Sun")
True
>>> a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18'))
>>> np.is_busday(a)
array([ True,  True,  True,  True,  True, False, False])

np.busday_count()

用 busday_count 计算指定日期区间的有效日数:

>>> import numpy as np

>>> np.busday_count(np.datetime64('2011-07-11'), np.datetime64('2011-07-18'))
5
>>> np.busday_count(np.datetime64('2011-07-18'), np.datetime64('2011-07-11'))
-5

若已有日期数组,希望计算其中有效日的数量:

>>> import numpy as np

>>> a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18'))
>>> np.count_nonzero(np.is_busday(a))
5

自定义 weekmask

以下几种写法都表示默认的星期一至星期五有效。位置序列必须恰好七项,按周一到周日排列;0表示无效,1表示有效。字符串形式可用“0”“1”,也可用区分大小写的 Mon Tue Wed Thu Fri Sat Sun 缩写;允许任意数量空白:

# Positional sequences; positions are Monday through Sunday.
# Length of the sequence must be exactly 7.
weekmask = [1, 1, 1, 1, 1, 0, 0]
# list or other sequence; 0 == invalid day, 1 == valid day
weekmask = "1111100"
# string '0' == invalid day, '1' == valid day

# string abbreviations from this list: Mon Tue Wed Thu Fri Sat Sun
weekmask = "Mon Tue Wed Thu Fri"
# any amount of whitespace is allowed; abbreviations are case-sensitive.
weekmask = "MonTue Wed  Thu\tFri"

datetime64 的局限

假定每天恰好86400秒,使 datetime64 大致兼容 Python datetime 和 POSIX 时间语义;因此也共享它们关于 UTC 与历史时间确定的已知局限。以下是不完全列举。

不能解析正闰秒中的有效 UTC 时间戳

2016-12-31 23:59:60 UTC 是闰秒,所以 23:59:60.450 UTC 是有效时刻,但 datetime64 无法解析:

>>> import numpy as np

>>> np.datetime64("2016-12-31 23:59:60.450")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Seconds out of range in datetime string "2016-12-31 23:59:60.450"

UTC 日期间的时间差可能相差整数个 SI 秒

计算2021-01-01 12:56:23.423 UTC 与2001-01-01 00:00:00.000 UTC 之间的 SI 秒数:

>>> import numpy as np

>>> (
...   np.datetime64("2021-01-01 12:56:23.423")
...   - np.datetime64("2001-01-01")
... ) / np.timedelta64(1, "s")
631198583.423

正确结果是 631198588.423 SI 秒,因为2001年至2021年间有五个闰秒。

历史日期间的时间差不一定是 SI 秒

计算公元0年1月1日 UT 与1600年1月1日 UT 之间的秒数。UT 指世界时:

>>> import numpy as np

>>> a = np.datetime64("0000-01-01", "us")
>>> b = np.datetime64("1600-01-01", "us")
>>> b - a
numpy.timedelta64(50491123200000000,'us')

所得 50491123200 秒,是584388天乘以86400秒;这相当于与地球自转同步的时钟所计秒数。精确 SI 秒数只能估计,例如依据 Stephenson 等人在2016年 Royal Society’s Proceedings A 472 发表的Measurement of the Earth’s rotation: 720 BC to AD 2015。合理估计为 50491112870 ± 90 秒,与上述计算相差10330秒。

通用单位弃用迁移指南

timedelta64 通用单位自 NumPy 2.5 弃用,将来会报错。本节说明如何更新使用 timedelta64 与 datetime64 通用单位的代码,避免未来错误。

最直接的做法是将通用单位替换为明确的 D、h、m、s 等单位。具体选择取决于代码语境与精度需求:

>>> import numpy as np

>>> # Old code using generic units of timedelta64
>>> np.timedelta64(5, "s") + 1
DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead.

>>> # Updated code using a specific time unit
>>> np.timedelta64(5, "s") + np.timedelta64(1, "s")
np.timedelta64(6,'s')

比较 timedelta64 时,两边应使用相同的明确单位,即使表示零:

>>> np.timedelta64(0, "s") == 0
DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `== 0`).Please use a specific unit instead.
np.True_

>>> np.timedelta64(0, "s") == np.timedelta64(0, "s")
np.True_

用 numpy.testing.assert_allclose 比较时间差时,atol 也要指定单位:

>>> arr = np.ones(5, dtype='m8[s]')
>>> np.testing.assert_allclose(arr, np.timedelta64(1, "s"), atol=np.timedelta64(0, "s"))

此前 datetime64 允许不指定单位的 NaT;随着通用单位弃用,现在必须显式给出单位,以与其他日期时间和时间差操作保持一致:

>>> np.datetime64("NAT")
DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead.

>>> np.datetime64("NAT", "D")
np.datetime64('NaT', 'D')

来源:NumPy Developers,Datetimes and timedeltas,对应 NumPy v2.5 Manual。中文翻译,示例及输出按原文保留。Copyright (c) 2005–2025 NumPy Developers,BSD 三条款许可。

原项目许可文本
Copyright (c) 2005-2025, NumPy Developers.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.

    * Neither the name of the NumPy Developers nor the names of any
       contributors may be used to endorse or promote products derived
       from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容