loading...

rSchedule: a javascript recurring dates library

johncarroll profile image John Carroll Updated on ・3 min read

rSchedule is a javascript library, written in typescript, for working with recurring dates and ICal recurrences. Rules can be imported / exported in iCalendar RFC 5545 format, and Rule objects themselves adhere to the javascript iterator protocol.

Example usage:

const rule = new Rule({
  frequency: 'YEARLY',
  byMonthOfYear: [2, 6],
  byDayOfWeek: ['SU', ['MO', 3]],
  start: new Date(2010,1,7),
})

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 makes use of a fairly simple DateAdapter wrapper object which abstracts away from individual date library implementations, making rSchedule date library agnostic. If your chosen DateAdapter supports time zones, rSchedule supports time zones.

StandardDateAdapter, LuxonDateAdapter, MomentDateAdapter, and MomentTZDateAdapter packages currently exists which provide a DateAdapter complient wrapper for the standard javascript Date object, as well as moment, moment-timezone, and luxon DateTime objects. Additionally, it should be pretty easy for you to create your own DateAdapter for your preferred library.

rSchedule has been coded from scratch to facilitate the creation of complex recurring schedules and there's a lot packed in. For a complete overview, check out the project on Gitlab.

Occurrence Stream Operators

Without delving too deeply into the library, I want to call out one, very cool feature which it has: occurrence stream operators.

Occurrence stream operators are inspired by rxjs pipe operators, and they allow you to combine and manipulate occurrence streams from different objects.

An example from my own app:

I want to get the volunteer schedule for a particular volunteer. A volunteer schedule is the intersection of the schedule a person signs up for and the occurrence schedule of the associated volunteer opportunity.

Put another way, a volunteer opportunity has its own occurrence schedule. Lets say: every other Tuesday as well as every Friday. Separately, someone might sign up to volunteer "every tuesday", as well as a few, specific, Fridays. The days that an individual is actually going to volunteer though, are the intersection of these two schedules.

Here's how you could build this new schedule using rSchedule:

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

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

Breaking this example down:

  • volunteerAvailability contains an array different schedules when a volunteer has indicated that they are available.
  • opportunitySchedule contains a calendar describing when the volunteer opportunity actually occurs.

We want to create a calendar containing the times when the volunteer is available to participate in this volunteer opportunity (the 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
    })
  );

The resulting volunteerSchedule can be iterated over using volunteerSchedule.occurrences(). I can also easily group occurrences by month using volunteerSchedule.collections({granularity: 'MONTHLY'}) and iterate over the months.

Considering each volunteer that signs up will have their own schedule, I can then combine each of these schedules together to create a new calendar containing the dates which every person is scheduled to volunteer.

For example:

declare const volunteerSchedules: Calendar[];

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

I imagine most users will not need such powerful tools, and for them they can just use the provided Schedule object which implements RRULE, EXRULE, RDATE, and EXDATE from the ICAL spec.

For example:

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)
    }
  ],
  data: 'Holds anything I want',
})

schedule
  .occurrences({take: 10})
  .toArray()
  .map(date => date.toISOString())

To learn more, check out the rSchedule repo.

Posted on by:

Discussion

pic
Editor guide
 

why not use github for this package?

 

The main reason is that Gitlab itself is open source and Github isn't. Beyond that, I'm not as familiar with Github (from the maintainer's perspective) so I'm not sure how the features compare.