Giter VIP home page Giter VIP logo

ical-expander's Introduction

ical-expander ๐Ÿ“… npm version Test local branches Known Vulnerabilities

ICS / iCal / iCalendar parser / expander.

Wrapper around ical.js that automatically handles EXDATE (excluded recursive occurrences), RRULE and recurring events overridden by RECURRENCE-ID.

Also handles timezones, and includes timezones from the IANA Time Zone Database, so that it parses correctly when a timezone definition is not available in the ICS file itself. zones.json can be found here and compiled by running compile-zones.js.

Be careful as the processing done in this library is synchronous and will block the JS event loop while processing. Especially when processing large ICS files and with high maxIterations values.

Install

npm install ical-expander

โš ๏ธ Warning: This package uses ES6 features, and might require transpiling if used in browsers

Example

Download .ics from google calendar for example.

const IcalExpander = require('ical-expander');
const fs = require('fs');

const ics = fs.readFileSync('./basic.ics', 'utf-8');

const icalExpander = new IcalExpander({ ics, maxIterations: 100 });
const events = icalExpander.between(new Date('2017-01-24T00:00:00.000Z'), new Date('2017-03-30T00:00:00.000Z'));

const mappedEvents = events.events.map(e => ({ startDate: e.startDate, summary: e.summary }));
const mappedOccurrences = events.occurrences.map(o => ({ startDate: o.startDate, summary: o.item.summary }));
const allEvents = [].concat(mappedEvents, mappedOccurrences);

console.log(allEvents.map(e => `${e.startDate.toJSDate().toISOString()} - ${e.summary}`).join('\n'));

Usage

Constructor

const icalExpander = new IcalExpander({ ics, maxIterations })
  • ics: String containing ICS data to parse
  • maxIterations: Max iterations on each RRULE. Defaults to 1000 (undefined or null). 0 means never stop (be careful!)

between()

icalExpander.between(after, before)

Include all events occuring between after and before. i.e. with a start time before before JS Date and an end time after JS Date.

  • after JS Date: Start of range. Default: No start limit.
  • before JS Date. End of range. Default: No end limit. Do not run with no end limit and maxIterations: 0

Example of events included between a date range:

range:    ---------- after |-----------------------| before -------------
event 1:                   |          start <------|------------> end
event 2:         start <---|---------------> end   |
event 3:      start <------|-----------------------|--------------> end
event 4:                   |    start <---> end    |                                                   

Example of events not included between a date range:

range:    -------------------- after |--------| before ------------------
event 5:  start <------> end         |        |
event 6:                             |        |          start <---> end

before()

icalExpander.before(before) is an alias for icalExpander.between(undefined, before)

after()

icalExpander.after() is an alias for icalExpander.between(after)

all()

icalExpander.all() is an alias for icalExpander.between()
Do not run this with maxIterations: 0

Return value

All methods return:

{
  events: [],
  occurrences: [],
}

icalExpander.component

Root ICAL.Component from parsed data.

TODO

  • RECURRENCE-ID: check that within same day?
  • Get data from moment-timezone instead?

See also:

ical-expander's People

Contributors

andeee avatar invibot avatar itsjw avatar kwwette avatar marcoancona avatar mifi avatar mwebler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ical-expander's Issues

Get currently running events

Is there an ability to get currently running events, perhaps with IcalExpander.now()?

things now() would catch for all events (including recurring):

  • events running all day for that day
  • events running at that particular time (startDate <= now() && endDate >= now())

Should 'between' contain entries that are partially inside the range?

Hi there, first of all, thank you, your library have saved us a lot of time ๐Ÿ˜„

I have one question about how the between function works... On lines 76 and 96 you have this condition to filter the events in the range:

(!before || endTime <= before.getTime()) &&
(!after || startTime >= after.getTime())

In this case, you are filtering only events that start after before AND ends before after (starts and ends in the range).
Shouldn't this case return also events that are just partially inside the range?

i.e. event starts before the range but ends inside it (or starts inside it and ends after the range):
eventssample

In this case, all 3 events should be included because all of them occur during the requested range (even if they start or end out of the range)
One use case where this would be used is asking for 'What I am doing today?'. If there is an event that starts at 11pm and ends in the next day at 2am, I would want this event to be shown, because it starts on the requested range. This is the event1 case in the example.

What do you think about it? If you agree I can submit a PR for that...
If it is designed to work like this, no problem as well. Then I will probably just make a fork for my projects ๐Ÿ˜„

Thanks!``

Possible problem with G-Suite Google Calendar recurring events

I'm using ical-expander to parse and expand recurring events returned various calendar servers, and I noticed a very strange behavior with iCal data returned from G-Suite calendars with recurring events that I'm thinking might be a bug either in ical-expander, or possibly ical.js.

Everything works fine with my personal Google Calendar: a calendar with one recurring event returns the following iCal data, which ical-expander parses and expands as expected:

BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Test
X-WR-TIMEZONE:America/Los_Angeles
X-WR-CALDESC:Test Public Calendar
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:DAYLIGHT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20180914T140000
DTEND;TZID=America/Los_Angeles:20180914T150000
RRULE:FREQ=WEEKLY;COUNT=5;BYDAY=FR
DTSTAMP:20180911T175958Z
UID:[email protected]
CREATED:20180911T155050Z
DESCRIPTION:
LAST-MODIFIED:20180911T155050Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Recurring Event with Ending
TRANSP:OPAQUE
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20180914T100000
DTEND;TZID=America/Los_Angeles:20180914T110000
RRULE:FREQ=WEEKLY;BYDAY=FR
DTSTAMP:20180911T175958Z
UID:[email protected]
CREATED:20180911T154655Z
DESCRIPTION:This is a description
LAST-MODIFIED:20180911T154656Z
LOCATION:Test Location
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Test Recurring Event
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR

But when I try the same thing on a G-Suite calendar (https://calendar.google.com/calendar/ical/601t.com_a029mt9oo3j20r5okc98g4lfe4%40group.calendar.google.com/public/basic.ics), the calendar server seems to auto-expand the recurring event like this:

BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Test
X-WR-TIMEZONE:America/Los_Angeles
X-WR-CALDESC:Test public calendar
BEGIN:VEVENT
DTSTART:20181012T190000Z
DTEND:20181012T200000Z
DTSTAMP:20180910T215427Z
UID:[email protected]
ATTENDEE;X-NUM-GUESTS=0:mailto:[email protected]
 lendar.google.com
RECURRENCE-ID:20181012T190000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20181005T190000Z
DTEND:20181005T200000Z
DTSTAMP:20180910T215427Z
UID:[email protected]
ATTENDEE;X-NUM-GUESTS=0:mailto:[email protected]
 lendar.google.com
RECURRENCE-ID:20181005T190000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20180928T190000Z
DTEND:20180928T200000Z
DTSTAMP:20180910T215427Z
UID:[email protected]
ATTENDEE;X-NUM-GUESTS=0:mailto:[email protected]
 lendar.google.com
RECURRENCE-ID:20180928T190000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20180921T190000Z
DTEND:20180921T200000Z
DTSTAMP:20180910T215427Z
UID:[email protected]
ATTENDEE;X-NUM-GUESTS=0:mailto:[email protected]
 lendar.google.com
RECURRENCE-ID:20180921T190000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20180914T190000Z
DTEND:20180914T200000Z
DTSTAMP:20180910T215427Z
UID:[email protected]
ATTENDEE;X-NUM-GUESTS=0:mailto:[email protected]
 lendar.google.com
RECURRENCE-ID:20180914T190000Z
SUMMARY:Busy
END:VEVENT

... more events ...

END:VCALENDAR

Note how each VEVENT has a RECURRENCE-ID property that refers back to the start time of the original recurring event, but they don't have an RRULE nor an EXDATE property.

When I parse/expand the latter example using ical-expander, I get totally empty arrays for both events and occurrences.

I stepped through the code, and the problem happens at this line:

this.events.filter(e => !e.isRecurrenceException()).forEach((event) => {

The .filter() predicate calls the .isRecurrenceException() method of the ical Event object, which is here:

https://github.com/mozilla-comm/ical.js/blob/5ae4372f1fad2ef19f315e358b1f46fe4ca1ec5e/lib/ical/event.js#L301

This method returns true if the event has a RECURRENCE-ID property, which all of the events returned from the G-Suite calendar have. Thus, I get back empty arrays.

I'm not an expert on RFC5545, but I was surprised that ical.js uses just the presence of RECURRENCE-ID to determine that an event is a recurrence exception. From my reading of the spec, the RECURRENCE-ID is just a reference back to the original event in a recurring series, and the EXDATE property is used for exceptions. But perhaps this behavior is due to the way some servers tended to use RECURRENCE-ID in practice to identify recurrence exceptions?

Bottom line: is this a problem in ical-expander, ical.js, or the iCal data being returned from the G-Suite calendar? Thanks!

DTEND should be non-inclusive

Hello,

I have discovered the bug in this library at the latest version 2.1. According to the RFC at 3.6.1:

The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.

So for example, in this calendar:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:+//IDN ki-wi.cz//NONSGML Player Lighter//EN
BEGIN:VEVENT
UID:197857
DTSTAMP:20190920T025726Z
DTSTART:20200101T000000
DTEND:20201231T235900
SUMMARY:simple event
END:VEVENT
END:VCALENDAR

When you make a selection, that includes only the DTEND, it shouldn't contain any event or occurrences, but it does. DTEND + 1 works as expected and no event is returned.

const date = new Date('2020-12-31T23:59');
const match = expander.between(date, date);
return Boolean(match.events.length || match.occurrences.length);

This code will return true, but it should return false.

Having problems with an EXDate

I have the following calendar Content. When I run the icalExpander.between() I get an error of invalid date-time value: "2020-11-26T::"

The calendar content seems to be building the EXDate with a a comma-separated list of dates without times. Does the expander expect dates and times there? The Icalendar Format.org definition seems to allow for date-time or date.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//ddaysoftware.com//NONSGML DDay.iCal 1.0//EN
BEGIN:VEVENT
DTEND:20201110T110000
DTSTAMP:20201102T160712Z
DTSTART:20201110T080000
EXDATE:20201126,20201127
RRULE:FREQ=WEEKLY;BYDAY=MO,TH
SEQUENCE:0
UID:652ea16f-d20f-472b-b906-191e5339cf55
END:VEVENT
END:VCALENDAR

found some bugs

Hello, I'm using this project in my node.js app, thanks to this project, it's really save my time.
But I found one bug and one mistake in example code:

Bug:
parameter after and before in between function are string, calling getTime() cause exception

Example code:
since parse() use opt.ics property, maybe the example code should be new IcalExpander({ ics: ics, maxIterations: 100 }); the ics: is missing

thank!

Improve performance

The implementation is memory inneficient and therefore slows down as the iterations occur.

When working with bigger calendars and more occurrences we need something faster.

I'll check if I can speed things up.

Add possibility to expand weekly recurring event starting from the specified start date

Hi! Thanks for the handy lib)

There could be the case when we create a recurring event and set it to repeat for example on Thursdays, but specify Monday as start date.
Generally in this case event occurrencies will be created starting from the first Thursday that occurs after specified start date.
On Monday event occurrence will not be created.
This is the way how Google Calendar expands events. The same does ical-expander.

But some calendars (like the one in Kerio Connect app) behave differently: the first occurrence is created for the specified start date (Monday) and all other on Thursdays.

On my current project customer wants the behavior to match the one in Kerio Connect.

Could you please provide some thoughts how it can be implemented in the right way?

Also, it'd be great if you add some option to the lib, that will allow to achieve this behavior.

"between" for modified recurring events doesn't work as expected

Hi Mikael,

in the test ics file icaljs-issue-285.ics there is a recurring event, where one occurence is modified not to be on 17.01.2017 but on 18.01.2017.
using your library

new IcalExpander({ ics: icaljsIssue285 })
    .between(new Date('2017-01-18T00:00:00.000Z'), new Date('2017-01-19T00:00:00.000Z'))

returns neither a occurence nor an event - I would expect the library to return the modified recurrence here.

I have prepared 2 branches on my fork, each one should fix the issue - but I would like to discuss the changes and options with you first.

  • Option 1: andeee/ical-expander@be5e719 Reread the start/endTime when checking the modified recurrence
  • Option 2: andeee/ical-expander@0de32aa Upgrade to ical.js 1.3.0
    The issue with Option 2 is that the behaviour changed in ical.js 1.3.0 so the modified reccurence doesn't get into the events array but in the occurrences array with the modified start/end date - you saw the effect of it in #11
    To restore the behaviour of ical.js 1.2.2 for ical-expander there would be still something to do.

I'm looking forward to your reply!

Some events with recurrence-id are not meant to be exceptions but ocurrences

There are some events where the library misuses the isRecurrenceException method.

BEGIN:VEVENT DESCRIPTION:the description UID:040000008200E00074C5B7101A82E00800000000E02A9BC02E82D701000000000000000 010000000BA756286EA265A41AB00CB3B15394D45 RECURRENCE-ID;TZID=America/Denver:20230717T100000 SUMMARY:FW: DigiCert/Connect Status Call DTSTART;TZID=America/Denver:20230717T100000 DTEND;TZID=America/Denver:20230717T110000 CLASS:PUBLIC PRIORITY:5 DTSTAMP:20230717T143408Z TRANSP:OPAQUE STATUS:CONFIRMED SEQUENCE:32 LOCATION:See below X-MICROSOFT-CDO-APPT-SEQUENCE:32 X-MICROSOFT-CDO-BUSYSTATUS:BUSY X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY X-MICROSOFT-CDO-ALLDAYEVENT:FALSE X-MICROSOFT-CDO-IMPORTANCE:1 X-MICROSOFT-CDO-INSTTYPE:3 X-MICROSOFT-DONOTFORWARDMEETING:FALSE X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT

In this event, we have a recurrence-id and a UID, but this event should not be included in the exdates, instead, it should be displayed the 20230717

it seems like this data has the problem from the root since ical-js mark this event as an exception, but I'm wondering if there is something we can do about it, like checking the EXDATE

``

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.