Внимание, мы переехали! Если вы хотите и дальше следить за последними техническими новостями Square, посетите наш новый дом https://developer.squareup.com/blog

В команде Square Appointments нам часто приходится иметь дело с повторяющимися событиями - от простых случаев (например, еженедельное обеденное собрание) до более сложных (например, стрижка раз в два месяца в предпоследний вторник, за исключением 19 сентября. ). К счастью, спецификация iCalendar предоставляет компактный способ представить все эти случаи. Задача для нас состоит в том, чтобы превратить текст RRULE в набор фактических дат, которые мы можем использовать в нашем приложении. Учитывая обширную экосистему драгоценных камней Ruby, мы ожидали, что сразу же найдем что-то для выполнения этой задачи за нас, но не смогли найти ничего, что было бы именно тем, что нам было нужно.

Существующие инструменты Ruby не совсем подходили

Экосистема Ruby уже содержит отличные жемчужины, которые имеют дело с повторениями - ice_cube, вероятно, является самым популярным. ice_cube позволяет создавать повторения через свободный интерфейс Ruby и может преобразовывать повторения, созданные в Ruby, в его текстовый эквивалент RRULE. К сожалению для нас, наша проблема немного в другом. В первую очередь мы обрабатываем RRULE, встраиваемые в повторяющиеся события, импортированные из Календаря Google, и превращаем их в набор Time объектов Ruby - по сути, проблема, обратная той, для решения которой предназначена ice_cube. ice_cube действительно предоставляет некоторую возможность синтаксического анализа RRULE, но не поддерживает некоторые параметры (например, BYSETPOS, возможность указывать такие вещи, как каждую вторую и четвертую среду в месяце), и мы не видели способа включить функции, которые мы нужен без значительной переделки камня. Поскольку мы имеем дело с внешними данными от Google, которые не находятся под нашим контролем и потенциально могут содержать любые действительные RRULE, нам нужно было поддерживать как можно ближе к полному набору спецификации iCalendar.

Были были библиотеки, которые делали именно то, что нам нужно, но, к сожалению, они были на Python и JS. И если вы когда-либо писали код даты / времени, вы знаете, что очень легко ошибиться - очень много ошибок раз за разом (ваш список месяцев 0-индексируется или 1-индексируется?). Переписать одну из этих библиотек в Ruby без малозаметных ошибок казалось непростой задачей.

Исчерпывающие тесты спешат на помощь

К счастью, мы пытались решить четко ограниченную задачу, которая вполне подходила для набора тестов на основе данных - и у нас был уже написанный набор тестов, которые наша мобильная команда использовала за несколько месяцев до этого, чтобы протестировать еще одну реализацию синтаксического анализа RRULE. в Objective-C. Этот набор тестов охватил практически все возможные комбинации вариантов RRULE.

Этот набор тестов вселил в нас уверенность в том, что мы можем поэтапно переписать библиотеку Python dateutil (добавив некоторые идиомы Ruby, когда они были явно применимы). Признаюсь, когда я писал эту первую версию нашей новой библиотеки, я не понимал большей части того, что транскрибировал, но это не имело значения. Пока проходили тесты, я знал, что это работает. Как только мы перешли на «зеленый», мы смогли провести рефакторинг и превратить его во что-то более понятное и идиоматически Ruby, будучи уверенными, что наши обширные тесты защитят нас.

Конечный продукт

Мы только что выпустили нашу внутреннюю библиотеку rrule - маленькую жемчужину, полностью сосредоточенную на конкретной задаче создания Time экземпляров из заданного RRULE и даты начала повторения. Это позволяет делать такие вещи, как:

rule = RRule::Rule.new(‘FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH’, dtstart: Time.parse(‘Tue Sep 2 06:00:00 PDT 1997’), tzid: ‘America/New_York’)
rule.all
=> [Tue, 02 Sep 1997 09:00:00 EDT -04:00,
Thu, 04 Sep 1997 09:00:00 EDT -04:00,
Tue, 16 Sep 1997 09:00:00 EDT -04:00,
Thu, 18 Sep 1997 09:00:00 EDT -04:00,
Tue, 30 Sep 1997 09:00:00 EDT -04:00,
Thu, 02 Oct 1997 09:00:00 EDT -04:00,
Tue, 14 Oct 1997 09:00:00 EDT -04:00,
Thu, 16 Oct 1997 09:00:00 EDT -04:00]

Мы уже около года используем его в продакшене в Square, поэтому уверены в его точности. Прямо сейчас он поддерживает все случаи, с которыми мы сталкивались в данных Google, но мы хотели бы в конечном итоге пойти дальше и поддержать всю спецификацию iCalendar (которая может указывать повторения с точностью до секунд). Если вы пишете приложение Ruby и имеете дело с RRULE, мы надеемся, что rrule может быть вам полезен. Вы можете найти исходный код на https://github.com/square/ruby-rrule.