Для протокола: заголовок я позаимствовал у Цицерона, в Oratio in Catilinam Prima in Senatu Habita.
В реальной жизни мы часто имеем дело с временными интервалами. Свиданки с зубным врачом, бронирование гостиничных номеров, даже ежедневный обеденный перерыв: планирование всего этогозадача подгонки временного интервала в ряд других временных интервалов.
Допустим, нам пора навестить стоматолога. Я знаю, что мне нужен 1 час для ежегодной плановой проверки. Я могу посетить врача во время обеда или после работы. У доктора есть и другие пациенты. На диаграмме ниже мои рабочие часы показаны фиолетовым цветом, докторскиекрасным, нерабочиесерым, а счастливо найденный слот, когда мы оба свободны, зеленым.
Беглого взгляда на эту диаграмму достаточно, чтобы определить время назначенной встречи. Завтра, в обеденное время. Легко, да?
К сожалению, ни один из известных мне языков программирования не предоставляет возможности определить это программно. Последние несколько лет я состою в тесных отношениях с эликсиром, поэтому пришлось написать библиотеку, решающую именно эту проблему.
Итак, встречайте Tempus
!
Детали реализации
Сущность, вокруг которой построена вся библиотека
Slot
. Он представляет временной интервал самым
естественным и простым способом: это структура, с полями
from
и to
, оба типа
DateTime
. Набор слотов хранится в структуре
Slots
, под капотом реализованной как AVLTree
. Этот выбор был сделан для
того, чтобы сохранить базовый список слотов согласованным
(упорядоченным и не перекрывающимся) с наименьшими затратами при
оптимизации доступа по чтению и записи. Обычно список слотов
заполняется при инициализации и потом используется для проверки,
поиска свободных интервалов, и тому подобного.
Функция Slots.add/2
добавит в список
слотовновый, объединяя слоты по мере необходимости. Это
позволяет просто вставлять новые слоты в конструкцию, не беспокоясь
о порядке и перекрытии. Также предусмотрена вспомогательная функция
Slots.merge/2
для объединения двух
наборов слотов. Последнее особенно удобно, когда нужно, например,
найти пустой слот в обеих сериях, как в примере с выбором времени
посещения дантиста выше.
Можно также проверить любое время на предмет занятости при
помощи функций их модуля Slot
: покрывает ли слот заданное
время (или другой слот), являются ли слоты непересекающимися, или
нет и т. д.
Модуль Tempus
Основной модуль экспортирует функции для работы со слотами, как с прерывистой временной шкалой. Можно добавить любой произвольный интервал времени к началу координат, принимая во внимание слоты; проверить, свободен ли этот интервал, или уже занят слотами, получить следующий свободный или следующий занятый слот, инвертировать временную шкалу, и многое другое.
Вот незамысловатый пример из тестов:
slots = [ Tempus.Slot.wrap(~D|2020-08-07|), # whole day %Tempus.Slot{ from: ~U|2020-08-08 01:01:00Z|, # one minute to: ~U|2020-08-08 01:02:00Z| }, %Tempus.Slot{ from: ~U|2020-08-08 01:03:00Z|, # one minute to: ~U|2020-08-08 01:04:00Z| } ] |> Enum.into(%Tempus.Slots{})
Если добавить 0
секунд ко времени, уже занятому
слотом, вернется первое доступное время после занятого
промежутка.
Tempus.add(slots, ~U|2020-08-08 01:01:30Z|, 0, :second)# ~U[2020-08-08 01:02:00Z]
Добавив 70 секунд ко времени, на пять секунд предшествующему
первому занятому слоту~U[2020-08-08 01:00:55Z]
вернется
экземпляр DateTime
через пять секунд после второго
занятого слота (5sec + занято + 60sec + занято +
5sec
):
Tempus.add(slots, ~U|2020-08-08 01:00:55Z|, 70, :second)# ~U[2020-08-08 01:04:05Z]
И так далее. Разумеется, можно добавлять и отрицательные значения.
Слияние наборов слотов
Slots.merge/2
понимает Stream
в
качестве второго аргумента. На данный момент библиотека не
поддерживает слияние и использование двух потоков слотов, но
вливание потока в существующие временные интервалы возможно. Это
может быть полезно, когда у нас есть короткий список, скажем,
праздников, и мы хотим объединить его с повторяющимися слотами,
например с выходными.
Все функции, возвращающие Slots
и / или
Slot
гарантированно отдают допустимые объекты
(нормализованные, упорядоченные и объединенные по мере
необходимости).
Что еще?
Текущая реализация покрывает только наши внутренние потребности, поэтому я был бы рад услышать о том, чего не хватает этой библиотеке и что следует в нее добавить.
Удачных временных интервалов!