日期时间和时间差#

在 1.7.0 版本加入.

从 NumPy 1.7 开始,有一些核心数组数据类型原生支持日期时间功能.该数据类型称为 datetime64,之所以这样命名是因为 datetime 已经被 Python 标准库占用.

Datetime64 约定和假设#

类似于 Python 的 date 类,日期在当前的公历中表示,无限期地向未来和过去延伸. [1] 与只支持 1 AD — 9999 AD 年份范围的 Python date 不同,`datetime64` 还允许 BC 日期;BC 年份遵循 天文年份编号 惯例,即 2 BC 年份编号为 −1,1 BC 年份编号为 0,1 AD 年份编号为 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]')

datetime 类型与许多常见的 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]')

datetime 对象表示一个特定的时间点.如果两个 datetime 对象具有不同的单位,它们可能仍然表示相同的时间点,并且从较大的单位(如月)转换为较小的单位(如天)被认为是’安全’的转换,因为时间点仍然被精确地表示.

示例

>>> 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)ay, (M)onth, (Y)ear, (h)ours, (m)inutes, 或 (s)econds.`timedelta64` 数据类型还接受字符串 “NAT” 代替数字,表示 “Not A Time” 值.

示例

>>> 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')

有两种 Timedelta 单位(’Y’,年 和 ‘M’,月)被特殊处理,因为它们代表的时间量取决于使用的时间.虽然一个 Timedelta 天单位相当于 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'

日期时间单位#

Datetime 和 Timedelta 数据类型支持大量时间单位,以及可以根据输入数据强制转换为其他单位的通用单位.

日期时间总是以 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

minute

+/- 1.7e13 年

[1.7e13 BC, 1.7e13 AD]

s

second

+/- 2.9e11 年

[2.9e11 BC, 2.9e11 AD]

ms

毫秒

+/- 2.9e8 年

[ 2.9e8 BC, 2.9e8 AD]

微秒

微秒

+/- 2.9e5 年

[290301 BC, 294241 AD]

ns

纳秒

+/- 292 年

[ 1678 年, 2262 年]

ps

皮秒

+/- 106 天

[ 1969 年, 1970 年]

fs

飞秒

+/- 2.6 小时

[ 1969 年, 1970 年]

as

阿秒

+/- 9.2 秒

[ 1969 年, 1970 年]

工作日功能#

为了允许在仅某些工作日有效的上下文中使用日期时间,NumPy 包含了一组”busday”(工作日)函数.

busday 函数的默认设置是只有周一到周五(通常的工作日)是有效天数.实现基于一个包含 7 个布尔标志的”周掩码”来指示有效天数;可以指定其他有效天数集合的自定义周掩码.

“busday” 函数还可以检查一个”假期”日期列表,这些是无效的特定日期.

函数 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')

当输入日期落在周末或节假日时,:func: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')

该函数对于计算某些类型的日子(如假期)也很有用.在加拿大和美国,母亲节在五月的第二个星期天,这可以通过自定义的星期掩码来计算.

示例

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

当处理大量业务日期时,性能对于使用特定的一组周掩码和假期非常重要,有一个对象 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():#

要查找指定 datetime64 日期范围内有多少个有效天数,请使用 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

如果你有一个包含 datetime64 天值的数组,并且你想统计其中有多少是有效日期,你可以这样做:

示例

>>> 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

自定义周掩码#

以下是几个自定义周掩码值的示例.这些示例指定了”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 国际秒,因为在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 秒的精确值只能估计,例如,使用 Measurement of the Earth’s rotation: 720 BC to AD 2015, 2016, Royal Society’s Proceedings A 472, by Stephenson et.al. 中发布的数据.一个合理的估计是 50491112870 ± 90 秒,差异为 10330 秒.