日期時間與時間差#

從 NumPy 1.7 開始,核心陣列資料型態原生支援日期時間功能。此資料型態稱為 datetime64,之所以如此命名是因為 datetime 已被 Python 標準函式庫佔用。

Datetime64 慣例與假設#

類似於 Python 的 date 類別,日期以現行的格里曆表示,在未來和過去無限延伸。[1] 與僅支援西元 1 年至 9999 年範圍的 Python date 相反,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 日 UTC 時間 00:00:00)的偏移整數來建立日期時間。內部儲存的單位會自動從字串的形式選取,可以是日期單位時間單位。日期單位為年 (‘Y’)、月 (‘M’)、週 (‘W’) 和日 (‘D’),而時間單位為時 (‘h’)、分 (‘m’)、秒 (‘s’)、毫秒 (‘ms’) 和一些額外的 SI 字首秒基單位。datetime64 資料型態也接受字串 “NAT”(大小寫字母的任意組合),表示「非時間 (Not A Time)」值。

範例

簡單的 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 (Zulu 時間) 來處理。此行為已被棄用,未來將會引發錯誤。

日期時間與時間差算術#

NumPy 允許兩個日期時間值相減,此運算會產生具有時間單位的數字。由於 NumPy 的核心中沒有物理量系統,因此建立 timedelta64 資料型態以補充 datetime64timedelta64 的引數是一個數字(表示單位數量)和一個日期/時間單位,例如 (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')

有兩個時間差單位(‘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 年, 西元 9.2e18 年]

M

+/- 7.6e17 年

[西元前 7.6e17 年, 西元 7.6e17 年]

W

+/- 1.7e17 年

[西元前 1.7e17 年, 西元 1.7e17 年]

D

+/- 2.5e16 年

[西元前 2.5e16 年, 西元 2.5e16 年]

以下是時間單位

代碼

意義

時間跨度(相對)

時間跨度(絕對)

h

小時

+/- 1.0e15 年

[西元前 1.0e15 年, 西元 1.0e15 年]

m

分鐘

+/- 1.7e13 年

[西元前 1.7e13 年, 西元 1.7e13 年]

s

+/- 2.9e11 年

[西元前 2.9e11 年, 西元 2.9e11 年]

ms

毫秒

+/- 2.9e8 年

[西元前 2.9e8 年, 西元 2.9e8 年]

us / μs

微秒

+/- 2.9e5 年

[西元前 290301 年, 西元 294241 年]

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

當輸入日期落在週末或假日時,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():#

若要找出指定日期時間範圍內有多少有效日,請使用 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

自訂週遮罩#

以下是一些自訂週遮罩值的範例。這些範例指定了週一到週五作為有效日的「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 日期之間的時間差 64 計算可能會錯誤一個整數的 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 個閏秒。

  • 對於過去的日期,時間差 64 計算不會傳回預期的 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 秒。