Даты и временные интервалы#
Начиная с 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 (00:00:00 UTC 1 января 1970 года). Единица для внутреннего хранения автоматически выбирается из формы строки и может быть либо единица даты или
единица времени. Единицы даты - годы (‘Y’),
месяцы (‘M’), недели (‘W’) и дни (‘D’), а единицы времени -
часы (‘h’), минуты (‘m’), секунды (‘s’), миллисекунды (‘ms’) и
некоторые дополнительные единицы на основе секунд с префиксами СИ. datetime64 тип данных также принимает строку «NAT» в любом сочетании строчных/прописных букв для значения «Not A Time». Также поддерживается строка «now», возвращающая текущее время UTC. По умолчанию используется точность секунд ('s'), но можно указать другую единицу (например, 'M', 'D', 'h') для усечения результата до этой точности. Поддерживаются единицы мельче секунд (такие как 'ms' или 'ns'), но они будут показывать дробные части как нули, фактически усекая до целых секунд. Также поддерживается строка «today», возвращающая текущую дату UTC с точностью до дня. Она также поддерживает те же спецификаторы точности, что и now.
Пример
Простая дата ISO:
>>> import numpy as np
>>> np.datetime64('2005-02-25')
np.datetime64('2005-02-25')
Из целого числа и единицы даты, 1 год с эпохи UNIX:
>>> np.datetime64(1, 'Y')
np.datetime64('1971')
Использование месяцев в качестве единицы:
>>> np.datetime64('2005-02')
np.datetime64('2005-02')
Указание только месяца, но с принудительной единицей измерения 'days':
>>> 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')
Текущее время (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')
Текущая дата:
>>> np.datetime64('today')
np.datetime64('2025-08-05') # result will depend on the current date
При создании массива дат из строки всё ещё возможно автоматически выбрать единицу измерения из входных данных, используя тип datetime с общими единицами.
Пример
>>> 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 (время зулу). Это поведение устарело и будет вызывать ошибку в будущем.
Арифметика datetime и timedelta#
NumPy позволяет вычитание двух значений даты и времени, операция, которая
производит число с единицей времени. Поскольку NumPy не имеет системы
физических величин в своем ядре, timedelta64 тип данных был создан
для дополнения datetime64. Аргументы для timedelta64 являются числом, представляющим количество единиц, и единицей даты/времени, такой как (D)ень, (M)есяц, (Y)од, (h)асы, (m)инуты или (s)екунды. 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')
Datetimes и Timedeltas работают вместе, предоставляя способы для простых вычислений с датами и временем.
Пример
>>> 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 часам, единицы месяца и года не могут быть преобразованы напрямую в дни без использования 'небезопасного' приведения.
The 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 "" , line 1, in
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' (неделя) ровно в 7 раз длиннее временного интервала для 'D' (день), а временной интервал для 'D' (день) ровно в 24 раза длиннее временного интервала для 'h' (час).
Вот единицы даты:
Код |
Значение |
Временной интервал (относительный) |
Временной интервал (абсолютный) |
|---|---|---|---|
Y |
год |
+/- 9.2e18 лет |
[9.2e18 до н.э., 9.2e18 н.э.] |
M |
month |
+/- 7.6e17 лет |
[7.6e17 до н.э., 7.6e17 н.э.] |
W |
неделя |
+/- 1.7e17 лет |
[1.7e17 до н.э., 1.7e17 н.э.] |
D |
day |
+/- 2.5e16 лет |
[2.5e16 до н.э., 2.5e16 н.э.] |
А вот единицы времени:
Код |
Значение |
Временной интервал (относительный) |
Временной интервал (абсолютный) |
|---|---|---|---|
h |
hour |
+/- 1.0e15 лет |
[1.0e15 до н.э., 1.0e15 н.э.] |
m |
минута |
+/- 1.7e13 лет |
[1.7e13 до н.э., 1.7e13 н.э.] |
s |
второй |
+/- 2.9e11 лет |
[2.9e11 до н.э., 2.9e11 н.э.] |
мс |
миллисекунда |
+/- 2.9e8 лет |
[ 2.9e8 до н.э., 2.9e8 н.э.] |
мкс / мкс |
микросекунда |
+/- 2.9e5 лет |
[290301 BC, 294241 AD] |
нс |
наносекунда |
+/- 292 года |
[ 1678 AD, 2262 AD] |
ps |
пикосекунда |
+/- 106 дней |
[ 1969 AD, 1970 AD] |
fs |
фемтосекунда |
+/- 2.6 часа |
[ 1969 AD, 1970 AD] |
как |
аттосекунда |
+/- 9.2 секунды |
[ 1969 AD, 1970 AD] |
Преобразование datetime и timedelta в Python Object#
NumPy следует строгому протоколу при преобразовании datetime64 и/или timedelta64 в объекты Python (например, tuple, list, datetime.datetime).
Протокол описан в следующей таблице:
Тип ввода |
для |
для |
|---|---|---|
|
|
|
ns/ps/fs/as |
|
|
мкс/мс/с/м/ч |
||
D/W (линейные единицы) |
||
Y/M (Нелинейные единицы) |
|
|
Общие единицы измерения |
|
Пример
>>> import numpy as np
>>> type(np.datetime64('NaT').item())
>>> type(np.timedelta64('NaT').item())
>>> type(np.timedelta64(123, 'ns').item())
>>> type(np.datetime64('2025-01-01T12:00:00.123456').item())
>>> type(np.timedelta64(10, 'D').item())
В случае преобразования datetime64 и/или timedelta64 выполняется для типов Python, таких как int, float, и str соответствующие типы возвращаемых значений будут np.str_, np.int64 и np.float64.
Пример
>>> import numpy as np
>>> type(np.timedelta64(1, 'D').astype(int))
>>> type(np.datetime64('2025-01-01T12:00:00.123456').astype(float))
>>> type(np.timedelta64(123, 'ns').astype(str))
Функциональность рабочих дней#
Чтобы позволить использовать дату и время в контекстах, где действительны только определённые дни недели, NumPy включает набор функций 'busday' (рабочий день).
По умолчанию для функций busday допустимыми днями являются только с понедельника по пятницу (обычные рабочие дни). Реализация основана на «weekmask», содержащем 7 булевых флагов для указания допустимых дней; возможны пользовательские weekmask, определяющие другие наборы допустимых дней.
Функции "рабочего дня" могут дополнительно проверять список дат "праздников", конкретных дат, которые не являются действительными днями.
Функция 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 "" , line 1, in
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')
В некоторых случаях необходимо соответствующее использование roll и offset для получения желаемого ответа.
Пример
Первый рабочий день в указанную дату или после нее:
>>> 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')
Когда производительность важна для обработки множества бизнес-дат с определенным выбором weekmask и holidays, существует объект 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
Пользовательские маски недель#
Вот несколько примеров пользовательских значений weekmask. Эти примеры задают "рабочий день" по умолчанию с понедельника по пятницу как действительные дни.
Некоторые примеры:
# 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 "
" , line 1, inValueError: Seconds out of range in datetime string "2016-12-31 23:59:60.450" Вычисления Timedelta64 между двумя датами UTC могут быть ошибочны на целое количество секунд СИ.
Пример
Вычислить количество секунд СИ между "2021-01-01 12:56:23.423 UTC" и "2001-01-01 00:00:00.000 UTC":
>>> 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 для дат в прошлом не возвращают секунды СИ, как можно было бы ожидать.
Пример
Вычислить количество секунд между "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 секунд; это количество секунд часов, синхронизированных с вращением Земли. Точное значение в секундах СИ можно только оценить, например, используя данные, опубликованные в Измерение вращения Земли: 720 до н.э. до 2015 н.э., 2016, Proceedings of the Royal Society A 472, Stephenson et.al.. Разумная оценка — 50491112870 ± 90 секунд, с разницей в 10330 секунд.