jkbrzt / rrule

JavaScript library for working with recurrence rules for calendar dates as defined in the iCalendar RFC and more.
https://jkbrzt.github.io/rrule
Other
3.36k stars 516 forks source link

Request For Comments: Proposed rrule 3.0 API design #298

Open davidgoli opened 6 years ago

davidgoli commented 6 years ago

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:

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!

jkbrzt commented 5 years ago

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!

jorroll commented 5 years ago

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:

  1. Given two RRule objects, find the intersection of them.
  2. Given one RRule object, add a second RRule object to it and return a stream containing the union of the two RRule objects.
  3. Given two RRule objects, add them together (get the union) and return a deduplicated stream of the result.
  4. Add three RRule objects, de-duplicate the resulting union stream, then subtract a third RRule object from that union stream.
  5. 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

  1. Add together all RRules designated RRule
  2. Subtract all RRules designated EXRule
  3. Add all dates designated RDate
  4. Subtract all dates designated EXDate
  5. 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' ]
msageryd commented 4 years ago

@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

rofrol commented 3 years ago

How is v3 coming along?

jorroll commented 3 years ago

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.

matheusbaumgart commented 1 year ago

What are people using instead?

jorroll commented 1 year ago

What are people using instead?

rSchedule @matheusbaumgart. Though I made it, so I'm definitely biased.