mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-26 03:52:28 +00:00
Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com> Co-authored-by: Veeck <github@veeck.de> Co-authored-by: veeck <michael@veeck.de> Co-authored-by: dWoolridge <dwoolridge@charter.net> Co-authored-by: Johan <jojjepersson@yahoo.se> Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com> Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> Co-authored-by: buxxi <buxxi@omfilm.net> Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com> Co-authored-by: Dave Child <dave@addedbytes.com> Co-authored-by: grenagit <46225780+grenagit@users.noreply.github.com> Co-authored-by: Grena <grena@grenabox.fr> Co-authored-by: Magnus Marthinsen <magmar@online.no> Co-authored-by: Patrick <psieg@users.noreply.github.com> Co-authored-by: Piotr Rajnisz <56397164+rajniszp@users.noreply.github.com> Co-authored-by: Suthep Yonphimai <tomzt@users.noreply.github.com> Co-authored-by: CarJem Generations (Carter Wallace) <cwallacecs@gmail.com> Co-authored-by: Nicholas Fogal <nfogal.misc@gmail.com> Co-authored-by: JakeBinney <126349119+JakeBinney@users.noreply.github.com> Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com> Co-authored-by: Oscar Björkman <17575446+oscarb@users.noreply.github.com> Co-authored-by: Ismar Slomic <ismar@slomic.no> Co-authored-by: Jørgen Veum-Wahlberg <jorgen.wahlberg@amedia.no> Co-authored-by: Eddie Hung <6740044+eddiehung@users.noreply.github.com> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: bugsounet <bugsounet@bugsounet.fr> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
606 lines
23 KiB
JavaScript
606 lines
23 KiB
JavaScript
/* MagicMirror²
|
|
* Calendar Fetcher Util Methods
|
|
*
|
|
* By Michael Teeuw https://michaelteeuw.nl
|
|
* MIT Licensed.
|
|
*/
|
|
|
|
/**
|
|
* @external Moment
|
|
*/
|
|
const path = require("path");
|
|
const moment = require("moment");
|
|
const zoneTable = require(path.join(__dirname, "windowsZones.json"));
|
|
const Log = require("../../../js/logger");
|
|
|
|
const CalendarFetcherUtils = {
|
|
/**
|
|
* Calculate the time correction, either dst/std or full day in cases where
|
|
* utc time is day before plus offset
|
|
* @param {object} event the event which needs adjustment
|
|
* @param {Date} date the date on which this event happens
|
|
* @returns {number} the necessary adjustment in hours
|
|
*/
|
|
calculateTimezoneAdjustment: function (event, date) {
|
|
let adjustHours = 0;
|
|
// if a timezone was specified
|
|
if (!event.start.tz) {
|
|
Log.debug(" if no tz, guess based on now");
|
|
event.start.tz = moment.tz.guess();
|
|
}
|
|
Log.debug(`initial tz=${event.start.tz}`);
|
|
|
|
// if there is a start date specified
|
|
if (event.start.tz) {
|
|
// if this is a windows timezone
|
|
if (event.start.tz.includes(" ")) {
|
|
// use the lookup table to get theIANA name as moment and date don't know MS timezones
|
|
let tz = CalendarFetcherUtils.getIanaTZFromMS(event.start.tz);
|
|
Log.debug(`corrected TZ=${tz}`);
|
|
// watch out for unregistered windows timezone names
|
|
// if we had a successful lookup
|
|
if (tz) {
|
|
// change the timezone to the IANA name
|
|
event.start.tz = tz;
|
|
// Log.debug("corrected timezone="+event.start.tz)
|
|
}
|
|
}
|
|
Log.debug(`corrected tz=${event.start.tz}`);
|
|
let current_offset = 0; // offset from TZ string or calculated
|
|
let mm = 0; // date with tz or offset
|
|
let start_offset = 0; // utc offset of created with tz
|
|
// if there is still an offset, lookup failed, use it
|
|
if (event.start.tz.startsWith("(")) {
|
|
const regex = /[+|-]\d*:\d*/;
|
|
const start_offsetString = event.start.tz.match(regex).toString().split(":");
|
|
let start_offset = parseInt(start_offsetString[0]);
|
|
start_offset *= event.start.tz[1] === "-" ? -1 : 1;
|
|
adjustHours = start_offset;
|
|
Log.debug(`defined offset=${start_offset} hours`);
|
|
current_offset = start_offset;
|
|
event.start.tz = "";
|
|
Log.debug(`ical offset=${current_offset} date=${date}`);
|
|
mm = moment(date);
|
|
let x = parseInt(moment(new Date()).utcOffset());
|
|
Log.debug(`net mins=${current_offset * 60 - x}`);
|
|
|
|
mm = mm.add(x - current_offset * 60, "minutes");
|
|
adjustHours = (current_offset * 60 - x) / 60;
|
|
event.start = mm.toDate();
|
|
Log.debug(`adjusted date=${event.start}`);
|
|
} else {
|
|
// get the start time in that timezone
|
|
let es = moment(event.start);
|
|
// check for start date prior to start of daylight changing date
|
|
if (es.format("YYYY") < 2007) {
|
|
es.set("year", 2013); // if so, use a closer date
|
|
}
|
|
Log.debug(`start date/time=${es.toDate()}`);
|
|
start_offset = moment.tz(es, event.start.tz).utcOffset();
|
|
Log.debug(`start offset=${start_offset}`);
|
|
|
|
Log.debug(`start date/time w tz =${moment.tz(moment(event.start), event.start.tz).toDate()}`);
|
|
|
|
// get the specified date in that timezone
|
|
mm = moment.tz(moment(date), event.start.tz);
|
|
Log.debug(`event date=${mm.toDate()}`);
|
|
current_offset = mm.utcOffset();
|
|
}
|
|
Log.debug(`event offset=${current_offset} hour=${mm.format("H")} event date=${mm.toDate()}`);
|
|
|
|
// if the offset is greater than 0, east of london
|
|
if (current_offset !== start_offset) {
|
|
// big offset
|
|
Log.debug("offset");
|
|
let h = parseInt(mm.format("H"));
|
|
// check if the event time is less than the offset
|
|
if (h > 0 && h < Math.abs(current_offset) / 60) {
|
|
// if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time)
|
|
// we need to fix that
|
|
//adjustHours = 24;
|
|
// Log.debug("adjusting date")
|
|
}
|
|
//-300 > -240
|
|
//if (Math.abs(current_offset) > Math.abs(start_offset)){
|
|
if (current_offset > start_offset) {
|
|
adjustHours -= 1;
|
|
Log.debug("adjust down 1 hour dst change");
|
|
//} else if (Math.abs(current_offset) < Math.abs(start_offset)) {
|
|
} else if (current_offset < start_offset) {
|
|
adjustHours += 1;
|
|
Log.debug("adjust up 1 hour dst change");
|
|
}
|
|
}
|
|
}
|
|
Log.debug(`adjustHours=${adjustHours}`);
|
|
return adjustHours;
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
filterEvents: function (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], "YYYYMMDD") : moment(new Date(event[time]));
|
|
};
|
|
|
|
Log.debug(`There are ${Object.entries(data).length} calendar entries.`);
|
|
Object.entries(data).forEach(([key, event]) => {
|
|
Log.debug("Processing entry...");
|
|
const now = new Date();
|
|
const today = moment().startOf("day").toDate();
|
|
const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
|
|
let past = today;
|
|
|
|
if (config.includePastEvents) {
|
|
past = moment().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)}`);
|
|
let startDate = eventDate(event, "start");
|
|
let endDate;
|
|
|
|
if (typeof event.end !== "undefined") {
|
|
endDate = eventDate(event, "end");
|
|
} else if (typeof event.duration !== "undefined") {
|
|
endDate = startDate.clone().add(moment.duration(event.duration));
|
|
} else {
|
|
if (!isFacebookBirthday) {
|
|
// make copy of start date, separate storage area
|
|
endDate = moment(startDate.format("x"), "x");
|
|
} else {
|
|
endDate = moment(startDate).add(1, "days");
|
|
}
|
|
}
|
|
|
|
Log.debug(`start: ${startDate.toDate()}`);
|
|
Log.debug(`end:: ${endDate.toDate()}`);
|
|
|
|
// Calculate the duration of the event for use with recurring events.
|
|
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
|
Log.debug(`duration: ${duration}`);
|
|
|
|
// FIXME: Since the parsed json object from node-ical comes with time information
|
|
// this check could be removed (?)
|
|
if (event.start.length === 8) {
|
|
startDate = startDate.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;
|
|
|
|
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
|
|
const rule = event.rrule;
|
|
let addedEvents = 0;
|
|
|
|
const pastMoment = moment(past);
|
|
const futureMoment = moment(future);
|
|
|
|
// 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.
|
|
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
|
|
let pastLocal = 0;
|
|
let futureLocal = 0;
|
|
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().toDate(); //now
|
|
}
|
|
futureLocal = futureMoment.toDate(); // future
|
|
}
|
|
Log.debug(`Search for recurring events between: ${pastLocal} and ${futureLocal}`);
|
|
const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
|
|
Log.debug(`Title: ${event.summary}, with dates: ${JSON.stringify(dates)}`);
|
|
// 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.
|
|
Log.debug(`event.recurrences: ${event.recurrences}`);
|
|
if (event.recurrences !== undefined) {
|
|
for (let r 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.
|
|
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) {
|
|
dates.push(new Date(r));
|
|
}
|
|
}
|
|
}
|
|
// 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];
|
|
// Remove the time information of each date by using its substring, using the following method:
|
|
// .toISOString().substring(0,10).
|
|
// since the date is given as ISOString with YYYY-MM-DDTHH:MM:SS.SSSZ
|
|
// (see https://momentjs.com/docs/#/displaying/as-iso-string/).
|
|
const dateKey = date.toISOString().substring(0, 10);
|
|
let curEvent = event;
|
|
let showRecurrence = true;
|
|
|
|
// Get the offset of today where we are processing
|
|
// This will be the correction, we need to apply.
|
|
let nowOffset = new Date().getTimezoneOffset();
|
|
// For full day events, the time might be off from RRULE/Luxon problem
|
|
// Get time zone offset of the rule calculated event
|
|
let dateoffset = date.getTimezoneOffset();
|
|
|
|
// Reduce the time by the following offset.
|
|
Log.debug(` recurring date is ${date} offset is ${dateoffset}`);
|
|
|
|
let dh = moment(date).format("HH");
|
|
Log.debug(` recurring date is ${date} offset is ${dateoffset / 60} Hour is ${dh}`);
|
|
|
|
if (CalendarFetcherUtils.isFullDayEvent(event)) {
|
|
Log.debug("Fullday");
|
|
// If the offset is negative (east of GMT), where the problem is
|
|
if (dateoffset < 0) {
|
|
if (dh < Math.abs(dateoffset / 60)) {
|
|
// if the rrule byweekday WAS explicitly set , correct it
|
|
// reduce the time by the offset
|
|
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
|
// Apply the correction to the date/time to get it UTC relative
|
|
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
|
|
}
|
|
// the duration was calculated way back at the top before we could correct the start time..
|
|
// fix it for this event entry
|
|
//duration = 24 * 60 * 60 * 1000;
|
|
Log.debug(`new recurring date1 fulldate is ${date}`);
|
|
}
|
|
} else {
|
|
// if the timezones are the same, correct date if needed
|
|
//if (event.start.tz === moment.tz.guess()) {
|
|
// if the date hour is less than the offset
|
|
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
|
// if the rrule byweekday WAS explicitly set , correct it
|
|
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
|
// apply the correction to the date/time back to right day
|
|
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
|
|
}
|
|
// the duration was calculated way back at the top before we could correct the start time..
|
|
// fix it for this event entry
|
|
//duration = 24 * 60 * 60 * 1000;
|
|
Log.debug(`new recurring date2 fulldate is ${date}`);
|
|
}
|
|
//}
|
|
}
|
|
} else {
|
|
// not full day, but luxon can still screw up the date on the rule processing
|
|
// we need to correct the date to get back to the right event for
|
|
if (dateoffset < 0) {
|
|
// if the date hour is less than the offset
|
|
if (dh <= Math.abs(dateoffset / 60)) {
|
|
// if the rrule byweekday WAS explicitly set , correct it
|
|
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
|
// Reduce the time by t:
|
|
// Apply the correction to the date/time to get it UTC relative
|
|
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
|
|
}
|
|
// the duration was calculated way back at the top before we could correct the start time..
|
|
// fix it for this event entry
|
|
//duration = 24 * 60 * 60 * 1000;
|
|
Log.debug(`new recurring date1 is ${date}`);
|
|
}
|
|
} else {
|
|
// if the timezones are the same, correct date if needed
|
|
//if (event.start.tz === moment.tz.guess()) {
|
|
// if the date hour is less than the offset
|
|
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
|
// if the rrule byweekday WAS explicitly set , correct it
|
|
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
|
// apply the correction to the date/time back to right day
|
|
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
|
|
}
|
|
// the duration was calculated way back at the top before we could correct the start time..
|
|
// fix it for this event entry
|
|
//duration = 24 * 60 * 60 * 1000;
|
|
Log.debug(`new recurring date2 is ${date}`);
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
startDate = moment(date);
|
|
Log.debug(`Corrected startDate: ${startDate.toDate()}`);
|
|
|
|
let adjustDays = CalendarFetcherUtils.calculateTimezoneAdjustment(event, date);
|
|
|
|
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
|
|
if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
|
|
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
|
|
curEvent = curEvent.recurrences[dateKey];
|
|
startDate = moment(curEvent.start);
|
|
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
|
|
}
|
|
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
|
|
else if (curEvent.exdate !== undefined && 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: ${duration}`);
|
|
|
|
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
|
|
if (startDate.format("x") === endDate.format("x")) {
|
|
endDate = endDate.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 (endDate.isBefore(past) || startDate.isAfter(future)) {
|
|
showRecurrence = false;
|
|
}
|
|
|
|
if (CalendarFetcherUtils.timeFilterApplies(now, endDate, dateFilter)) {
|
|
showRecurrence = false;
|
|
}
|
|
|
|
if (showRecurrence === true) {
|
|
Log.debug(`saving event: ${description}`);
|
|
addedEvents++;
|
|
newEvents.push({
|
|
title: recurrenceTitle,
|
|
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
|
|
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
|
|
fullDayEvent: CalendarFetcherUtils.isFullDayEvent(event),
|
|
recurringEvent: true,
|
|
class: event.class,
|
|
firstYear: event.start.getFullYear(),
|
|
location: location,
|
|
geo: geo,
|
|
description: description
|
|
});
|
|
}
|
|
}
|
|
// 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 && startDate.format("x") === endDate.format("x")) {
|
|
endDate = endDate.endOf("day");
|
|
}
|
|
|
|
if (config.includePastEvents) {
|
|
// Past event is too far in the past, so skip.
|
|
if (endDate < past) {
|
|
return;
|
|
}
|
|
} else {
|
|
// It's not a fullday event, and it is in the past, so skip.
|
|
if (!fullDayEvent && endDate < new Date()) {
|
|
return;
|
|
}
|
|
|
|
// It's a fullday event, and it is before today, So skip.
|
|
if (fullDayEvent && endDate <= today) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// It exceeds the maximumNumberOfDays limit, so skip.
|
|
if (startDate > future) {
|
|
return;
|
|
}
|
|
|
|
if (CalendarFetcherUtils.timeFilterApplies(now, endDate, dateFilter)) {
|
|
return;
|
|
}
|
|
|
|
// get correction for date saving and dst change between now and then
|
|
let adjustDays = CalendarFetcherUtils.calculateTimezoneAdjustment(event, startDate.toDate());
|
|
// Every thing is good. Add it to the list.
|
|
newEvents.push({
|
|
title: title,
|
|
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
|
|
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
|
|
fullDayEvent: fullDayEvent,
|
|
class: event.class,
|
|
location: location,
|
|
geo: geo,
|
|
description: description
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
newEvents.sort(function (a, b) {
|
|
return a.startDate - b.startDate;
|
|
});
|
|
|
|
return newEvents;
|
|
},
|
|
|
|
/**
|
|
* Lookup iana tz from windows
|
|
* @param {string} msTZName the timezone name to lookup
|
|
* @returns {string|null} the iana name or null of none is found
|
|
*/
|
|
getIanaTZFromMS: function (msTZName) {
|
|
// Get hash entry
|
|
const he = zoneTable[msTZName];
|
|
// If found return iana name, else null
|
|
return he ? he.iana[0] : null;
|
|
},
|
|
|
|
/**
|
|
* Gets the title from the event.
|
|
* @param {object} event The event object to check.
|
|
* @returns {string} The title of the event, or "Event" if no title is found.
|
|
*/
|
|
getTitleFromEvent: function (event) {
|
|
let title = "Event";
|
|
if (event.summary) {
|
|
title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
|
|
} else if (event.description) {
|
|
title = event.description;
|
|
}
|
|
|
|
return title;
|
|
},
|
|
|
|
/**
|
|
* Checks if an event is a fullday event.
|
|
* @param {object} event The event object to check.
|
|
* @returns {boolean} True if the event is a fullday event, false otherwise
|
|
*/
|
|
isFullDayEvent: function (event) {
|
|
if (event.start.length === 8 || event.start.dateOnly || event.datetype === "date") {
|
|
return true;
|
|
}
|
|
|
|
const start = event.start || 0;
|
|
const startDate = new Date(start);
|
|
const end = event.end || 0;
|
|
if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
|
|
// Is 24 hours, and starts on the middle of the night.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Determines if the user defined time filter should apply
|
|
* @param {Date} now Date object using previously created object for consistency
|
|
* @param {Moment} endDate Moment object representing the event end date
|
|
* @param {string} filter The time to subtract from the end date to determine if an event should be shown
|
|
* @returns {boolean} True if the event should be filtered out, false otherwise
|
|
*/
|
|
timeFilterApplies: function (now, endDate, filter) {
|
|
if (filter) {
|
|
const until = filter.split(" "),
|
|
value = parseInt(until[0]),
|
|
increment = until[1].slice(-1) === "s" ? until[1] : `${until[1]}s`, // Massage the data for moment js
|
|
filterUntil = moment(endDate.format()).subtract(value, increment);
|
|
|
|
return now < filterUntil.format("x");
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Determines if the user defined title filter should apply
|
|
* @param {string} title the title of the event
|
|
* @param {string} filter the string to look for, can be a regex also
|
|
* @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string
|
|
* @param {string} regexFlags flags that should be applied to the regex
|
|
* @returns {boolean} True if the title should be filtered out, false otherwise
|
|
*/
|
|
titleFilterApplies: function (title, filter, useRegex, regexFlags) {
|
|
if (useRegex) {
|
|
let regexFilter = filter;
|
|
// Assume if leading slash, there is also trailing slash
|
|
if (filter[0] === "/") {
|
|
// Strip leading and trailing slashes
|
|
regexFilter = filter.substr(1).slice(0, -1);
|
|
}
|
|
return new RegExp(regexFilter, regexFlags).test(title);
|
|
} else {
|
|
return title.includes(filter);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (typeof module !== "undefined") {
|
|
module.exports = CalendarFetcherUtils;
|
|
}
|