rrule
rrule copied to clipboard
Request For Comments: Proposed rrule 3.0 API design
The original version of this library was a port of Python's dateutil.rrule
library. The syntax was thus very "Pythonic" and not very JavaScript-y, thus leading to a lot of inconsistencies. For example:
-
rrulestr
may return an RRule or an RRuleSet, which implement different interfaces - An
RRule
orRRuleSet
instance is partially mutable (so calling some instance methods will change behavior of others), but not predictably so (so modifying instance state, egrrule.options
, may or may not have an effect on the output) - Additionally, around v2.3.1, I introduced a change to start bundling the NLP library along with the base RRule library, which more than doubled the bundle size.
Proposal for rrule 3.0
Better RFC compliance
The proposed approach centers around the options
object. Its syntax structure should be consistent with the structure of the RFC spec:
{
rrule: {
freq: RRule.WEEKLY,
interval: 5,
byweekday: [RRule.MO, RRule.FR],
until: new Date(Date.UTC(2012, 12, 31))
},
dtstart: new Date(Date.UTC(2012, 1, 1, 10, 30)),
}
Strings and options objects are interchangeable
const str = "DTSTART:20120201T023000Z
RRULE:FREQ=MONTHLY;COUNT=5
RDATE:20120701T023000Z,20120702T023000Z
EXRULE:FREQ=MONTHLY;COUNT=2
EXDATE:20120601T023000Z"
const options = rrulestr(str)
{
dtstart: '2012-02-01T10:30:00.000Z',
rrule: {
freq: RRule.MONTHLY,
count: 5,
},
rdate: [
'2012-07-01T10:30:00.000Z',
'2012-07-02T10:30:00.000Z',
],
exrule: {
freq: RRule.MONTHLY,
count: 2,
},
exdate: [
'2012-06-01T10:30:00.000Z',
]
}
all(str)
... same output as all(options)
between(str, start, end)
... same output as between(options, start, end)
Immutability
RRule should use a function-based, immutable approach, where options
objects are just plain-old JS (POJS) objects (or their string equivalents), and a suite of functions is provided to generate JS dates, which are completely deterministic (the same options
object or string always returns the same set of dates):
const options = {
dtstart: new Date(Date.UTC(2012, 1, 1, 10, 30)),
rrule: {
freq: RRule.WEEKLY,
interval: 5,
byweekday: [RRule.MO, RRule.FR],
until: new Date(Date.UTC(2012, 12, 31))
}
}
all(options)
[ '2012-02-03T10:30:00.000Z',
'2012-03-05T10:30:00.000Z',
'2012-03-09T10:30:00.000Z',
'2012-04-09T10:30:00.000Z',
'2012-04-13T10:30:00.000Z',
'2012-05-14T10:30:00.000Z',
'2012-05-18T10:30:00.000Z',
/* … */]
between(options, new Date(Date.UTC(2012, 7, 1)), new Date(Date.UTC(2012, 8, 1)))
['2012-08-27T10:30:00.000Z',
'2012-08-31T10:30:00.000Z']
toString(options)
"DTSTART:20120201T093000Z
RRULE:FREQ=WEEKLY;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR"
Question: How important is the Cache
functionality? Could it be removed from this library and replaced in client code by memoization?
Remove RRuleSet
rrulestr
will now simply return a parsed options
object. The options
object also supports the extensions formerly available only to RRuleSet
, meaning that a complete RRuleSet
can be specified declaratively in a single statement:
const options = {
dtstart: new Date(Date.UTC(2012, 1, 1, 10, 30)),
rrule: {
freq: RRule.MONTHLY,
count: 5,
},
rdate: [
new Date(Date.UTC(2012, 6, 1, 10, 30)),
new Date(Date.UTC(2012, 6, 2, 10, 30))
],
// exrule has the same dtstart as the rest of the options
exrule: {
freq: RRule.MONTHLY,
count: 2,
},
exdate: [
new Date(Date.UTC(2012, 5, 1, 10, 30)),
]
}
all(options)
[ '2012-02-01T10:30:00.000Z',
'2012-05-01T10:30:00.000Z',
'2012-07-01T10:30:00.000Z',
'2012-07-02T10:30:00.000Z' ]
between(options, new Date(Date.UTC(2012, 2, 1)), new Date(Date.UTC(2012, 6, 2)))
[ '2012-05-01T10:30:00.000Z', '2012-07-01T10:30:00.000Z' ]
toString(options)
"DTSTART:20120201T023000Z
RRULE:FREQ=MONTHLY;COUNT=5
RDATE:20120701T023000Z,20120702T023000Z
EXRULE:FREQ=MONTHLY;COUNT=2
EXDATE:20120601T023000Z"
Question: EXRULE
has been deprecated in RFC 5545. Is it important to continue supporting it?
Future enhancement: RRULE
and EXRULE
could support arrays of options
in addition to options
.
Move NLP to its own npm package
rrule-nlp provides useful, if neglected, functionality. Its methods are concerned not with describing sets of dates (the output), but describing the rrule options itself (the input). It should be treated as its own package and removed from the main rrule package in the interest of size.
I expect to have a working implementation of this proposal by February 1, 2019. I'm opening a comment period on this proposal until that time. Thank you for your interest in this library!
Question: How important is the Cache functionality? Could it be removed from this library and replaced in client code by memoization?
I’m for removing it.
Question: EXRULE has been deprecated in RFC 5545. Is it important to continue supporting it?
I’m for removing this as well.
With options
being so central and existing only as a plain object with no constructor, I'd consider exposing an explicit validate(options)
(or something along those lines).
Excellent proposal, thanks @davidgoli!
I'm not sure if this is something you'd be interested in adding to this library, but a killer feature I need (which I implemented in rSchedule) is the ability to combine/manipulate multiple RRule objects (recurrence streams).
Some examples:
- Given two RRule objects, find the intersection of them.
- Given one RRule object, add a second RRule object to it and return a stream containing the union of the two RRule objects.
- Given two RRule objects, add them together (get the union) and return a deduplicated stream of the result.
- Add three RRule objects, de-duplicate the resulting union stream, then subtract a third RRule object from that union stream.
- etc.
If you implemented operator functions like these, then someone could create their own RRuleSet
object pretty easily. All they would need to do is
- Add together all RRules designated
RRule
- Subtract all RRules designated
EXRule
- Add all dates designated
RDate
- Subtract all dates designated
EXDate
- De-duplicate the result
For me, implementing this in rrulejs might allow me to remove the recurrence logic from rSchedule--allowing me to turn the rSchedule library into a simple collection of wrapper classes (i.e. Calendar
, Schedule
, RRule
).
I tend to think of these transformations as akin to rxjs pipe transformations.
Building off the api you outlined above, an implementation might look like
all(
combineOptions(
add(rruleOptionOne, rruleOptionTwo),
subtract(exruleOption),
add(rdateOne, rdateTwo, rdateThree),
subtract(exdateOne),
unique(),
)
)
// might return:
[ '2012-02-01T10:30:00.000Z',
'2012-05-01T10:30:00.000Z',
'2012-07-01T10:30:00.000Z',
'2012-07-02T10:30:00.000Z' ]
@davidgoli This is a great libray! How is v3 coming along? Did you abandon the idea of a new API? I'm curious to know because I'm building a task scheduler based on rrule and I'd like my api to be as future proof as possible. One thing would be to not support RRuleSet in my api if you're planning to remove it anyway.
https://github.com/msageryd/node-schedule-rrule
How is v3 coming along?
For @rofrol and others, I can't immediately find the comment, but I seem to remember that @davidgoli mentioned he no longer uses rrule and is no longer actively working on it. At the moment I believe this library is not actively maintained.
What are people using instead?
What are people using instead?
rSchedule @matheusbaumgart. Though I made it, so I'm definitely biased.