rSchedule: a javascript recurring dates library
John Carroll
Posted on September 3, 2018
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 September 3, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.