日期时间和时间差#
版本 1.7.0 中的新增功能。
从 NumPy 1.7 开始,核心数组数据类型原生支持日期时间功能。数据类型称为 datetime64
,之所以这样命名是因为 datetime
已经被 Python 标准库使用。
Datetime64 约定和假设#
类似于 Python date
类,日期以当前的格里高利历表示,在未来和过去无限期扩展。 [1] 与 Python 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 等)的概念。 [2]
基本日期时间#
创建日期时间最基本的方法是从 ISO 8601 日期或日期时间格式的字符串中创建。也可以从相对于 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC)的偏移量整数中创建日期时间。内部存储的单位会根据字符串的格式自动选择,可以是 日期单位 或 时间单位。日期单位为年(‘Y’)、月(‘M’)、周(‘W’)和日(‘D’),而时间单位为小时(‘h’)、分钟(‘m’)、秒(‘s’)、毫秒(‘ms’)和一些其他的 SI 前缀秒基单位。 datetime64
数据类型还接受字符串“NAT”(以任何大小写字母组合形式)来表示“非时间”值。
示例
一个简单的 ISO 日期
>>> import numpy as np
>>> np.datetime64('2005-02-25')
np.datetime64('2005-02-25')
从一个整数和一个日期单位创建,表示自 UNIX 纪元以来的 1 年
>>> 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')
np.datetime64('NaT')
当从字符串创建日期时间数组时,仍然可以通过使用具有通用单位的日期时间类型来自动从输入中选择单位。
示例
>>> import numpy as np
>>> np.array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype='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='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
版本 1.11.0 中已弃用: NumPy 不存储时区信息。为了向后兼容性,datetime64 仍然解析时区偏移量,它通过转换为 UTC±00:00(世界标准时间)来处理。这种行为已弃用,将来会引发错误。
日期时间和时间差运算#
NumPy 允许对两个日期时间值进行减法运算,该运算会生成一个具有时间单位的数字。由于 NumPy 的核心没有物理量系统,因此创建了 timedelta64
数据类型来补充 datetime64
。 timedelta64
的参数是一个数字,表示单位数量,以及一个日期/时间单位,例如(D)天、(M)月、(Y)年、(h)小时、(m)分钟或(s)秒。 timedelta64
数据类型还接受字符串“NAT”来代替数字,表示“非时间”值。
示例
>>> import numpy as np
>>> np.timedelta64(1, 'D')
np.timedelta64(1,'D')
>>> np.timedelta64(4, 'h')
np.timedelta64(4,'h')
>>> np.timedelta64('nAt')
np.timedelta64('NaT')
日期时间和时间差协同工作,为简单的日期时间计算提供了方法。
示例
>>> 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') - np.datetime64('2009-01-01')
np.timedelta64('NaT','D')
>>> np.datetime64('2009-01-01') + np.timedelta64('nat')
np.datetime64('NaT')
有两个时间差单位(‘Y’,年和‘M’,月)被特殊对待,因为它们所代表的时间长度取决于使用它们的时间。虽然一个时间差日单位等于 24 小时,但月单位和年单位不能直接转换为天,除非使用“不安全”的转换。
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'
日期时间单位#
日期时间和时间差数据类型支持大量的時間單位,以及可以根据输入数据强制转换为其他任何单位的通用单位。
日期时间始终以 1970-01-01T00:00 纪元存储。这意味着支持的日期始终是围绕纪元的对称间隔,在下表中称为“时间跨度”。
跨度的长度是 64 位整数范围乘以日期或单位的长度。例如,‘W’(周)的时间跨度正好是 ‘D’(天)的时间跨度的 7 倍,而 ‘D’(天)的时间跨度正好是 ‘h’(小时)的时间跨度的 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] |
工作日功能#
为了让日期时间能够在只有某些星期几有效的上下文中使用,NumPy 包含了一组“busday”(工作日)函数。
busday 函数的默认值是只有星期一到星期五(通常的工作日)是有效日。实现基于一个“weekmask”,其中包含 7 个布尔标志来指示有效日;可以自定义 weekmask 来指定其他有效日集合。
“busday”函数还可以检查一个“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')
当输入日期落在周末或节假日时, busday_offset
首先应用一个规则将日期滚动到有效工作日,然后应用偏移量。默认规则是 ‘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():#
要测试 datetime64
值是否为有效日,请使用 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():#
要查找特定日期时间64 日期范围内的有效日数量,请使用 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
如果你有一个日期时间64 天值的数组,并且想要统计其中有多少是有效日,你可以这样做
示例
>>> 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#
以下是一些自定义 weekmask 值的示例。这些示例指定了星期一到星期五是有效日的“busday”默认值。
一些示例
# 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” 是一个闰秒,因此 “2016-12-31 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 日期之间的 Timedelta64 计算可能会出现整数 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 年之间有 5 个闰秒。
过去日期的 Timedelta64 计算不会像预期的那样返回 SI 秒。
示例
计算 “000-01-01 UT” 和 “1600-01-01 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 年《英国皇家学会会刊 A》第 472 期发表的“地球自转测量:公元前 720 年至公元 2015 年” 中发布的数据。一个合理的估计是 50491112870 ± 90 秒,相差 10330 秒。