mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 04:45:17 +00:00
Release 2.30.0 (#3673)
## [2.30.0] - 2025-01-01 Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel, @KristjanESPERANTO, @rejas, @sdetweil. > ⚠️ This release needs nodejs version `v20` or `v22 or higher`, minimum version is `v20.18.1` ### Added - [core] Add wayland and windows start options to `package.json` (#3594) - [docs] Add step for npm publishing in release process (#3595) - [core] Add GitHub workflow to run spellcheck a few days before each release (#3623) - [core] Add test flag to `index.html` to pass to module js for test mode detection (needed by #3630) - [core] Add export on animation names (#3644) - [compliments] Add support for refreshing remote compliments file, and test cases (#3630) - [linter] Re-add `eslint-plugin-import`now that it supports ESLint v9 (#3586) - [linter] Re-activate `eslint-plugin-package-json` to lint `package.json` (#3643) - [linter] Add linting for markdown files (#3646) - [linter] Add some handy ESLint rules. - [calendar] Add ability to display end date for full date events, where end is not same day (showEnd=true) (#3650) - [core] Add text to the config.js.sample file about the locale variable (#3654, #3655) - [core] Add fetch timeout for all node_helpers (thru undici, forces node 20.18.1 minimum) to help on slower systems. (#3660) (3661) ### Changed - [core] Run code style checks in workflow only once (#3648) - [core] Fix animations export #3644 only on server side (#3649) - [core] Use project URL in fallback config (#3656) - [core] Fix Access Denied crash writing js/positions.js (on synology nas) #3651. new message, MM starts, but no modules showing (#3652) - [linter] Switch to 'npx' for lint-staged in pre-commit hook (#3658) ### Removed - [tests] Remove `node-pty` and `drivelist` from rebuilded test (#3575) - [deps] Remove `@eslint/js` dependency. Already installed with `eslint` in deep (#3636) ### Updated - [repo] Reactivate `stale.yaml` as GitHub action to mark issues as stale after 60 days and close them 7 days later (if no activity) (#3577, #3580, #3581) - [core] Update electron dependency to v32 (test electron rebuild) and all other dependencies too (#3657) - [tests] All test configs have been updated to allow full external access, allowing for easier debugging (especially when running as a container) - [core] Run and test with node 23 (#3588) - [workflow] delete exception `allow-ghsas: GHSA-8hc4-vh64-cxmj` in `dep-review.yaml` (#3659) ### Fixed - [updatenotification] Fix pm2 using detection when pm2 script is inside or outside MagicMirror root folder (#3576) (#3605) (#3626) (#3628) - [core] Fix loading node_helper of modules: avoid black screen, display errors and continue loading with next module (#3578) - [weather] Change default value for weatherEndpoint of provider openweathermap to "/onecall" (#3574) - [tests] Fix electron tests with mock dates, the mock on server side was missing (#3597) - [tests] Fix testcases with hard coded Date.now (#3597) - [core] Fix missing `basePath` where `location.host` is used (#3613) - [compliments] croner library changed filenames used in latest version (#3624) - [linter] Fix ESLint ignore pattern which caused that default modules not to be linted (#3632) - [core] Fix module path in case of sub/sub folder is used and use path.resolve for resolve `moduleFolder` and `defaultModuleFolder` in app.js (#3653) - [calendar] Update to resolve issues #3098 #3144 #3351 #3422 #3443 #3467 #3537 related to timezone changes - [calendar] Fix #3267 (styles array), also fixes event with both exdate AND recurrence(and testcase) - [calendar] Fix showEndsOnlyWithDuration not working, #3598, applies ONLY to full day events - [calendar] Fix showEnd for Full Day events (#3602) - [tests] Suppress "module is not defined" in e2e tests (#3647) - [calendar] Fix #3267 (styles array, really this time!) - [core] Fix #3662 js/positions.js created incorrectly --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Michael Teeuw <michael@xonaymedia.nl> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Ross Younger <crazyscot@gmail.com> Co-authored-by: Veeck <github@veeck.de> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: jkriegshauser <joshuakr@nvidia.com> Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com> Co-authored-by: vppencilsharpener <tim.pray@gmail.com> Co-authored-by: veeck <michael.veeck@nebenan.de> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com> Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com> Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com> Co-authored-by: Jason Stieber <jrstieber@gmail.com> Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com> Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com> Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com> Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com> Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com> Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com> Co-authored-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: veeck <gitkraken@veeck.de>
This commit is contained in:
@@ -168,12 +168,17 @@ Module.register("calendar", {
|
||||
|
||||
this.selfUpdate();
|
||||
},
|
||||
notificationReceived (notification, payload, sender) {
|
||||
|
||||
if (notification === "FETCH_CALENDAR") {
|
||||
if (this.hasCalendarURL(payload.url)) {
|
||||
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Override socket notification handler.
|
||||
socketNotificationReceived (notification, payload) {
|
||||
if (notification === "FETCH_CALENDAR") {
|
||||
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
|
||||
}
|
||||
|
||||
if (this.identifier !== payload.id) {
|
||||
return;
|
||||
@@ -417,18 +422,26 @@ Module.register("calendar", {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
|
||||
// Add end time if showEnd
|
||||
if (this.config.showEnd) {
|
||||
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
|
||||
// no duration here, don't display end
|
||||
} else {
|
||||
// and has a duation
|
||||
if (event.startDate !== event.endDate) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
|
||||
}
|
||||
}
|
||||
|
||||
// For full day events we use the fullDayEventDateFormat
|
||||
if (event.fullDayEvent) {
|
||||
//subtract one second so that fullDayEvents end at 23:59:59, and not at 0:00:00 one the next day
|
||||
event.endDate -= ONE_SECOND;
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
// only show end if requested and allowed and the dates are different
|
||||
if (this.config.showEnd && !this.config.showEndsOnlyWithDuration && moment(event.startDate, "x").format("YYYYMMDD") !== moment(event.endDate, "x").format("YYYYMMDD")) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
} else
|
||||
if ((moment(event.startDate, "x").format("YYYYMMDD") !== moment(event.endDate, "x").format("YYYYMMDD")) && (moment(event.startDate, "x") < moment(now, "x"))) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(now, "x").format(this.config.fullDayEventDateFormat));
|
||||
}
|
||||
} else if (this.config.getRelative > 0 && event.startDate < now) {
|
||||
// Ongoing and getRelative is set
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
@@ -460,16 +473,18 @@ Module.register("calendar", {
|
||||
if (event.startDate >= now || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
// Use relative time
|
||||
if (!this.config.hideTime && !event.fullDayEvent) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
|
||||
Log.debug("event not hidden and not fullday");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }))}`;
|
||||
} else {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
Log.debug("event full day or hidden");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(
|
||||
moment(event.startDate, "x").calendar(null, {
|
||||
sameDay: this.config.showTimeToday ? "LT" : `[${this.translate("TODAY")}]`,
|
||||
nextDay: `[${this.translate("TOMORROW")}]`,
|
||||
nextWeek: "dddd",
|
||||
sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat
|
||||
})
|
||||
);
|
||||
)}`;
|
||||
}
|
||||
if (event.fullDayEvent) {
|
||||
// Full days events within the next two days
|
||||
@@ -488,9 +503,11 @@ Module.register("calendar", {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
|
||||
}
|
||||
}
|
||||
Log.info("event fullday");
|
||||
} else if (event.startDate - now < this.config.getRelative * ONE_HOUR) {
|
||||
Log.info("not full day but within getrelative size");
|
||||
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").fromNow());
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(moment(event.startDate, "x").fromNow())}`;
|
||||
}
|
||||
} else {
|
||||
// Ongoing event
|
||||
@@ -603,6 +620,7 @@ Module.register("calendar", {
|
||||
const calendar = this.calendarData[calendarUrl];
|
||||
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
|
||||
let maxPastDaysCompare = now - this.maximumPastDaysForUrl(calendarUrl) * ONE_DAY;
|
||||
let by_url_calevents = [];
|
||||
for (const e in calendar) {
|
||||
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||
|
||||
@@ -620,9 +638,6 @@ Module.register("calendar", {
|
||||
if (this.config.hideDuplicates && this.listContainsEvent(events, event)) {
|
||||
continue;
|
||||
}
|
||||
if (--remainingEntries < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event.url = calendarUrl;
|
||||
@@ -667,15 +682,21 @@ Module.register("calendar", {
|
||||
|
||||
for (let splitEvent of splitEvents) {
|
||||
if (splitEvent.endDate > now && splitEvent.endDate <= future) {
|
||||
events.push(splitEvent);
|
||||
by_url_calevents.push(splitEvent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
events.push(event);
|
||||
by_url_calevents.push(event);
|
||||
}
|
||||
}
|
||||
by_url_calevents.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
|
||||
events = events.concat(by_url_calevents.slice(0, remainingEntries));
|
||||
Log.debug(`events for calendar=${events.length}`);
|
||||
}
|
||||
|
||||
Log.info(`sorting events count=${events.length}`);
|
||||
events.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
@@ -715,7 +736,7 @@ Module.register("calendar", {
|
||||
}
|
||||
events = newEvents;
|
||||
}
|
||||
|
||||
Log.info(`slicing events total maxcount=${this.config.maximumEntries}`);
|
||||
return events.slice(0, this.config.maximumEntries);
|
||||
},
|
||||
|
||||
@@ -886,9 +907,9 @@ Module.register("calendar", {
|
||||
let p = this.getCalendarProperty(url, property, defaultValue);
|
||||
if (property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol") {
|
||||
const className = this.getCalendarProperty(url, "symbolClassName", this.config.defaultSymbolClassName);
|
||||
p = className + p;
|
||||
if (p instanceof Array) p.push(className);
|
||||
else p = className + p;
|
||||
}
|
||||
|
||||
if (!(p instanceof Array)) p = [p];
|
||||
return p;
|
||||
},
|
||||
|
@@ -56,7 +56,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
||||
|
||||
try {
|
||||
data = ical.parseICS(responseData);
|
||||
Log.debug(`parsed data=${JSON.stringify(data)}`);
|
||||
Log.debug(`parsed data=${JSON.stringify(data, null, 2)}`);
|
||||
events = CalendarFetcherUtils.filterEvents(data, {
|
||||
excludedEvents,
|
||||
includePastEvents,
|
||||
|
@@ -160,7 +160,7 @@ const CalendarFetcherUtils = {
|
||||
}
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
Log.debug(`Event:\n${JSON.stringify(event)}`);
|
||||
Log.debug(`Event:\n${JSON.stringify(event, null, 2)}`);
|
||||
let startMoment = eventDate(event, "start");
|
||||
let endMoment;
|
||||
|
||||
@@ -246,6 +246,8 @@ const CalendarFetcherUtils = {
|
||||
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;
|
||||
@@ -261,9 +263,10 @@ const CalendarFetcherUtils = {
|
||||
|
||||
// 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;
|
||||
let futureLocal;
|
||||
|
||||
if (CalendarFetcherUtils.isFullDayEvent(event)) {
|
||||
Log.debug("fullday");
|
||||
// if full day event, only use the date part of the ranges
|
||||
@@ -283,52 +286,52 @@ const CalendarFetcherUtils = {
|
||||
}
|
||||
futureLocal = futureMoment.toDate(); // future
|
||||
}
|
||||
Log.debug(`Search for recurring events between: ${pastLocal} and ${futureLocal}`);
|
||||
const hasByWeekdayRule = rule.options.byweekday !== undefined && rule.options.byweekday !== null;
|
||||
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(new Date(pastLocal.valueOf() - oneDayInMs), new Date(futureLocal.valueOf() + oneDayInMs), true, () => { return true; });
|
||||
Log.debug(`Title: ${event.summary}, with dates: ${JSON.stringify(dates)}`);
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
// RRule can generate dates with an incorrect recurrence date. Process the array here and apply date correction.
|
||||
if (hasByWeekdayRule) {
|
||||
Log.debug("Rule has byweekday, checking for correction");
|
||||
dates.forEach((date, index, arr) => {
|
||||
// NOTE: getTimezoneOffset() is negative of the expected value. For America/Los_Angeles under DST (GMT-7),
|
||||
// this value is +420. For Australia/Sydney under DST (GMT+11), this value is -660.
|
||||
const tzOffset = date.getTimezoneOffset() / 60;
|
||||
const hour = date.getHours();
|
||||
if ((tzOffset < 0) && (hour < -tzOffset)) { // east of GMT
|
||||
Log.debug(`East of GMT (tzOffset: ${tzOffset}) and hour=${hour} < ${-tzOffset}, Subtracting 1 day from ${date}`);
|
||||
arr[index] = new Date(date.valueOf() - oneDayInMs);
|
||||
} else if ((tzOffset > 0) && (hour >= (24 - tzOffset))) { // west of GMT
|
||||
Log.debug(`West of GMT (tzOffset: ${tzOffset}) and hour=${hour} >= 24-${tzOffset}, Adding 1 day to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + oneDayInMs);
|
||||
}
|
||||
});
|
||||
// Adjusting the dates could push it beyond the 'until' date, so filter those out here.
|
||||
if (rule.options.until !== null) {
|
||||
dates = dates.filter((date) => {
|
||||
return date.valueOf() <= rule.options.until.valueOf();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The dates array from rrule can be confused by DST. If the event was created during DST and we
|
||||
// are querying a date range during non-DST, rrule can have the incorrect time for the date range.
|
||||
// Reprocess the array here computing and applying the time offset.
|
||||
dates.forEach((date, index, arr) => {
|
||||
let adjustHours = CalendarFetcherUtils.calculateTimezoneAdjustment(event, date);
|
||||
if (adjustHours !== 0) {
|
||||
Log.debug(`Applying timezone adjustment hours=${adjustHours} to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + (adjustHours * 60 * 60 * 1000));
|
||||
}
|
||||
// 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
|
||||
@@ -337,29 +340,22 @@ const CalendarFetcherUtils = {
|
||||
// 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}`);
|
||||
//
|
||||
// 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(pastMoment, futureMoment)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, sometimes rrule doesn't include the event.start even if it is in the requested range. Ensure
|
||||
// inclusion here. Unfortunately dates.includes() doesn't find it so we have to do forEach().
|
||||
{
|
||||
let found = false;
|
||||
dates.forEach((d) => { if (d.valueOf() === event.start.valueOf()) found = true; });
|
||||
if (!found) {
|
||||
Log.debug(`event.start=${event.start} was not included in results from rrule; adding`);
|
||||
dates.splice(0, 0, event.start);
|
||||
}
|
||||
}
|
||||
|
||||
// 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];
|
||||
@@ -367,30 +363,42 @@ const CalendarFetcherUtils = {
|
||||
let curDurationMs = durationMs;
|
||||
let showRecurrence = true;
|
||||
|
||||
startMoment = moment(date);
|
||||
let startMoment = moment(date);
|
||||
|
||||
// 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/).
|
||||
// This must be done after `date` is adjusted
|
||||
const dateKey = date.toISOString().substring(0, 10);
|
||||
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 && 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];
|
||||
startMoment = moment(curEvent.start);
|
||||
curDurationMs = curEvent.end.valueOf() - startMoment.valueOf();
|
||||
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.
|
||||
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;
|
||||
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");
|
||||
}
|
||||
@@ -408,7 +416,7 @@ const CalendarFetcherUtils = {
|
||||
}
|
||||
|
||||
if (showRecurrence === true) {
|
||||
Log.debug(`saving event: ${description}`);
|
||||
Log.debug(`saving event: ${recurrenceTitle}`);
|
||||
newEvents.push({
|
||||
title: recurrenceTitle,
|
||||
startDate: startMoment.format("x"),
|
||||
@@ -421,7 +429,10 @@ const CalendarFetcherUtils = {
|
||||
geo: geo,
|
||||
description: description
|
||||
});
|
||||
} else {
|
||||
Log.debug("not saving event ", recurrenceTitle, new Date(startMoment));
|
||||
}
|
||||
Log.debug(" ");
|
||||
}
|
||||
// End recurring event parsing.
|
||||
} else {
|
||||
@@ -472,7 +483,9 @@ const CalendarFetcherUtils = {
|
||||
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
|
||||
@@ -488,6 +501,200 @@ const CalendarFetcherUtils = {
|
||||
return newEvents;
|
||||
},
|
||||
|
||||
/**
|
||||
* fixup thew event fields that have dates to use local time
|
||||
* BEFORE calling rrule.between
|
||||
* @param the event being processed
|
||||
* @returns nothing
|
||||
*/
|
||||
fixEventtoLocal (event) {
|
||||
// if there are excluded dates, their date is incorrect and possibly key as well.
|
||||
if (event.exdate !== undefined) {
|
||||
Object.keys(event.exdate).forEach((dateKey) => {
|
||||
// get the date
|
||||
let exdate = event.exdate[dateKey];
|
||||
Log.debug("exdate w key=", exdate);
|
||||
//exdate=CalendarFetcherUtils.convertDateToLocalTime(exdate, event.end.tz)
|
||||
exdate = new Date(new Date(exdate.valueOf() - ((120 * 60 * 1000))).getTime());
|
||||
Log.debug("new exDate item=", exdate, " with old key=", dateKey);
|
||||
let newkey = exdate.toISOString().slice(0, 10);
|
||||
if (newkey !== dateKey) {
|
||||
Log.debug("new exDate item=", exdate, ` key=${newkey}`);
|
||||
event.exdate[newkey] = exdate;
|
||||
//delete event.exdate[dateKey]
|
||||
}
|
||||
});
|
||||
Log.debug("updated exdate list=", event.exdate);
|
||||
}
|
||||
if (event.recurrences) {
|
||||
Object.keys(event.recurrences).forEach((dateKey) => {
|
||||
let exdate = event.recurrences[dateKey];
|
||||
//exdate=new Date(new Date(exdate.valueOf()-(60*60*1000)).getTime())
|
||||
Log.debug("new recurrence item=", exdate, " with old key=", dateKey);
|
||||
exdate.start = CalendarFetcherUtils.convertDateToLocalTime(exdate.start, exdate.start.tz);
|
||||
exdate.end = CalendarFetcherUtils.convertDateToLocalTime(exdate.end, exdate.end.tz);
|
||||
Log.debug("adjusted recurringEvent start=", exdate.start, " end=", exdate.end);
|
||||
});
|
||||
}
|
||||
Log.debug("modified recurrences before rrule.between", event.recurrences);
|
||||
},
|
||||
|
||||
/**
|
||||
* convert a UTC date to local time
|
||||
* BEFORE calling rrule.between
|
||||
* @param date ti conert
|
||||
* tz event is currently in
|
||||
* @returns updated date object
|
||||
*/
|
||||
convertDateToLocalTime (date, tz) {
|
||||
let delta_tz_offset = 0;
|
||||
let now_offset = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess());
|
||||
let event_offset = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(tz);
|
||||
Log.debug("date to convert=", date);
|
||||
if (Math.sign(now_offset) !== Math.sign(event_offset)) {
|
||||
delta_tz_offset = Math.abs(now_offset) + Math.abs(event_offset);
|
||||
} else {
|
||||
// signs are the same
|
||||
// if negative
|
||||
if (Math.sign(now_offset) === -1) {
|
||||
// la looking at chicago
|
||||
if (now_offset < event_offset) { // 5 -7
|
||||
delta_tz_offset = now_offset - event_offset;
|
||||
}
|
||||
else { //7 -5 , chicago looking at LA
|
||||
delta_tz_offset = event_offset - now_offset;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// berlin looking at sydney
|
||||
if (now_offset < event_offset) { // 5 -7
|
||||
delta_tz_offset = event_offset - now_offset;
|
||||
Log.debug("less delta=", delta_tz_offset);
|
||||
}
|
||||
else { // 11 - 2, sydney looking at berlin
|
||||
delta_tz_offset = -(now_offset - event_offset);
|
||||
Log.debug("more delta=", delta_tz_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
const newdate = new Date(new Date(date.valueOf() + (delta_tz_offset * 60 * 1000)).getTime());
|
||||
Log.debug("modified date =", newdate);
|
||||
return newdate;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the exdate/recurrence hash key from the date object
|
||||
* BEFORE calling rrule.between
|
||||
* @param the date of the event
|
||||
* @returns string date key YYYY-MM-DD
|
||||
*/
|
||||
getDateKeyFromDate (date) {
|
||||
// get our runtime timezone offset
|
||||
const nowDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess());
|
||||
let startday = date.getDate();
|
||||
let adjustment = 0;
|
||||
Log.debug(" day of month=", (`0${startday}`).slice(-2), " nowDiff=", nowDiff, ` start time=${date.toString().split(" ")[4].slice(0, 2)}`);
|
||||
Log.debug("date string= ", date.toString());
|
||||
Log.debug("date iso string ", date.toISOString());
|
||||
// if the dates are different
|
||||
if (date.toString().slice(8, 10) < date.toISOString().slice(8, 10)) {
|
||||
startday = date.toString().slice(8, 10);
|
||||
Log.debug("< ", startday);
|
||||
} else { // tostring is more
|
||||
if (date.toString().slice(8, 10) > date.toISOString().slice(8, 10)) {
|
||||
startday = date.toISOString().slice(8, 10);
|
||||
Log.debug("> ", startday);
|
||||
}
|
||||
}
|
||||
return date.toISOString().substring(0, 8) + (`0${startday}`).slice(-2);
|
||||
},
|
||||
|
||||
/**
|
||||
* get the timezone offset from the timezone string
|
||||
*
|
||||
* @param the timezone string
|
||||
* @returns the numerical offset
|
||||
*/
|
||||
getTimezoneOffsetFromTimezone (timeZone) {
|
||||
const str = new Date().toLocaleString("en", { timeZone, timeZoneName: "longOffset" });
|
||||
Log.debug("tz offset=", str);
|
||||
const [_, h, m] = str.match(/([+-]\d+):(\d+)$/) || ["", "+00", "00"];
|
||||
return h * 60 + (h > 0 ? +m : -m);
|
||||
},
|
||||
|
||||
/**
|
||||
* fixup the date start moment after rrule.between returns date array
|
||||
*
|
||||
* @param date object from rrule.between results
|
||||
* the event object it came from
|
||||
* @returns moment object
|
||||
*/
|
||||
getAdjustedStartMoment (date, event) {
|
||||
|
||||
let startMoment = moment(date);
|
||||
|
||||
Log.debug("startMoment pre=", startMoment);
|
||||
// get our runtime timezone offset
|
||||
const nowDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess()); // 10/18 16:49, 300
|
||||
let eventDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(event.end.tz); // watch out, start tz is cleared to handle rrule 120 23:49
|
||||
|
||||
Log.debug("tz diff event=", eventDiff, " local=", nowDiff, " end event timezone=", event.end.tz);
|
||||
|
||||
// if the diffs are different (not same tz for processing as event)
|
||||
if (nowDiff !== eventDiff) {
|
||||
// if signs are different
|
||||
if (Math.sign(nowDiff) !== Math.sign(eventDiff)) {
|
||||
// its the accumulated total
|
||||
Log.debug("diff signs, accumulate");
|
||||
eventDiff = Math.abs(eventDiff) + Math.abs(nowDiff);
|
||||
// sign of diff depends on where you are looking at which event.
|
||||
// australia looking at US, add to get same time
|
||||
Log.debug("new different event diff=", eventDiff);
|
||||
if (Math.sign(nowDiff) === -1) {
|
||||
eventDiff *= -1;
|
||||
// US looking at australia event have to subtract
|
||||
Log.debug("new diff, same sign, total event diff=", eventDiff);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// signs are the same, all east of UTC or all west of UTC
|
||||
// if the signs are negative (west of UTC)
|
||||
Log.debug("signs are the same");
|
||||
if (Math.sign(eventDiff) === -1) {
|
||||
//if west, looking at more west
|
||||
if (nowDiff < eventDiff) {
|
||||
//-600 -420
|
||||
eventDiff = -(eventDiff - (nowDiff - eventDiff)); //-180
|
||||
Log.debug("now looking back east delta diff=", eventDiff);
|
||||
}
|
||||
else {
|
||||
Log.debug("now looking more west");
|
||||
eventDiff = Math.abs(eventDiff - nowDiff);
|
||||
}
|
||||
} else {
|
||||
Log.debug("signs are both positive");
|
||||
// signs are positive (east of UTC)
|
||||
// berlin < sydney
|
||||
if (nowDiff < eventDiff) {
|
||||
// germany vs australia
|
||||
eventDiff = -(eventDiff - nowDiff);
|
||||
}
|
||||
else {
|
||||
// australia vs germany
|
||||
//eventDiff = eventDiff; //- nowDiff
|
||||
}
|
||||
}
|
||||
}
|
||||
startMoment = moment.tz(new Date(date.valueOf() + (eventDiff * (60 * 1000))), event.end.tz);
|
||||
} else {
|
||||
Log.debug("same tz event and display");
|
||||
eventDiff = 0;
|
||||
startMoment = moment.tz(new Date(date.valueOf() - (eventDiff * (60 * 1000))), event.end.tz);
|
||||
}
|
||||
Log.debug("startMoment post=", startMoment);
|
||||
return startMoment;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup iana tz from windows
|
||||
* @param {string} msTZName the timezone name to lookup
|
||||
|
Reference in New Issue
Block a user