Published
Edited
Jul 14, 2019
Fork of RRULEs
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
luxon = import('https://unpkg.com/luxon@1.16.0/src/luxon.js?module')
Insert cell
luxon.DateTime.local();
Insert cell
rrule = import('https://unpkg.com/rrule@2.6.0/dist/esm/src/index.js?module')
Insert cell
moment = require('moment@2.24.0/moment.js')
Insert cell
// function addMinutes(date, minutes) {
// return new Date(date.getTime() + minutes*60000);
// }
// console.log(addMinutes(new Date('2014-11-02'), 60*24)); //In USA, prints 11pm on Nov 2, not 12am Nov 3!
// console.log(moment(new Date('2014-11-02')).add(24*60, 'm').toDate())
// console.log(luxon.DateTime.fromJSDate(new Date('2014-11-02')).plus({days: 1}).toJSDate())
// console.log(moment(new Date('2014-11-02')).add(1, 'd').toDate())
// console.log(luxon.DateTime.fromJSDate(new Date('2014-11-02')).plus({minutes: 24*60}).toJSDate())
Insert cell
// Wrapper implementation
rruleWrapper = ({
// Rrule wrapper for specifying start AND end date times for events (vanilla rrule.js only outputs start times)
// This wrapper treats every event as a combination of two rrules; one rrule for defining start dates and the other for // end dates of each recurrence.
// An alternative approach would have been to use an "event duration" parameter to specify the end time of events.
// i.e. to make a standard rrule, then generate event start times, then use the duration to calculate the endtimes.
// The drawback with that approach is that it doesn't
makeRule(config) {
// Breaks apart an rrule configuration that has a date end parameter into two rrules
this.configStart = Object.assign({}, config); // clone config
this.configEnd = Object.assign({}, config); // clone config
const configTemp = Object.assign({}, config); // clone config
this.configEnd.dtstart = configTemp.dtend
delete this.configStart.dtend
delete this.configEnd.dtend
this.ruleStart = new rrule.RRule(this.configStart)
this.ruleEnd = new rrule.RRule(this.configEnd)
return this
},
toString() {
// Returns a string representation of the rule as per the iCalendar RFC (plus dtend). Only properties explicitly specified in options are included:
return('DTEND:'+this.configEnd
.dtstart.toISOString().replace(/[^\w\s]|_/g, "")
.replace(/\s+/g, " ").slice(0, -4)+'Z\n'+this.ruleStart.toString())
},
fromString (rfcString) {
var dtEndString = rfcString.substr(rfcString.indexOf(":")+1,rfcString.indexOf("\n")- rfcString.indexOf(":")-1)
var dtEnd = luxon.DateTime.fromISO(dtEndString).toJSDate()
var rfcString = rfcString.substr(rfcString.indexOf("\n")+1)
var options = rrule.RRule.parseString(rfcString)
options.dtend = dtEnd
return this.makeRule(options)
},
after({dt, inclusive=false} = {}) {
// Returns the first recurrence interval after the given Date instance. Optionally can include the current interval
var dtStart = this.ruleStart.after(dt, inclusive)
var dtEnd = this.ruleEnd.after(dt, inclusive)
// console.log(dt)
// console.log(dtStart)
// console.log(dtEnd)
if (dtStart > dtEnd) {
dtStart = this.ruleStart.before(dt, inclusive)
}
if (inclusive == false & dtEnd >= dt & dtStart <= dt) {
dtStart = this.ruleStart.after(dtStart) // Update dtStart to be the next start date after this one.
dtEnd = this.ruleEnd.after(dtEnd) // Update dtEnd to be the next end date after this one.
}
return {start: dtStart, end: dtEnd};
// return [dtStart, dtEnd];
},
before({dt, inclusive=false} = {}) {
// Returns the last recurrence interval before the given Date instance. Optionally can include the current interval
var dtStart = this.ruleStart.before(dt, inclusive);
var dtEnd = this.ruleEnd.before(dt, inclusive);
if (dtEnd < dtStart) {
dtEnd = this.ruleEnd.after(dt, inclusive)
}
if (inclusive == false & dtEnd >= dt & dtStart <= dt) {
dtStart = this.ruleStart.before(dtStart) // Update dtStart to be the start date right before this one.
dtEnd = this.ruleEnd.before(dtEnd) // Update dtEnd to be end date right before this one.
}
return {start: dtStart, end: dtEnd}; // output object
// return [dtStart, dtEnd];
},
between({sliceStart, sliceEnd, inclusive=false, count = 0} = {}) {
// Returns all the occurrences of the rrule between after and before (optionally for a certain count of occurences)
var startInterval = this.before({dt: sliceStart, inclusive: true})
var endInterval = this.after({dt: sliceEnd, inclusive: true})
if (startInterval.start <= sliceStart & sliceStart <= startInterval.end) {
console.log('here0')
if (inclusive == true) {
sliceStart = startInterval.start // sliceStart. Snap to Start 1
console.log('here1')
} else {
sliceStart = startInterval.end
console.log('here2')
}
}
if (endInterval.start <= sliceEnd & sliceEnd <= endInterval.end) {
if (inclusive == true) {
sliceEnd = endInterval.end // sliceEnd. Snap to End 2
console.log('here3')
} else {
sliceEnd = endInterval.start
console.log('here4')
}
}
if (count <= 0) {
this.startDates = this.ruleStart.between(sliceStart, sliceEnd, inclusive);
this.endDates= this.ruleEnd.between(sliceStart, sliceEnd, inclusive);
} else {
this.startDates = this.ruleStart.between(sliceStart, sliceEnd, inclusive, function (date, i){return i < count});
this.endDates= this.ruleEnd.between(sliceStart, sliceEnd, inclusive, function (date, i){return i < count});
}
var newArray = []
for (var i = 0; i < this.startDates.length; i++) {
newArray.push({start: this.startDates[i], end: this.endDates[i]})
// newArray.push([this.startDates[i], this.endDates[i]])
}
return newArray;
},
})
Insert cell
config = ({
freq: rrule.RRule.WEEKLY,
// byweekday: [weekdays["TUESDAY"]],
dtstart: new Date(Date.UTC(2012, 5, 15, 14, 30)),
dtend: new Date(Date.UTC(2012, 5, 15, 15, 30)),
})
Insert cell
rruleWrapper.makeRule(config)
Insert cell
// Can access the rule object for each start and end date
// rruleWrapper.ruleStart
// rruleWrapper.ruleEnd
Insert cell
testTime = luxon.DateTime.fromJSDate(config.dtstart).plus({minute: 36, year: 7}).minus({day: 1}).toUTC().toJSDate()
// testTime = new Date(Date.UTC(2019, 5, 14, 15, 6))
// testTime = Math.floor((new Date()).getTime() / 1000)
Insert cell
testTime2 = luxon.DateTime.fromJSDate(config.dtstart).plus({minute: 36, year: 8}).minus({day: 3}).toUTC().toJSDate()
Insert cell
console.log(testTime2)
Insert cell
// Returns the recurrences between the given start and end date instances.
rruleWrapper.between({ sliceStart: testTime, sliceEnd: testTime2, inclusive: false, count: 10})
Insert cell
// Returns the first recurrence after the given Date instance.
rruleWrapper.after({dt: testTime, inclusive: true})
Insert cell
// Returns the last recurrence before the given Date instance.
rruleWrapper.before({dt: testTime, inclusive: true})
Insert cell
// Satisfies requirement B) Output an RRule string that includes end date time
rruleWrapper.toString()
Insert cell
//Satisfies requirement C) Generate an RRule from the RRule string in B)
b = rruleWrapper.fromString(rruleWrapper.toString())
Insert cell
b.toString()
Insert cell
b.after({dt: testTime, inclusive: true})
Insert cell
rfcString = rruleWrapper.toString()
Insert cell
Insert cell
// Make wrapper
rrulesetWrapper = ({
// Rruleset wrapper for specifying start AND end date times for rulesets (vanilla rrule.js only outputs start times)
// This wrapper treats every event as a combination of two rulesets; one ruleset for defining start date times and the other for end date times.
// fromString (rfcString) {
// var dtEndString = rfcString.substr(rfcString.indexOf(":")+1,rfcString.indexOf("\n")- rfcString.indexOf(":")-1)
// var dtEnd = luxon.DateTime.fromISO(dtEndString).toJSDate()
// var rfcString = rfcString.substr(rfcString.indexOf("\n")+1)
// var options = rrule.RRule.parseString(rfcString)
// options.dtend = dtEnd
// return this.makeRule(options)
// },
})
Insert cell
Insert cell
a = rfcString.substr(rfcString.indexOf(":")+1,rfcString.indexOf("\n")- rfcString.indexOf(":")-1)
Insert cell
// rrule.RRule.parseString(rfcString)
dtEnd = luxon.DateTime.fromISO(a).toJSDate()
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more