Даты и время являются частыми элементами во множестве данных, от финансовых отчетов до экологических исследований. Для обработки этих повсеместных данных MATLAB предлагает удобные типы данных.

Тип datetime

Рассмотрим тип данных и одноименную функцию datetime:

datetime
  datetime
   15-Sep-2023 09:55:05

Итак, мы начали с функции, которая имеет такое же название, как тип datetime. Когда мы вызываем её без аргумента, как мы только что сделали, она считывает календарь и часы нашего компьютера и возвращает дату и время в виде объекта, тип которого - datetime.

Время состоит из трех частей: часов, минут и секунд, разделенных двоеточиями. Эти двоеточия не имеют никакого отношения к оператору «двоеточие» :, это обычная пунктуация.

Рассмотрим некоторые аргументы, которые можно передать в функцию datetime.

datetime('yesterday'), datetime('today'), datetime('tomorrow')
ans = 
  datetime
   14-Sep-2023

ans = 
  datetime
   15-Sep-2023

ans = 
  datetime
   16-Sep-2023

Вызов функции с аргументами yesterday, today, tomorrow возвращает дату вчерашнего, сегодняшнего и завтрашнего дня соответственно.

Также можно получить дату и время текущего момента, передав аргумент now.

datetime('now')
  datetime
   15-Sep-2023 10:05:35

Тип duration

Тип данных duration представляет собой полезный инструмент для представления продолжительности времени. Создать переменные этого типа можно с помощью функций seconds (секунды), minutes (минуты), hours (часы) или days (дни).

hours(2.5)
ans = 
  duration
   2.5 hr
days(2.5)
ans = 
  duration
   2.5 days

Примечательно, что в случае работы с типом данных duration MATLAB следит за единицами измерения. Для обычных чисел нет возможности определить единицы измерения, например, вес объекта в килограммах.

Арифметика между типами datetime и duration

Длительность можно добавить к переменным типа datetime. Создадим переменную отображающую временную метку момента её создания:

right_now = datetime('now')
 right_now = 
   datetime
    15-Sep-2023 10:42:13

Теперь вычислим дату и время, которые будут через три года от текущей временной метки right_now.

three_years_from_now = right_now + years(3)
three_years_from_now = 
  datetime
   16-Sep-2026 04:09:49

Через три дня:

three_days_from_now = right_now + days(3)
three_days_from_now = 
  datetime
   18-Sep-2023 10:42:13

Три дня назад:

three_days_ago = right_now - days(3)
three_days_ago = 
  datetime
   12-Sep-2023 10:42:13

Самое время обратить внимание на то, что названия двух функций - years (года) и days (дни), а также их родственных функций - hours (часы), minutes (минуты), seconds (секунды) - имеют множественное число. Они имеют букву s на конце. Необходимо об этом помнить, потому что существуют также функции с одноименными названиями в единственном числе, такие как day, year и second которые имеют совсем другие значения. О них мы поговорим позже.

Аргументы функции datetime

Сложение и вычитание - не единственный способ получить даты в далеком прошлом или будущем. Функция datetime также принимает аргументы, с помощью которых можно напрямую перейти к любой точке во времени.

Первое публичное представление MATLAB состоялось на заседании конференции “IEEE Conference on Decision and Control”, CDC, в Лас-Вегасе в декабре 1984 года. Конференция проходила с 12 по 14 декабря. Зададим дату начала этой конференции

matlab_debut = datetime(1984, 12, 12)
matlab_debut = 
  datetime
   12-Dec-1984

Перемирие, положившее конец боевым действиям в Первой мировой войне, было подписано в 1918 году в 11 часов 11-го дня 11-го месяца.

Armistice_WWI = datetime(1918, 11, 11, 11, 0, 0)

Но чей это был 11-й час? Если посмотреть на разные страны мира, где-то может быть 11 часов утра, а где-то — 5 часов вечера. Договор был подписан в Лондоне, и мы можем установить это время в соответствии с лондонским часовым поясом.

Чтобы установить часовой пояс, сначала нужно выяснить, в каком часовом поясе находится Лондон. Часовые пояса управляются по всему миру организацией Internet Assigned Numbers Authority, которая известна в первую очередь тем, что распределяет интернет-номера. MATLAB избавляет нас от хлопот определения часового пояса, предоставляя удобную функцию, называемую timezones.

При вызове функции timezones открывается таблица поиска мест и соответствующие им часовые пояса

Resulting browser from timezones command

Armistice_WWI.TimeZone = 'Europe/London'
Armistice_WWI = 
  datetime
   11-Nov-1918 11:00:00

Ничего не изменится, потому что для этой переменной ранее не был установлен часовой пояс. Давайте сделаем копию этой переменной. Копирование переменной включает в себя все её содержимое, так что свойство часового пояса сохранится.

Изменим часовой пояс соответствующий Москве:

Armistice_WWI_Moscow = Armistice_WWI;
Armistice_WWI_Moscow.TimeZone = 'Europe/Moscow';
Armistice_WWI_Moscow
Armistice_WWI_Moscow = 
  datetime
   11-Nov-1918 14:31:19

Чтобы просмотреть возможные поля переменной datetime:

fieldnames(Armistice_WWI)
ans =
  8×1 cell array
    {'Format'  }
    {'TimeZone'}
    {'Year'    }
    {'Month'   }
    {'Day'     }
    {'Hour'    }
    {'Minute'  }
    {'Second'  }

Следует отметить, что имена полей чувствительны к регистру, и мы не можете добавлять новые поля.

Версальский договор был подписан спустя 228 дней с момента перемирия:

Treaty_of_Versailles = Armistice_WWI + days(228)
Treaty_of_Versailles = 
  datetime
   27-Jun-1919 12:00:00

Мы уже несколько раз замечали, что время изменилось; это связано с учетом перехода на летнее время. В Европе впервые стали переходить на летнее время в 1916 году.

К счастью для нас, MATLAB позаботится об учете перехода на летнее время. Это является большим преимуществом, особенно, когда время суток имеет значение.

Настройка формата времени datetime

В случае с Версальским договором время не играет особенной роли. Так как нас интересует только дата, следует изменить форматирование используя поле Format.

Treaty_of_Versailles.Format = 'dd-MMMM-yy';
Treaty_of_Versailles
Treaty_of_Versailles = 
  datetime
   27-June-19

Теперь время не отображается. Если же мы предпочитаем более короткий формат:

Treaty_of_Versailles.Format = 'dd/MM/yy';
Treaty_of_Versailles
Treaty_of_Versailles = 
  datetime
   27/06/19

Или, может быть, еще более длинный формат:

Treaty_of_Versailles.Format = 'eeee, MMMM dd, yyyy';
Treaty_of_Versailles
Treaty_of_Versailles = 
  datetime
   Friday, June 27, 1919

Теперь мы знаем, что Версальский договор был подписан в пятницу.

Давайте посмотрим когда был 100 летний юбилей:

Treaty_of_Versailles + years(100)
ans = 
  datetime
   Wednesday, June 26, 2019

Годовщина должна быть 27-го, но почему-то мы получили 26 июня.

Почему наши расчеты отличаются на один день? Все дело в високосных годах, когда в календарь вводится дополнительный день — 29 февраля. В то же время функция years предполагает, что в каждом году ровно 365.2425 дней.

Работа с календарными промежутками времени

К счастью, MATLAB предлагает способ решить и эту проблему. Для достижения точного соответствия используйте функцию calyears, что означает “календарные годы” (calendar years). Эта функция учитывает график високосных лет.

Treaty_of_Versailles + calyears(100)
ans = 
  datetime
   Thursday, June 27, 2019

calyears является функцией возвращающей тип данных calendarDuration.

Тип данных calendarDuration используется, когда требуется вариативный промежуток времени. Например, продолжительность 1 месяца может составлять 28, 30 или 31 день. Функции caldays, calweeks, calmonths, calquarters, calyears возвращают тип данных calendarDuration.

Различия между схожими по названию функциями

Обратим внимание на функции year, month, week, day, hour, minute, second, и quarter, которые не следует путать с их множественными аналогами. Зачем они нужны и что они делают?

Эти функции появились за много лет до введения типов данных datetime и duration в 2014 году. Изначально они были частью библиотеки для решения финансовых задач, что представляет собой одно из ключевых направлений использования MATLAB.

После добавления новых типов данных, старые функции были обновлены, чтобы принимать новые типы данных datetime и duration. В связи с необходимостью новой функциональности были введены отдельные функции years, days, hours, minutes и другие, названные иначе, чтобы обеспечить совместимость с предыдущими версиями MATLAB. Эти новые функции получили имена во множественном числе для отличия от оригинальных.

Давайте рассмотрим, что делают старые функции:

year(Treaty_of_Versailles)
ans =
        1919
month(Treaty_of_Versailles)
ans =
     6
day(Treaty_of_Versailles)
ans =
    27
hour(Treaty_of_Versailles)
ans =
    12

Эти функции разделяют дату на части. Вы даже можете определить к какому кварталу года относится дата.

quarter(Treaty_of_Versailles)
ans =
     2

Есть и другой способ разделения информации - с помощью точечной нотации:

Armistice_WWI.Year, Armistice_WWI.Day, Armistice_WWI.Hour
ans =
        1918

ans =
    11

ans =
    11

Также можно воспользоваться распаковкой значений, функциями yms и hms:

[y, mo, d] = ymd(Armistice_WWI)
y =
        1918

mo =
    11

d =
    11
[h, min, s] = hms(Armistice_WWI)
h =
    11

min =
     0

s =
     0

В каждом случае значения, которые мы получили в результате этих многочисленных операций извлечения, будут типа double . Ни одна из них не вернет нам объект типа duration.

Чтобы получить объект типа duration необходимо использовать функции, именованные во множественном числе, такие как years и days. Однако существует еще одна функция, которая также может вернуть duration, и запомнить ее название довольно легко — duration (длительность).

long_movie = duration(3, 7, 43)
long_movie = 
  duration
   03:07:43

Входные параметры функции duration работают аналогично входным данным функции datetime, за исключением того, что начинаются с часов, а не с лет. Эти входные данные составляют 3 часа, 7 минут и 43 секунды. Выходные данные также похожи, но вместо запятых значения разделяются двоеточиями.

Можно также указать четвертый аргумент для duration, если необходима большая точность времени.

Операции с типом duration

Что возможно с объектом duration? Например, с ним можно производить арифметические операции. Предположим, мы хотим узнать середину длительности фильма.

half_movie = long_movie/2
half_movie = 
  duration
   01:33:51
2 * half_movie

Давайте умножим половину длительности на два, ожидаемо мы получим исходную длительность:

ans = 
  duration
   03:07:43

Вычитание тоже работает:

long_movie - half_movie 
ans = 
  duration
   01:33:51

Что случится в случае добавления единицы?

long_movie + 1
ans = 
  duration
   27:07:43

Неожиданно фильм стал действительно очень длинным. Напомним, что числа по умолчанию являются типом double. Этот пример смешанной арифметики особенно интересен, при добавление 1 к продолжительности она увеличивается на 24 часа, что предполагает, что основная единица измерения продолжительности - день, аналогично системе дат в Microsoft Excel.

Тем не менее, напрямую перевести продолжительность в тип double не получится:

double(long_movie)
Error using duration/double (line 1104)
Undefined function 'double' for input arguments of type 'duration'. To convert from durations to numeric, use the SECONDS, MINUTES, HOURS, DAYS,
or YEARS functions.

Поскольку MATLAB не может знать, в каких единицах времени мы хотим получить ответ, он не выполнит преобразование. Вместо этого необходимо использовать функции seconds, minutes, hours, days, устраняя неоднозначность намерений.

Рассмотрим пример, посчитаем продолжительность семи летней войны:

start_7_years_war = datetime(1756, 5, 17);
end_7_years_war = datetime(1763, 2, 15);
duration_7_years_war = end_7_years_war - start_7_years_war
duration_7_years_war = 
  duration
   59160:00:00

Теперь мы знаем, что семи летняя война длилась 59 160 часов. Переведем продолжительность в годы:

year_count = years(duration_7_years_war)
year_count =
    6.7489

Неполные 7 лет, узнаем насколько меньше в днях:

missing_days = days(start_7_years_war + calyears(7) - end_7_years_war)
missing_days =
    91

Важно отметить, что такое вычитание учитывает вариативное количество дней в каждом месяце.

Представим, что война началась и закончилась на год позже:

missing_days = days((start_7_years_war + calyears(1) + calyears(7)) - ...
                    (end_7_years_war + calyears(1)))
missing_days =
    92

На день больше. 1764 год был високосным, так что в феврале было 29 дней.

Но есть, что-то еще более интересное в результате, он неожиданно не содержит единицы измерения, хотя ранее мы говорили, что тип duration указывает, например, часы, минуты или недели.

Полиморфизм функций years, days, hours, minutes…

Дело в том, что days не возвращает duration, а возвращает double, в случае передачи входного аргумента типа duration.

% missing_days = days(start_7_years_war + calyears(7) - end_7_years_war)
class(start_7_years_war + calyears(7) - end_7_years_war)
class(missing_days) 
ans =
    'duration'
ans =
    'double'

С другой стороны при передачи типа double вернется тип duration.

days(2.5)
ans = 
  duration
   2.5 days

Каким образом функция может вести себя по-разному от одного вызова к другому? Дело в полиморфизме. Полиморфизм - это способность функции обрабатывать данные разных типов. Эта функция и её «родственники» — years, minutes и так далее — полиморфны. В нашем случае тип их вывода зависит от типа их ввода.

Такое решение делает функции универсальными, контекстно-интеллектуальными и на самом деле более интуитивными. При передаче в эти функции типа duration, выводится тип double, и это закладывает основу для выполнения арифметических действий (поскольку числовые значения по умолчанию double). Если мы передаем длительность (тип duration) в функцию days мы спрашиваем: «сколько дней составляет эта длительность?». Следовательно, возврат числа (тип doudle) имеет смысл. Если же мы наоборот передадим в функцию days число (тип doudle), логично ожидать продолжительность (тип duration), поскольку вы указываете «столько-то дней».

Во многом функции years, days, hours, minutes, seconds, и milliseconds, следует воспринимать, в первую очередь, как конверсию (перевод) между duration и double, и только во вторую очередь, как функции конверсии между единицами измерения времени. Полиморфизм обеспечивает возможность выполнения 12 конверсий шестью функциями.

Сравнение между временными точками

В MATLAB возможно сравнивать точки во времени с помощью операторов сравнения. Давайте рассмотрим пример. Прежде всего, давайте определим пару новых точек во времени.

catholic_christmas = datetime(2024, 12, 25);
orhtodox_christmas = datetime(2025, 01, 07);
catholic_christmas < orthodox_christmas
ans =
  logical
   1
string(ans)
ans = 
    "true"

Мы получили истинность выражения catholic_christmas < orthodox_christmas, действительно, католическое рождество наступает раньше православного.