rrecur v2.0.0
rrecur
Convert between RFC2445 RRULE and its JSON equivalent AND see the future (and past). Useful with rrule.js
.
If you want UIs like these:
Google Calendar: http://imgur.com/a/gT7Af
Thunderbird Calendar: http://imgur.com/a/LhnWU
Kendo Calendar: http://imgur.com/a/zVLyg
You need a schedule-logic library like this to actually interpret RRULE
s and schedule the events.
Usage
parse & stringify
This snippet will work both in the Browser and with Node.js (hence the scary bit at the bottom).
From JSON to RRULE
(function (exports) {
'use strict';
var Rrecur = exports.Rrecur || require('rrecur').Rrecur
, rfcString
;
// every other month on the first and last sunday
rfcString = Rrecur.stringify({
"freq": "monthly"
, "interval": "2"
, "count": "10"
, "byday": ["1su","-1su"]
});
}('undefined' !== typeof exports && exports || new Function('return this')()));
From RRULE to JSON
(function (exports) {
'use strict';
var Rrecur = exports.Rrecur || require('rrecur').Rrecur
, rfcString
, rruleObject
;
rfcString = "RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU";
rruleObject = Rrecur.parse(rfcString);
// Also supports sans-RRULE prefix plus DTSTART for the sake of `rrule.js` compatability
rfcString = "DTSTART=20140616T103000Z;FREQ=DAILY;BYHOUR=10;BYMINUTE=30;BYSECOND=0;UNTIL=20150616T153000Z";
rruleObject = Rrecur.parse(rfcString);
}('undefined' !== typeof exports && exports || new Function('return this')()));
next occurrence of an event
Scenario: I want an alarm to go off every day at 10:30am, given my location.
Since we can't specify timezones with a JavaScript date object, we pretend internally that local time is zoneless time. UTC is handled exactly as UTC.
So let's say it's 8:00am on our server in Utah on the first day of summer and we want to set an alarm for 10:30am in New York (30 minutes from now) every mon, wed, fri:
var rrecur
;
rrecur = Rrecur.create({
dtstart: {
zoneless: Rrecur.toLocaleISOString(new Date(2014,06,21, 10,30,0), "GMT-0400 (EDT)")
// OR utc: new Date(2014,06,21, 10,30,0).toISOString()
, locale: "GMT-0400 (EDT)"
}
, rrule: {
freq: 'daily'
, until: Rrecur.toAdjustedISOString(new Date(2014,06,22, 10,30,0), "GMT-0400 (EDT)")
, count: 1
, byhour: [10]
, byminute: [30]
, byday: ['mo','we','fr'] // Or [Rrecur.weekdays[1], Rrecur.weekdays[3], Rrecur.weekdays[5]]
// `bysecond` will default to 00, since that's what's specified in `dtstart`
}
}, new Date());
rrecur.next(); // 2014-05-21T10:30:00.000-0400
If you didn't specify locale
then you would get back a time in UTC
or in GMT-0600 (MDT)
that you would need to manually adjust.
Whether you specify zoneless
or utc
, you must still specify locale
.
one-time events
var rrecur
;
rrecur = Rrecur.create({
dtstart: {
locale: new Date(2014,06,21, 10,30,0).toString()
}
, rrule: null
}, new Date());
// Assuming you specified the Date on a machine running on MDT
rrecur.next(); // 2014-07-21T16:30:00.000-0000
For one-time events you may simply use a locale string, which will be converted
into to both zoneless
and utc
for internal purposes.
Also, rrule
will be automatically populated to match the zoneless
dtstart.
Installation
browser
NOTE: you only need rrecur.js
for the basic JSON <-> RRULE
conversion.
If you want to actually find the occurances you'll need
underscore.js
, rrule.js
, and moment.js
, and rrecur-iterator.js
.
However, in some future version I may be able to eliminate underscore.js
and rrule.js
.
via bower
bower install rrecur
via download
wget https://raw2.github.com/coolaj86/rrecurjs/master/rrecur.js
wget https://raw2.github.com/coolaj86/rrecurjs/master/rrecur-iterator.js
and insert the script tag, of course
<script src="underscore.js"></script>
<script src="rrule.js"></script>
<script src="moment.js"></script>
<script src="rrecur.js"></script>
<script src="rrecur-iterator.js"></script>
script(src="underscore.js")
script(src="rrule.js")
script(src="moment.js")
script(src="rrecur.js")
script(src="rrecur-iterator.js")
I know, it's a lot of dependencies... but that's just how it is.
In a future version it may be reasonable to drop underscore
and rrule
(or at least substitute lodash
for underscore),
but moment
is a must. JavaScript's Date object is just too messed up.
node.js
npm install rrecur
API
Convert between RFC2445 RRULE and its JSON equivalent.
Rrecur.parse(rruleStr)
- parses a string rrule (allows non-standarddtstart
in string)Rrecur.stringify(rruleObj)
- stringifies an rrule object (allows non-standarddtstart
)
Find the next (or previous) occurence of an event in an rrule chain.
Rrecur.create(rrule, localeDateString)
- creates a wrapped instance fromrrule.js
from an rrule object or stringlocaleDateString
- a string such asWed Jul 16 2014 10:30:00 GMT-0400 (EDT)
Rrecur#previous()
- cycles backwards through time to find a previous instance up todtstart
Rrecur#next()
- cycles forwards through time to find the next instance up tountil
orcount
Utility functions
Rrecur.toLocaleISOString(date, [locale])
- ouput an ISO string with timezone information2014-06-21T10:00:00.000-0600
instead ofSat Jun 21 2014 10:15:08 GMT-0600 (MDT)
or2014-06-21T16:00:00.000Z
- If
locale
is specified in a format such as-04:00
orGMT-0400 (EDT)
, the local (not UTC) time is still used, but the offset is replaced with the supplied locale.
Rrecur.toAdjustedISOString(date, locale)
date
- A local date object (with the wrong timezone)locale
A JavaScript Locale string (or Date string) with the desired timezone- returns a UTC string adjusted to accurately represent the desired timezone
RRULEs
You put in an object like this:
{ "freq": "weekly"
, "until": "2015-01-01T10:30:00.000Z"
, "byday": ["tu", "th"]
, "byhour": [4]
, "byminute": [30]
}
freq
-yearly|monthly|weekly|daily|hourly|minutely|secondly
interval
- bi-weekly, tri-weekly, etcbymonth
byweekno
byyearday
bymonthday
byday
- 0-7, su,mo,tu,we,th,fr,sa,subyhour
byminute
bysecond
until
- seems that this must be given in UTC as per spec, which is weirdcount
- how many occurrenceswkst
- which day the week starts on 0-7, sa,su,mobysetpos
- not sure how this works - http://www.kanzaki.com/docs/ical/recur.htmldtstart
- specifies the first event (or a date close to it), non-standard as part of rrule, but is part of ical- If you don't specify
dtstart
, the current time will be used. - You cannot get
previous()
beforedtstart
- If you don't specify
locale
- specifies the locale in the formatGMT-0500 (EST)
- non-standard, in generaltzid
- specifies the locale, non-standard as part of rrule, but is part of ical
See https://github.com/jkbr/rrule#api for implementation details.
Examples
JSON version of iCal RRULE
{ "freq": "daily"
, "until": "2015-01-01T10:30:00.000Z"
, "byday": ["tu", "th"]
, "byhour": [4]
, "byminute": [30]
}
Non-standard iCal directive
(once TZID
is implemented, this will be standard)
DTSTART;LOCALE=GMT-0500 (EST):20140616T103000
RRULE:FREQ=DAILY;BYHOUR=10;BYMINUTE=30;BYSECOND=0;UNTIL=20150616T153000Z
Non-standard iCal directive (rrule.js
-flavored)
DTSTART=20140616T103000Z;FREQ=DAILY;BYHOUR=10;BYMINUTE=30;BYSECOND=0;UNTIL=20150616T153000Z
Appendix
Because I needed a place to rant.
Timezones
Timezones are a PAIN! Right!?
We take care of the hard brainwork for you, but just so you know:
Here are the problems:
- JavaScript's native Date object has only two options - Locale and UTC
- The server may have a different locale than the client
rrule.js
only operates on Locale - the local time of the serverRRULE
s are zoneless - 10:30am is meant to be in the user's timeDTSTART;TZID=America/New_York
isn't yet supported byrrule.js
Here are solutions:
DTSTART
you can includeLOCALE
(recommended) or use UTC (confusing)UNTIL
you must use UTC (confusing, but that's the way it is)- Try
Rrecur.toAdjustedISOString(new Date(2014,06,16,10,30,00), 'GMT-0600 (MDT)')
- Try
RRULE
s are zoneless, but we do our best to put them in the right zone.- Everything is calculated in local time under the hood.
Remember:
- Always specify RRULEs in local time (except DTSTART and UNTIL in UTC).
- An RRULE for Monday at 10am will fire well after the sun is risen whether in California or China
- An RRULE for Monday at 10am will have a different UTC conversion in California than in China
dtstart
should always be in UTC or in local time with a localeuntil
must be specified in UTC- all calculations will be done in the local time of the server