RSchedule - это библиотека javascript, написанная на машинописном языке, для работы с повторяющимися датами / событиями. Правила можно импортировать / экспортировать в формате iCal RFC 5545, а сами объекты правил придерживаются протокола итератора javascript.

Пример использования:

const rule = new RRule({
  frequency: ‘YEARLY’,
  byMonthOfYear: [2, 6],
  byDayOfWeek: [‘SU’, [‘MO’, 3]],
  start: new Date(2010,1,7),
}, {
  dateAdapter: StandardDateAdapter
})
let index = 0;
for (const date of rule.occurrences()) {
  date.toISOString()
  index++
 
  if (index > 10) break;
}
rule.occurrences({
  start: new Date(2010,5,7),
  take: 5
})
  .toArray()
  .map(date => date.toISOString())

rSchedule использует довольно простой объект-оболочку DateAdapter, который абстрагируется от отдельных реализаций библиотеки дат, что делает библиотеку дат rSchedule независимой. Если выбранный вами DateAdapter поддерживает часовые пояса, rSchedule поддерживает часовые пояса.

В настоящее время существуют пакеты StandardDateAdapter, LuxonDateAdapter, MomentDateAdapter и MomentTZDateAdapter, которые предоставляют оболочку, совместимую с DateAdapter, для стандартного объекта Date javascript, а также объектов момент, часовой пояс и люксон. Кроме того, вам должно быть довольно легко создать собственный DateAdapter для предпочитаемой библиотеки.

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

Операторы потока событий

Не углубляясь слишком глубоко в библиотеку, я хочу выделить одну очень интересную функцию, которая у нее есть: операторы потока вхождений.

Операторы потока вхождений вдохновлены операторами конвейера rxjs, и они позволяют комбинировать и управлять потоками вхождений из разных объектов.

Пример из моего собственного приложения:

Я хочу получить расписание волонтеров для конкретного волонтера. График волонтеров - это * пересечение * расписания, на которое подписывается человек, и графика появления соответствующей возможности для волонтеров.

Другими словами, у волонтерской возможности есть свой собственный график. Допустим: каждый другой вторник, а также каждую пятницу. Отдельно кто-то может записаться на волонтерство «каждый вторник», а также в несколько конкретных пятниц. Однако дни, когда человек * на самом деле * собирается стать волонтером, являются пересечением этих двух графиков.

Вот как можно построить это новое расписание с помощью rSchedule:

declare const volunteerAvailability: Schedule[];
declare const opportunitySchedule: Calendar;

const volunteerSchedule = new Calendar().pipe(
    add(...volunteerAvailability),
    unique(),
    intersection({
      streams: opportunitySchedule
    })
  );

Разбивая этот пример:

  • volunteerAvailability содержит массив различных расписаний, когда доброволец указал, что они доступны.
  • opportunitySchedule содержит календарь, описывающий, когда действительно появляется возможность стать волонтером.

Мы хотим создать календарь, содержащий время, когда волонтер доступен для участия в этой возможности для волонтеров (volunteerSchedule).

const volunteerSchedule =

  // create a new calendar
  new Calendar().pipe(

    // add all times that the volunteer is available, in general
    add(...volunteerAvailability),

    // filter to get only unique times
    unique(),

    // get the intersection of these times with the
    // volunteer opportunity's schedule
    intersection({
      streams: opportunitySchedule
    })
  );

Полученный volunteerSchedule можно повторить с помощью volunteerSchedule.occurrences(). Я также могу легко группировать вхождения по месяцам, используя volunteerSchedule.collections({granularity: 'MONTHLY'}), и перебирать по месяцам.

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

Например:

declare const volunteerSchedules: Calendar[];

const scheduleOfAllVolunteers =
  new Calendar({ schedules: volunteerSchedules });

Я полагаю, что большинству пользователей не понадобятся такие мощные инструменты, и для них они могут просто использовать предоставленный объект Schedule, который реализует RRULE, EXRULE, RDATE и EXDATE из ICAL spec (который также упрощает синтаксический анализ / сериализацию строк ICAL).

Например:

const schedule = new Schedule({
 rrules: [
   {
     frequency: ‘WEEKLY’,
     start: new Date(2012, 5, 24),
     end: new Date(2012, 11, 31)
   },
   {
     frequency: ‘DAILY’,
     start: new Date(2011, 9, 2)
   }
 ],
 dateAdapter: StandardDateAdapter,
 data: ‘Holds anything I want’,
})
schedule
  .occurrences({take: 10})
  .toArray()
  .map(date => date.toISOString())
// or
const schedule = parseICal(icalString);

Чтобы узнать больше, загляните в репо rSchedule.