filterEvents (data, config) {
		const newEvents = [];

		// limitFunction doesn't do much limiting, see comment re: the dates
		// array in rrule section below as to why we need to do the filtering
		// ourselves
		const limitFunction = function (date, i) {
			return true;
		};

		const eventDate = function (event, time) {
			return CalendarFetcherUtils.isFullDayEvent(event) ? moment(event[time]).startOf("day") : moment(event[time]);
		};

		Log.debug(`There are ${Object.entries(data).length} calendar entries.`);

		const now = new Date(Date.now());
		const todayLocal = moment(now).startOf("day").toDate();
		const futureLocalDate
			= moment(now)
				.startOf("day")
				.add(config.maximumNumberOfDays, "days")
				.subtract(1, "seconds") // Subtract 1 second so that events that start on the middle of the night will not repeat.
				.toDate();

		Object.entries(data).forEach(([key, event]) => {
			Log.debug("Processing entry...");
			let pastLocalDate = todayLocal;

			if (config.includePastEvents) {
				pastLocalDate = moment(now).startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
			}

			// FIXME: Ugly fix to solve the facebook birthday issue.
			// Otherwise, the recurring events only show the birthday for next year.
			let isFacebookBirthday = false;
			if (typeof event.uid !== "undefined") {
				if (event.uid.indexOf("@facebook.com") !== -1) {
					isFacebookBirthday = true;
				}
			}

			if (event.type === "VEVENT") {
				Log.debug(`Event:\n${JSON.stringify(event, null, 2)}`);
				let startMoment = eventDate(event, "start");
				let endMoment;

				if (typeof event.end !== "undefined") {
					endMoment = eventDate(event, "end");
				} else if (typeof event.duration !== "undefined") {
					endMoment = startMoment.clone().add(moment.duration(event.duration));
				} else {
					if (!isFacebookBirthday) {
						// make copy of start date, separate storage area
						endMoment = moment(startMoment.valueOf());
					} else {
						endMoment = moment(startMoment).add(1, "days");
					}
				}

				Log.debug(`start: ${startMoment.toDate()}`);
				Log.debug(`end:: ${endMoment.toDate()}`);

				// Calculate the duration of the event for use with recurring events.
				const durationMs = endMoment.valueOf() - startMoment.valueOf();
				Log.debug(`duration: ${durationMs}`);

				// FIXME: Since the parsed json object from node-ical comes with time information
				// this check could be removed (?)
				if (event.start.length === 8) {
					startMoment = startMoment.startOf("day");
				}

				const title = CalendarFetcherUtils.getTitleFromEvent(event);
				Log.debug(`title: ${title}`);

				let excluded = false,
					dateFilter = null;

				for (let f in config.excludedEvents) {
					let filter = config.excludedEvents[f],
						testTitle = title.toLowerCase(),
						until = null,
						useRegex = false,
						regexFlags = "g";

					if (filter instanceof Object) {
						if (typeof filter.until !== "undefined") {
							until = filter.until;
						}

						if (typeof filter.regex !== "undefined") {
							useRegex = filter.regex;
						}

						// If additional advanced filtering is added in, this section
						// must remain last as we overwrite the filter object with the
						// filterBy string
						if (filter.caseSensitive) {
							filter = filter.filterBy;
							testTitle = title;
						} else if (useRegex) {
							filter = filter.filterBy;
							testTitle = title;
							regexFlags += "i";
						} else {
							filter = filter.filterBy.toLowerCase();
						}
					} else {
						filter = filter.toLowerCase();
					}

					if (CalendarFetcherUtils.titleFilterApplies(testTitle, filter, useRegex, regexFlags)) {
						if (until) {
							dateFilter = until;
						} else {
							excluded = true;
						}
						break;
					}
				}

				if (excluded) {
					return;
				}

				const location = event.location || false;
				const geo = event.geo || false;
				const description = event.description || false;
				let d1;
				let d2;

				if (event.rrule && typeof event.rrule !== "undefined" && !isFacebookBirthday) {
					const rule = event.rrule;

					const pastMoment = moment(pastLocalDate);
					const futureMoment = moment(futureLocalDate);

					// can cause problems with e.g. birthdays before 1900
					if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) {
						rule.origOptions.dtstart.setYear(1900);
						rule.options.dtstart.setYear(1900);
					}

					// For recurring events, get the set of start dates that fall within the range
					// of dates we're looking for.

					let pastLocal;
					let futureLocal;

					if (CalendarFetcherUtils.isFullDayEvent(event)) {
						Log.debug("fullday");
						// if full day event, only use the date part of the ranges
						pastLocal = pastMoment.toDate();
						futureLocal = futureMoment.toDate();

						Log.debug(`pastLocal: ${pastLocal}`);
						Log.debug(`futureLocal: ${futureLocal}`);
					} else {
						// if we want past events
						if (config.includePastEvents) {
							// use the calculated past time for the between from
							pastLocal = pastMoment.toDate();
						} else {
							// otherwise use NOW.. cause we shouldn't use any before now
							pastLocal = moment(now).toDate(); //now
						}
						futureLocal = futureMoment.toDate(); // future
					}
					const oneDayInMs = 24 * 60 * 60 * 1000;
					d1 = new Date(new Date(pastLocal.valueOf() - oneDayInMs).getTime());
					d2 = new Date(new Date(futureLocal.valueOf() + oneDayInMs).getTime());
					Log.debug(`Search for recurring events between: ${d1} and ${d2}`);

					event.start = rule.options.dtstart;

					Log.debug("fix rrule start=", rule.options.dtstart);
					Log.debug("event before rrule.between=", JSON.stringify(event, null, 2), "exdates=", event.exdate);
					// fixup the exdate and recurrence date to local time too for post between() handling
					CalendarFetcherUtils.fixEventtoLocal(event);

					Log.debug(`RRule: ${rule.toString()}`);
					rule.options.tzid = null; // RRule gets *very* confused with timezones

					let dates = rule.between(d1, d2, true, () => { return true; });

					Log.debug(`Title: ${event.summary}, with dates: \n\n${JSON.stringify(dates)}\n`);

					// shouldn't need this  anymore, as RRULE not passed junk
					dates = dates.filter((d) => {
						if (JSON.stringify(d) === "null") return false;
						else return true;
					});

					// go thru all the rrule.between() dates and put back the tz offset removed so rrule.between would work
					let datesLocal = [];
					let offset = d1.getTimezoneOffset();
					Log.debug("offset =", offset);
					dates.forEach((d) => {
						let dtext = d.toISOString().slice(0, -5);
						Log.debug(" date text form without tz=", dtext);
						let dLocal = new Date(d.valueOf() + (offset * 60000));
						let offset2 = dLocal.getTimezoneOffset();
						Log.debug("date after offset applied=", dLocal);
						if (offset !== offset2) {
							// woops, dst/std switch
							let delta = offset - offset2;
							Log.debug("offset delta=", delta);
							dLocal = new Date(d.valueOf() + ((offset - delta) * 60000));
							Log.debug("corrected normalized date=", dLocal);
						} else Log.debug(" neutralized date=", dLocal);
						datesLocal.push(dLocal);
					});
					dates = datesLocal;


					// The "dates" array contains the set of dates within our desired date range range that are valid
					// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
					// had its date changed from outside the range to inside the range.  For the time being,
					// we'll handle this by adding *all* recurrence entries into the set of dates that we check,
					// because the logic below will filter out any recurrences that don't actually belong within
					// our display range.
					// Would be great if there was a better way to handle this.
					//
					// i don't think we will ever see this anymore (oct 2024) due to code fixes for rrule.between()
					//
					Log.debug("event.recurrences:", event.recurrences);
					if (event.recurrences !== undefined) {
						for (let dateKey in event.recurrences) {
							// Only add dates that weren't already in the range we added from the rrule so that
							// we don't double-add those events.
							let d = new Date(dateKey);
							if (!moment(d).isBetween(d1, d2)) {
								Log.debug("adding recurring event not found in between list =", d, " should not happen now using local dates oct 17,24");
								dates.push(d);
							}
						}
					}

					// Loop through the set of date entries to see which recurrences should be added to our event list.
					for (let d in dates) {
						let date = dates[d];
						let curEvent = event;
						let curDurationMs = durationMs;
						let showRecurrence = true;

						let startMoment = moment(date);

						let dateKey = CalendarFetcherUtils.getDateKeyFromDate(date);

						Log.debug("event date dateKey=", dateKey);
						// For each date that we're checking, it's possible that there is a recurrence override for that one day.
						if (curEvent.recurrences !== undefined) {
							Log.debug("have recurrences=", curEvent.recurrences);
							if (curEvent.recurrences[dateKey] !== undefined) {
								Log.debug("have a recurrence match for dateKey=", dateKey);
								// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
								curEvent = curEvent.recurrences[dateKey];
								curEvent.start = new Date(new Date(curEvent.start.valueOf()).getTime());
								curEvent.end = new Date(new Date(curEvent.end.valueOf()).getTime());
								startMoment = CalendarFetcherUtils.getAdjustedStartMoment(curEvent.start, event);
								endMoment = CalendarFetcherUtils.getAdjustedStartMoment(curEvent.end, event);
								date = curEvent.start;
								curDurationMs = new Date(endMoment).valueOf() - startMoment.valueOf();
							} else {
								Log.debug("recurrence key ", dateKey, " doesn't match");
							}
						}
						// If there's no recurrence override, check for an exception date.  Exception dates represent exceptions to the rule.
						if (curEvent.exdate !== undefined) {
							Log.debug("have datekey=", dateKey, " exdates=", curEvent.exdate);
							if (curEvent.exdate[dateKey] !== undefined) {
								// This date is an exception date, which means we should skip it in the recurrence pattern.
								showRecurrence = false;
							}
						}
						Log.debug(`duration: ${curDurationMs}`);

						startMoment = CalendarFetcherUtils.getAdjustedStartMoment(date, event);

						endMoment = moment(startMoment.valueOf() + curDurationMs);

						if (startMoment.valueOf() === endMoment.valueOf()) {
							endMoment = endMoment.endOf("day");
						}

						const recurrenceTitle = CalendarFetcherUtils.getTitleFromEvent(curEvent);

						// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
						// it to the event list.
						if (endMoment.isBefore(pastLocal) || startMoment.isAfter(futureLocal)) {
							showRecurrence = false;
						}

						if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
							showRecurrence = false;
						}

						if (showRecurrence === true) {
							Log.debug(`saving event: ${recurrenceTitle}`);
							newEvents.push({
								title: recurrenceTitle,
								startDate: startMoment.format("x"),
								endDate: endMoment.format("x"),
								fullDayEvent: CalendarFetcherUtils.isFullDayEvent(event),
								recurringEvent: true,
								class: event.class,
								firstYear: event.start.getFullYear(),
								location: location,
								geo: geo,
								description: description
							});
						} else {
							Log.debug("not saving event ", recurrenceTitle, new Date(startMoment));
						}
						Log.debug(" ");
					}
					// End recurring event parsing.
				} else {
					// Single event.
					const fullDayEvent = isFacebookBirthday ? true : CalendarFetcherUtils.isFullDayEvent(event);
					// Log.debug("full day event")

					// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
					if (fullDayEvent && startMoment.valueOf() === endMoment.valueOf()) {
						endMoment = endMoment.endOf("day");
					}

					if (config.includePastEvents) {
						// Past event is too far in the past, so skip.
						if (endMoment < pastLocalDate) {
							return;
						}
					} else {
						// It's not a fullday event, and it is in the past, so skip.
						if (!fullDayEvent && endMoment < now) {
							return;
						}

						// It's a fullday event, and it is before today, So skip.
						if (fullDayEvent && endMoment <= todayLocal) {
							return;
						}
					}

					// It exceeds the maximumNumberOfDays limit, so skip.
					if (startMoment > futureLocalDate) {
						return;
					}

					if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
						return;
					}

					// get correction for date saving and dst change between now and then
					let adjustHours = CalendarFetcherUtils.calculateTimezoneAdjustment(event, startMoment.toDate());
					// This shouldn't happen
					if (adjustHours) {
						Log.warn(`Unexpected timezone adjustment of ${adjustHours} hours on non-recurring event`);
					}
					// Every thing is good. Add it to the list.
					newEvents.push({
						title: title,
						startDate: startMoment.add(adjustHours, "hours").format("x"),
						endDate: endMoment.add(adjustHours, "hours").format("x"),
						fullDayEvent: fullDayEvent,
						recurringEvent: false,
						class: event.class,
						firstYear: event.start.getFullYear(),
						location: location,
						geo: geo,
						description: description
					});
				}
			}
		});

		newEvents.sort(function (a, b) {
			return a.startDate - b.startDate;
		});

		return newEvents;
	}
Filter the events from ical according to the given config
@param {object} data the calendar data from ical
@param {object} config The configuration object
@returns {string[]} the filtered events