mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 04:45:17 +00:00
Release 2.32.0 (#3826)
## [2.32.0] - 2025-07-01 Thanks to: @bughaver, @bugsounet, @khassel, @KristjanESPERANTO, @plebcity, @rejas, @sdetweil. > ⚠️ This release needs nodejs version `v22.14.0 or higher` ### Added - [config] Allow to change module order for final renderer (or dynamically with CSS): Feature `order` in config (#3762) - [clock] Added option 'disableNextEvent' to hide next sun event (#3769) - [clock] Implement short syntax for clock week (#3775) ### Changed - [refactor] Simplify module loading process (#3766) - Use `node --run` instead of `npm run` (#3764) and adapt `start:dev` script (#3773) - [workflow] Run linter and spellcheck with LTS node version (#3767) - [workflow] Split "Run test" step into two steps for more clarity (#3767) - [linter] Review linter setup (#3783) - Fix command to lint markdown in `CONTRIBUTING.md` - Re-activate JSDoc linting and fix linting issues - Refactor ESLint config to use `defineConfig` and `globalIgnores` - Replace `eslint-plugin-import` with `eslint-plugin-import-x` - Switch Stylelint config to flat format and simplify Stylelint scripts - [workflow] Replace Node.js version v23 with v24 (#3770) - [refactor] Replace deprecated constants `fs.F_OK` and `fs.R_OK` (#3789) - [refactor] Replace `ansis` with built-in function `util.styleText` (#3793) - [core] Integrate stuff from `vendor` and `fonts` folders into main `package.json`, simplifies install and maintaining dependencies (#3795, #3805) - [l10n] Complete translations (with the help of translation tools) (#3794) - [refactor] Refactored `calendarfetcherutils` in Calendar module to handle timezones better (#3806) - Removed as many of the date conversions as possible - Use `moment-timezone` when calculating recurring events, this will fix problems from the past with offsets and DST not being handled properly - Added some tests to test the behavior of the refactored methods to make sure the correct event dates are returned - [linter] Enable ESLint rule `no-console` and replace `console` with `Log` in some files (#3810) - [tests] Review and refactor translation tests (#3792) ### Fixed - [fix] Handle spellcheck issues (#3783) - [calendar] fix fullday event rrule until with timezone offset (#3781) - [feat] Add rule `no-undef` in config file validation to fix #3785 (#3786) - [fonts] Fix `roboto.css` to avoid error message `Unknown descriptor 'var(' in @font-face rule.` in firefox console (#3787) - [tests] Fix and refactor e2e test `Same keys` in `translations_spec.js` (#3809) - [tests] Fix e2e tests newsfeed and calendar to exit without open handles (#3817) ### Updated - [core] Update dependencies including electron to v36 (#3774, #3788, #3811, #3804, #3815, #3823) - [core] Update package type to `commonjs` - [logger] Review factory code part: use `switch/case` instead of `if/else if` (#3812) --------- 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: 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: sam detweiler <sdetweil@gmail.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> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: DevIncomin <56730075+Developer-Incoming@users.noreply.github.com> Co-authored-by: Nathan <n8nyoung@gmail.com> Co-authored-by: mixasgr <mixasgr@users.noreply.github.com> Co-authored-by: Savvas Adamtziloglou <savvas-gr@greeklug.gr> Co-authored-by: Konstantinos <geraki@gmail.com> Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com> Co-authored-by: BugHaver <43462320+bughaver@users.noreply.github.com> Co-authored-by: BugHaver <43462320+lsaadeh@users.noreply.github.com> Co-authored-by: Koen Konst <koenspero@gmail.com> Co-authored-by: Koen Konst <c.h.konst@avisi.nl>
This commit is contained in:
@@ -77,7 +77,7 @@ Module.register("calendar", {
|
||||
|
||||
// Define required scripts.
|
||||
getScripts () {
|
||||
return ["calendarutils.js", "moment.js"];
|
||||
return ["calendarutils.js", "moment.js", "moment-timezone.js"];
|
||||
},
|
||||
|
||||
// Define required translations.
|
||||
@@ -215,18 +215,9 @@ Module.register("calendar", {
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
},
|
||||
|
||||
eventEndingWithinNextFullTimeUnit (event, ONE_DAY) {
|
||||
const now = new Date();
|
||||
return event.endDate - now <= ONE_DAY;
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom () {
|
||||
const ONE_SECOND = 1000; // 1,000 milliseconds
|
||||
const ONE_MINUTE = ONE_SECOND * 60;
|
||||
const ONE_HOUR = ONE_MINUTE * 60;
|
||||
const ONE_DAY = ONE_HOUR * 24;
|
||||
|
||||
const events = this.createEventList(true);
|
||||
const wrapper = document.createElement("table");
|
||||
wrapper.className = this.config.tableClass;
|
||||
@@ -258,7 +249,9 @@ Module.register("calendar", {
|
||||
let lastSeenDate = "";
|
||||
|
||||
events.forEach((event, index) => {
|
||||
const dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
|
||||
const eventStartDateMoment = this.timestampToMoment(event.startDate);
|
||||
const eventEndDateMoment = this.timestampToMoment(event.endDate);
|
||||
const dateAsString = eventStartDateMoment.format(this.config.dateFormat);
|
||||
if (this.config.timeFormat === "dateheaders") {
|
||||
if (lastSeenDate !== dateAsString) {
|
||||
const dateRow = document.createElement("tr");
|
||||
@@ -340,7 +333,7 @@ Module.register("calendar", {
|
||||
repeatingCountTitle = this.countTitleForUrl(event.url);
|
||||
|
||||
if (repeatingCountTitle !== "") {
|
||||
const thisYear = new Date(parseInt(event.startDate)).getFullYear(),
|
||||
const thisYear = eventStartDateMoment.year(),
|
||||
yearDiff = thisYear - event.firstYear;
|
||||
|
||||
repeatingCountTitle = `, ${yearDiff} ${repeatingCountTitle}`;
|
||||
@@ -395,14 +388,14 @@ Module.register("calendar", {
|
||||
timeWrapper.className = `time light ${this.config.flipDateHeaderTitle ? "align-right " : "align-left "}${this.timeClassForUrl(event.url)}`;
|
||||
timeWrapper.style.paddingLeft = "2px";
|
||||
timeWrapper.style.textAlign = this.config.flipDateHeaderTitle ? "right" : "left";
|
||||
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
|
||||
timeWrapper.innerHTML = eventStartDateMoment.format("LT");
|
||||
|
||||
// Add endDate to dataheaders if showEnd is enabled
|
||||
if (this.config.showEnd) {
|
||||
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
|
||||
// no duration here, don't display end
|
||||
} else {
|
||||
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(moment(event.endDate, "x").format("LT"))}`;
|
||||
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(eventEndDateMoment.format("LT"))}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,44 +408,43 @@ Module.register("calendar", {
|
||||
const timeWrapper = document.createElement("td");
|
||||
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
const now = new Date();
|
||||
const now = moment();
|
||||
|
||||
if (this.config.timeFormat === "absolute") {
|
||||
// Use dateFormat
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(eventStartDateMoment.format(this.config.dateFormat));
|
||||
// Add end time if showEnd
|
||||
if (this.config.showEnd) {
|
||||
// and has a duation
|
||||
if (event.startDate !== event.endDate) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(eventEndDateMoment.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));
|
||||
eventEndDateMoment.subtract(1, "second");
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(eventStartDateMoment.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")) {
|
||||
if (this.config.showEnd && !this.config.showEndsOnlyWithDuration && !eventStartDateMoment.isSame(eventEndDateMoment, "d")) {
|
||||
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) {
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(eventEndDateMoment.format(this.config.fullDayEventDateFormat));
|
||||
} else if (!eventStartDateMoment.isSame(eventEndDateMoment, "d") && eventStartDateMoment.isBefore(now)) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(now.format(this.config.fullDayEventDateFormat));
|
||||
}
|
||||
} else if (this.config.getRelative > 0 && eventStartDateMoment.isBefore(now)) {
|
||||
// Ongoing and getRelative is set
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
this.translate("RUNNING", {
|
||||
fallback: `${this.translate("RUNNING")} {timeUntilEnd}`,
|
||||
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
|
||||
timeUntilEnd: eventEndDateMoment.fromNow(true)
|
||||
})
|
||||
);
|
||||
} else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * ONE_DAY) {
|
||||
} else if (this.config.urgency > 0 && eventStartDateMoment.diff(now, "d") < this.config.urgency) {
|
||||
// Within urgency days
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").fromNow());
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(eventStartDateMoment.fromNow());
|
||||
}
|
||||
if (event.fullDayEvent && this.config.nextDaysRelative) {
|
||||
// Full days events within the next two days
|
||||
@@ -460,9 +452,9 @@ Module.register("calendar", {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
|
||||
} else if (event.yesterday) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("YESTERDAY"));
|
||||
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
|
||||
} else if (event.tomorrow) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TOMORROW"));
|
||||
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
|
||||
} else if (event.dayAfterTomorrow) {
|
||||
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
|
||||
}
|
||||
@@ -470,15 +462,15 @@ Module.register("calendar", {
|
||||
}
|
||||
} else {
|
||||
// Show relative times
|
||||
if (event.startDate >= now || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
if (eventStartDateMoment.isSameOrAfter(now) || (event.fullDayEvent && eventEndDateMoment.diff(now, "days") === 0)) {
|
||||
// Use relative time
|
||||
if (!this.config.hideTime && !event.fullDayEvent) {
|
||||
Log.debug("event not hidden and not fullday");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }))}`;
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(eventStartDateMoment.calendar(null, { sameElse: this.config.dateFormat }))}`;
|
||||
} else {
|
||||
Log.debug("event full day or hidden");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(
|
||||
moment(event.startDate, "x").calendar(null, {
|
||||
eventStartDateMoment.calendar(null, {
|
||||
sameDay: this.config.showTimeToday ? "LT" : `[${this.translate("TODAY")}]`,
|
||||
nextDay: `[${this.translate("TOMORROW")}]`,
|
||||
nextWeek: "dddd",
|
||||
@@ -488,7 +480,7 @@ Module.register("calendar", {
|
||||
}
|
||||
if (event.fullDayEvent) {
|
||||
// Full days events within the next two days
|
||||
if (event.today || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
if (event.today || (event.fullDayEvent && eventEndDateMoment.diff(now, "days") === 0)) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
|
||||
} else if (event.dayBeforeYesterday) {
|
||||
if (this.translate("DAYBEFOREYESTERDAY") !== "DAYBEFOREYESTERDAY") {
|
||||
@@ -496,25 +488,25 @@ Module.register("calendar", {
|
||||
}
|
||||
} else if (event.yesterday) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("YESTERDAY"));
|
||||
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
|
||||
} else if (event.tomorrow) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TOMORROW"));
|
||||
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
|
||||
} else if (event.dayAfterTomorrow) {
|
||||
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
|
||||
}
|
||||
}
|
||||
Log.info("event fullday");
|
||||
} else if (event.startDate - now < this.config.getRelative * ONE_HOUR) {
|
||||
} else if (eventStartDateMoment.diff(now, "h") < this.config.getRelative) {
|
||||
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(eventStartDateMoment.fromNow())}`;
|
||||
}
|
||||
} else {
|
||||
// Ongoing event
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
this.translate("RUNNING", {
|
||||
fallback: `${this.translate("RUNNING")} {timeUntilEnd}`,
|
||||
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
|
||||
timeUntilEnd: eventEndDateMoment.fromNow(true)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -593,46 +585,46 @@ Module.register("calendar", {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* converts the given timestamp to a moment with a timezone
|
||||
* @param {number} timestamp timestamp from an event
|
||||
* @returns {moment.Moment} moment with a timezone
|
||||
*/
|
||||
timestampToMoment (timestamp) {
|
||||
return moment(timestamp, "x").tz(moment.tz.guess());
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the sorted list of all events.
|
||||
* @param {boolean} limitNumberOfEntries Whether to filter returned events for display.
|
||||
* @returns {object[]} Array with events.
|
||||
*/
|
||||
createEventList (limitNumberOfEntries) {
|
||||
const ONE_SECOND = 1000; // 1,000 milliseconds
|
||||
const ONE_MINUTE = ONE_SECOND * 60;
|
||||
const ONE_HOUR = ONE_MINUTE * 60;
|
||||
const ONE_DAY = ONE_HOUR * 24;
|
||||
let now = moment();
|
||||
let today = now.clone().startOf("day");
|
||||
let future = now.clone().startOf("day").add(this.config.maximumNumberOfDays, "days");
|
||||
|
||||
let now, today, future;
|
||||
if (this.config.forceUseCurrentTime || this.defaults.forceUseCurrentTime) {
|
||||
now = new Date();
|
||||
today = moment().startOf("day");
|
||||
future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
|
||||
} else {
|
||||
now = new Date(Date.now()); // Can use overridden time
|
||||
today = moment(now).startOf("day");
|
||||
future = moment(now).startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
|
||||
}
|
||||
let events = [];
|
||||
|
||||
for (const calendarUrl in this.calendarData) {
|
||||
const calendar = this.calendarData[calendarUrl];
|
||||
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
|
||||
let maxPastDaysCompare = now - this.maximumPastDaysForUrl(calendarUrl) * ONE_DAY;
|
||||
let maxPastDaysCompare = now.clone().subtract(this.maximumPastDaysForUrl(calendarUrl), "days");
|
||||
let by_url_calevents = [];
|
||||
for (const e in calendar) {
|
||||
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||
const eventStartDateMoment = this.timestampToMoment(event.startDate);
|
||||
const eventEndDateMoment = this.timestampToMoment(event.endDate);
|
||||
|
||||
if (this.config.hidePrivate && event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
if (limitNumberOfEntries) {
|
||||
if (event.endDate < maxPastDaysCompare) {
|
||||
if (eventEndDateMoment.isBefore(maxPastDaysCompare)) {
|
||||
continue;
|
||||
}
|
||||
if (this.config.hideOngoing && event.startDate < now) {
|
||||
if (this.config.hideOngoing && eventStartDateMoment.isBefore(now)) {
|
||||
continue;
|
||||
}
|
||||
if (this.config.hideDuplicates && this.listContainsEvent(events, event)) {
|
||||
@@ -641,47 +633,46 @@ Module.register("calendar", {
|
||||
}
|
||||
|
||||
event.url = calendarUrl;
|
||||
event.today = event.startDate >= today && event.startDate < today + ONE_DAY;
|
||||
event.dayBeforeYesterday = event.startDate >= today - ONE_DAY * 2 && event.startDate < today - ONE_DAY;
|
||||
event.yesterday = event.startDate >= today - ONE_DAY && event.startDate < today;
|
||||
event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY;
|
||||
event.dayAfterTomorrow = !event.tomorrow && event.startDate >= today + ONE_DAY * 2 && event.startDate < today + 3 * ONE_DAY;
|
||||
event.today = eventStartDateMoment.isSame(now, "d");
|
||||
event.dayBeforeYesterday = eventStartDateMoment.isSame(now.clone().subtract(2, "days"), "d");
|
||||
event.yesterday = eventStartDateMoment.isSame(now.clone().subtract(1, "days"), "d");
|
||||
event.tomorrow = eventStartDateMoment.isSame(now.clone().add(1, "days"), "d");
|
||||
event.dayAfterTomorrow = eventStartDateMoment.isSame(now.clone().add(2, "days"), "d");
|
||||
|
||||
/*
|
||||
* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
|
||||
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
|
||||
*/
|
||||
const maxCount = Math.round((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / ONE_DAY) + 1;
|
||||
const maxCount = eventEndDateMoment.diff(eventStartDateMoment, "days");
|
||||
if (this.config.sliceMultiDayEvents && maxCount > 1) {
|
||||
const splitEvents = [];
|
||||
let midnight
|
||||
= moment(event.startDate, "x")
|
||||
= eventStartDateMoment
|
||||
.clone()
|
||||
.startOf("day")
|
||||
.add(1, "day")
|
||||
.endOf("day")
|
||||
.format("x");
|
||||
.endOf("day");
|
||||
let count = 1;
|
||||
while (event.endDate > midnight) {
|
||||
while (eventEndDateMoment.isAfter(midnight)) {
|
||||
const thisEvent = JSON.parse(JSON.stringify(event)); // clone object
|
||||
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + ONE_DAY;
|
||||
thisEvent.tomorrow = !thisEvent.today && thisEvent.startDate >= today + ONE_DAY && thisEvent.startDate < today + 2 * ONE_DAY;
|
||||
thisEvent.endDate = moment(midnight, "x").clone().subtract(1, "day").format("x");
|
||||
thisEvent.today = this.timestampToMoment(thisEvent.startDate).isSame(now, "d");
|
||||
thisEvent.tomorrow = this.timestampToMoment(thisEvent.startDate).isSame(now.clone().add(1, "days"), "d");
|
||||
thisEvent.endDate = midnight.clone().subtract(1, "day").format("x");
|
||||
thisEvent.title += ` (${count}/${maxCount})`;
|
||||
splitEvents.push(thisEvent);
|
||||
|
||||
event.startDate = midnight;
|
||||
event.startDate = midnight.format("x");
|
||||
count += 1;
|
||||
midnight = moment(midnight, "x").add(1, "day").endOf("day").format("x"); // next day
|
||||
midnight = midnight.clone().add(1, "day").endOf("day"); // next day
|
||||
}
|
||||
// Last day
|
||||
event.title += ` (${count}/${maxCount})`;
|
||||
event.today += event.startDate >= today && event.startDate < today + ONE_DAY;
|
||||
event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY;
|
||||
event.today += this.timestampToMoment(event.startDate).isSame(now, "d");
|
||||
event.tomorrow = this.timestampToMoment(event.startDate).isSame(now.clone().add(1, "days"), "d");
|
||||
splitEvents.push(event);
|
||||
|
||||
for (let splitEvent of splitEvents) {
|
||||
if (splitEvent.endDate > now && splitEvent.endDate <= future) {
|
||||
if (this.timestampToMoment(splitEvent.endDate).isAfter(now) && this.timestampToMoment(splitEvent.endDate).isSameOrBefore(future)) {
|
||||
by_url_calevents.push(splitEvent);
|
||||
}
|
||||
}
|
||||
@@ -716,16 +707,16 @@ Module.register("calendar", {
|
||||
*/
|
||||
if (this.config.limitDays > 0) {
|
||||
let newEvents = [];
|
||||
let lastDate = today.clone().subtract(1, "days").format("YYYYMMDD");
|
||||
let lastDate = today.clone().subtract(1, "days");
|
||||
let days = 0;
|
||||
for (const ev of events) {
|
||||
let eventDate = moment(ev.startDate, "x").format("YYYYMMDD");
|
||||
let eventDate = this.timestampToMoment(ev.startDate);
|
||||
|
||||
/*
|
||||
* if date of event is later than lastdate
|
||||
* check if we already are showing max unique days
|
||||
*/
|
||||
if (eventDate > lastDate) {
|
||||
if (eventDate.isAfter(lastDate)) {
|
||||
// if the only entry in the first day is a full day event that day is not counted as unique
|
||||
if (!this.config.limitDaysNeverSkip && newEvents.length === 1 && days === 1 && newEvents[0].fullDayEvent) {
|
||||
days--;
|
||||
|
@@ -81,10 +81,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
||||
* Schedule the timer for the next update.
|
||||
*/
|
||||
const scheduleTimer = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchCalendar();
|
||||
}, reloadInterval);
|
||||
if (process.env.JEST_WORKER_ID === undefined) {
|
||||
// only set timer when not running in jest
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchCalendar();
|
||||
}, reloadInterval);
|
||||
}
|
||||
};
|
||||
|
||||
/* public methods */
|
||||
|
@@ -1,114 +1,130 @@
|
||||
/**
|
||||
* @external Moment
|
||||
*/
|
||||
const path = require("node:path");
|
||||
const moment = require("moment");
|
||||
const moment = require("moment-timezone");
|
||||
|
||||
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
|
||||
* Determine based on the title of an event if it should be excluded from the list of events
|
||||
* TODO This seems like an overly complicated way to exclude events based on the title.
|
||||
* @param {object} config the global config
|
||||
* @param {string} title the title of the event
|
||||
* @returns {object} excluded: true if the event should be excluded, false otherwise
|
||||
* until: the date until the event should be excluded.
|
||||
*/
|
||||
calculateTimezoneAdjustment (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}`);
|
||||
shouldEventBeExcluded (config, title) {
|
||||
let filter = {
|
||||
excluded: false,
|
||||
until: null
|
||||
};
|
||||
for (let f in config.excludedEvents) {
|
||||
let filter = config.excludedEvents[f],
|
||||
testTitle = title.toLowerCase(),
|
||||
until = null,
|
||||
useRegex = false,
|
||||
regexFlags = "g";
|
||||
|
||||
// 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)
|
||||
if (filter instanceof Object) {
|
||||
if (typeof filter.until !== "undefined") {
|
||||
until = filter.until;
|
||||
}
|
||||
}
|
||||
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 = 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}`);
|
||||
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 {
|
||||
// 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();
|
||||
filter = filter.toLowerCase();
|
||||
}
|
||||
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");
|
||||
if (CalendarFetcherUtils.titleFilterApplies(testTitle, filter, useRegex, regexFlags)) {
|
||||
if (until) {
|
||||
filter.until = until;
|
||||
} else {
|
||||
filter.excluded = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.debug(`adjustHours=${adjustHours}`);
|
||||
return adjustHours;
|
||||
return filter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get local timezone.
|
||||
* This method makes it easier to test if different timezones cause problems by changing this implementation.
|
||||
* @returns {string} timezone
|
||||
*/
|
||||
getLocalTimezone () {
|
||||
return moment.tz.guess();
|
||||
},
|
||||
|
||||
/**
|
||||
* This function returns a list of moments for a recurring event.
|
||||
* @param {object} event the current event which is a recurring event
|
||||
* @param {moment.Moment} pastLocalMoment The past date to search for recurring events
|
||||
* @param {moment.Moment} futureLocalMoment The future date to search for recurring events
|
||||
* @param {number} durationInMs the duration of the event, this is used to take into account currently running events
|
||||
* @returns {moment.Moment[]} All moments for the recurring event
|
||||
*/
|
||||
getMomentsFromRecurringEvent (event, pastLocalMoment, futureLocalMoment, durationInMs) {
|
||||
const rule = event.rrule;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// subtract the max of the duration of this event or 1 day to find events in the past that are currently still running and should therefor be displayed.
|
||||
const oneDayInMs = 24 * 60 * 60000;
|
||||
let searchFromDate = pastLocalMoment.clone().subtract(Math.max(durationInMs, oneDayInMs), "milliseconds").toDate();
|
||||
let searchToDate = futureLocalMoment.clone().add(1, "days").toDate();
|
||||
Log.debug(`Search for recurring events between: ${searchFromDate} and ${searchToDate}`);
|
||||
|
||||
// if until is set, and its a full day event, force the time to midnight. rrule gets confused with non-00 offset
|
||||
// looks like MS Outlook sets the until time incorrectly for fullday events
|
||||
if ((rule.options.until !== undefined) && CalendarFetcherUtils.isFullDayEvent(event)) {
|
||||
Log.debug("fixup rrule until");
|
||||
rule.options.until = moment(rule.options.until).clone().startOf("day").add(1, "day")
|
||||
.toDate();
|
||||
}
|
||||
|
||||
Log.debug("fix rrule start=", rule.options.dtstart);
|
||||
Log.debug("event before rrule.between=", JSON.stringify(event, null, 2), "exdates=", event.exdate);
|
||||
|
||||
Log.debug(`RRule: ${rule.toString()}`);
|
||||
rule.options.tzid = null; // RRule gets *very* confused with timezones
|
||||
|
||||
let dates = rule.between(searchFromDate, searchToDate, 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) => {
|
||||
return JSON.stringify(d) !== "null";
|
||||
});
|
||||
|
||||
// Dates are returned in UTC timezone but with localdatetime because tzid is null.
|
||||
// So we map the date to a moment using the original timezone of the event.
|
||||
return dates.map((d) => (event.start.tz ? moment.tz(d, "UTC").tz(event.start.tz, true) : moment.tz(d, "UTC").tz(CalendarFetcherUtils.getLocalTimezone(), true)));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -120,34 +136,33 @@ const CalendarFetcherUtils = {
|
||||
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]);
|
||||
const startMoment = event[time].tz ? moment.tz(event[time], event[time].tz) : moment.tz(event[time], CalendarFetcherUtils.getLocalTimezone());
|
||||
return CalendarFetcherUtils.isFullDayEvent(event) ? startMoment.startOf("day") : startMoment;
|
||||
};
|
||||
|
||||
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)
|
||||
const now = moment();
|
||||
const pastLocalMoment = config.includePastEvents ? now.clone().startOf("day").subtract(config.maximumNumberOfDays, "days") : now;
|
||||
const futureLocalMoment
|
||||
= now
|
||||
.clone()
|
||||
.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();
|
||||
// Subtract 1 second so that events that start on the middle of the night will not repeat.
|
||||
.subtract(1, "seconds");
|
||||
|
||||
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();
|
||||
const title = CalendarFetcherUtils.getTitleFromEvent(event);
|
||||
Log.debug(`title: ${title}`);
|
||||
|
||||
// Return quickly if event should be excluded.
|
||||
let { excluded, eventFilterUntil } = this.shouldEventBeExcluded(config, title);
|
||||
if (excluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Ugly fix to solve the facebook birthday issue.
|
||||
@@ -161,211 +176,47 @@ const CalendarFetcherUtils = {
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
Log.debug(`Event:\n${JSON.stringify(event, null, 2)}`);
|
||||
let startMoment = eventDate(event, "start");
|
||||
let endMoment;
|
||||
let eventStartMoment = eventDate(event, "start");
|
||||
let eventEndMoment;
|
||||
|
||||
if (typeof event.end !== "undefined") {
|
||||
endMoment = eventDate(event, "end");
|
||||
eventEndMoment = eventDate(event, "end");
|
||||
} else if (typeof event.duration !== "undefined") {
|
||||
endMoment = startMoment.clone().add(moment.duration(event.duration));
|
||||
eventEndMoment = eventStartMoment.clone().add(moment.duration(event.duration));
|
||||
} else {
|
||||
if (!isFacebookBirthday) {
|
||||
// make copy of start date, separate storage area
|
||||
endMoment = moment(startMoment.valueOf());
|
||||
eventEndMoment = eventStartMoment.clone();
|
||||
} else {
|
||||
endMoment = moment(startMoment).add(1, "days");
|
||||
eventEndMoment = eventStartMoment.clone().add(1, "days");
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug(`start: ${startMoment.toDate()}`);
|
||||
Log.debug(`end:: ${endMoment.toDate()}`);
|
||||
Log.debug(`start: ${eventStartMoment.toDate()}`);
|
||||
Log.debug(`end:: ${eventEndMoment.toDate()}`);
|
||||
|
||||
// Calculate the duration of the event for use with recurring events.
|
||||
const durationMs = endMoment.valueOf() - startMoment.valueOf();
|
||||
const durationMs = eventEndMoment.valueOf() - eventStartMoment.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;
|
||||
|
||||
// TODO This should be a seperate function.
|
||||
if (event.rrule && typeof event.rrule !== "undefined" && !isFacebookBirthday) {
|
||||
const rule = event.rrule;
|
||||
// Recurring event.
|
||||
let moments = CalendarFetcherUtils.getMomentsFromRecurringEvent(event, pastLocalMoment, futureLocalMoment, durationMs);
|
||||
|
||||
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];
|
||||
// Loop through the set of moment entries to see which recurrences should be added to our event list.
|
||||
// TODO This should create an event per moment so we can change anything we want.
|
||||
for (let m in moments) {
|
||||
let curEvent = event;
|
||||
let curDurationMs = durationMs;
|
||||
let showRecurrence = true;
|
||||
let recurringEventStartMoment = moments[m].tz(CalendarFetcherUtils.getLocalTimezone()).clone();
|
||||
let recurringEventEndMoment = recurringEventStartMoment.clone().add(durationMs, "ms");
|
||||
|
||||
let startMoment = moment(date);
|
||||
|
||||
let dateKey = CalendarFetcherUtils.getDateKeyFromDate(date);
|
||||
let dateKey = recurringEventStartMoment.tz("UTC").format("YYYY-MM-DD");
|
||||
|
||||
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.
|
||||
@@ -375,12 +226,17 @@ const CalendarFetcherUtils = {
|
||||
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();
|
||||
// Some event start/end dates don't have timezones
|
||||
if (curEvent.start.tz) {
|
||||
recurringEventStartMoment = moment(curEvent.start).tz(curEvent.start.tz).tz(CalendarFetcherUtils.getLocalTimezone());
|
||||
} else {
|
||||
recurringEventStartMoment = moment(curEvent.start).tz(CalendarFetcherUtils.getLocalTimezone());
|
||||
}
|
||||
if (curEvent.end.tz) {
|
||||
recurringEventEndMoment = moment(curEvent.end).tz(curEvent.end.tz).tz(CalendarFetcherUtils.getLocalTimezone());
|
||||
} else {
|
||||
recurringEventEndMoment = moment(curEvent.end).tz(CalendarFetcherUtils.getLocalTimezone());
|
||||
}
|
||||
} else {
|
||||
Log.debug("recurrence key ", dateKey, " doesn't match");
|
||||
}
|
||||
@@ -393,25 +249,20 @@ const CalendarFetcherUtils = {
|
||||
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");
|
||||
if (recurringEventStartMoment.valueOf() === recurringEventEndMoment.valueOf()) {
|
||||
recurringEventEndMoment = recurringEventEndMoment.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)) {
|
||||
if (recurringEventEndMoment.isBefore(pastLocalMoment) || recurringEventStartMoment.isAfter(futureLocalMoment)) {
|
||||
showRecurrence = false;
|
||||
}
|
||||
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, recurringEventEndMoment, eventFilterUntil)) {
|
||||
showRecurrence = false;
|
||||
}
|
||||
|
||||
@@ -419,8 +270,8 @@ const CalendarFetcherUtils = {
|
||||
Log.debug(`saving event: ${recurrenceTitle}`);
|
||||
newEvents.push({
|
||||
title: recurrenceTitle,
|
||||
startDate: startMoment.format("x"),
|
||||
endDate: endMoment.format("x"),
|
||||
startDate: recurringEventStartMoment.format("x"),
|
||||
endDate: recurringEventEndMoment.format("x"),
|
||||
fullDayEvent: CalendarFetcherUtils.isFullDayEvent(event),
|
||||
recurringEvent: true,
|
||||
class: event.class,
|
||||
@@ -430,7 +281,7 @@ const CalendarFetcherUtils = {
|
||||
description: description
|
||||
});
|
||||
} else {
|
||||
Log.debug("not saving event ", recurrenceTitle, new Date(startMoment));
|
||||
Log.debug("not saving event ", recurrenceTitle, eventStartMoment);
|
||||
}
|
||||
Log.debug(" ");
|
||||
}
|
||||
@@ -441,47 +292,41 @@ const CalendarFetcherUtils = {
|
||||
// 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 (fullDayEvent && eventStartMoment.valueOf() === eventEndMoment.valueOf()) {
|
||||
eventEndMoment = eventEndMoment.endOf("day");
|
||||
}
|
||||
|
||||
if (config.includePastEvents) {
|
||||
// Past event is too far in the past, so skip.
|
||||
if (endMoment < pastLocalDate) {
|
||||
if (eventEndMoment < pastLocalMoment) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// It's not a fullday event, and it is in the past, so skip.
|
||||
if (!fullDayEvent && endMoment < now) {
|
||||
if (!fullDayEvent && eventEndMoment < now) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's a fullday event, and it is before today, So skip.
|
||||
if (fullDayEvent && endMoment <= todayLocal) {
|
||||
if (fullDayEvent && eventEndMoment <= now.startOf("day")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It exceeds the maximumNumberOfDays limit, so skip.
|
||||
if (startMoment > futureLocalDate) {
|
||||
if (eventStartMoment > futureLocalMoment) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, eventEndMoment, eventFilterUntil)) {
|
||||
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"),
|
||||
startDate: eventStartMoment.format("x"),
|
||||
endDate: eventEndMoment.format("x"),
|
||||
fullDayEvent: fullDayEvent,
|
||||
recurringEvent: false,
|
||||
class: event.class,
|
||||
@@ -501,214 +346,6 @@ 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
|
||||
// -350 <-300
|
||||
if (nowDiff < eventDiff) {
|
||||
//-600 -420
|
||||
//300 -300 -360 +300
|
||||
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
|
||||
* @returns {string|null} the iana name or null of none is found
|
||||
*/
|
||||
getIanaTZFromMS (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.
|
||||
@@ -748,8 +385,8 @@ const CalendarFetcherUtils = {
|
||||
|
||||
/**
|
||||
* 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 {moment.Moment} now Date object using previously created object for consistency
|
||||
* @param {moment.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
|
||||
*/
|
||||
@@ -760,7 +397,7 @@ const CalendarFetcherUtils = {
|
||||
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.toDate();
|
||||
return now < filterUntil;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -14,7 +14,7 @@ Module.register("clock", {
|
||||
clockBold: false,
|
||||
showDate: true,
|
||||
showTime: true,
|
||||
showWeek: false,
|
||||
showWeek: false, // options: true, false, 'short'
|
||||
dateFormat: "dddd, LL",
|
||||
sendNotifications: false,
|
||||
|
||||
@@ -25,7 +25,7 @@ Module.register("clock", {
|
||||
analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
|
||||
secondsColor: "#888888", // DEPRECATED, use CSS instead. Class "clock-second-digital" for digital clock, "clock-second" for analog clock.
|
||||
|
||||
showSunTimes: false,
|
||||
showSunTimes: false, // options: true, false, 'disableNextEvent'
|
||||
showMoonTimes: false, // options: false, 'times' (rise/set), 'percent' (lit percent), 'phase' (current phase), or 'both' (percent & phase)
|
||||
lat: 47.630539,
|
||||
lon: -122.344147
|
||||
@@ -171,21 +171,28 @@ Module.register("clock", {
|
||||
if (this.config.showSunTimes) {
|
||||
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
|
||||
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
|
||||
let nextEvent;
|
||||
if (now.isBefore(sunTimes.sunrise)) {
|
||||
nextEvent = sunTimes.sunrise;
|
||||
} else if (now.isBefore(sunTimes.sunset)) {
|
||||
nextEvent = sunTimes.sunset;
|
||||
} else {
|
||||
const tomorrowSunTimes = SunCalc.getTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
|
||||
nextEvent = tomorrowSunTimes.sunrise;
|
||||
let sunWrapperInnerHTML = "";
|
||||
|
||||
if (this.config.showSunTimes !== "disableNextEvent") {
|
||||
let nextEvent;
|
||||
if (now.isBefore(sunTimes.sunrise)) {
|
||||
nextEvent = sunTimes.sunrise;
|
||||
} else if (now.isBefore(sunTimes.sunset)) {
|
||||
nextEvent = sunTimes.sunset;
|
||||
} else {
|
||||
const tomorrowSunTimes = SunCalc.getTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
|
||||
nextEvent = tomorrowSunTimes.sunrise;
|
||||
}
|
||||
const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
|
||||
const untilNextEventString = `${untilNextEvent.hours()}h ${untilNextEvent.minutes()}m`;
|
||||
|
||||
sunWrapperInnerHTML = `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-sun" aria-hidden="true"></i> ${untilNextEventString}</span>`;
|
||||
}
|
||||
const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
|
||||
const untilNextEventString = `${untilNextEvent.hours()}h ${untilNextEvent.minutes()}m`;
|
||||
sunWrapper.innerHTML
|
||||
= `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-sun" aria-hidden="true"></i> ${untilNextEventString}</span>`
|
||||
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
|
||||
|
||||
sunWrapperInnerHTML += `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
|
||||
|
||||
sunWrapper.innerHTML = sunWrapperInnerHTML;
|
||||
digitalWrapper.appendChild(sunWrapper);
|
||||
}
|
||||
|
||||
@@ -217,7 +224,12 @@ Module.register("clock", {
|
||||
}
|
||||
|
||||
if (this.config.showWeek) {
|
||||
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
|
||||
if (this.config.showWeek === "short") {
|
||||
weekWrapper.innerHTML = this.translate("WEEK_SHORT", { weekNumber: now.week() });
|
||||
} else {
|
||||
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
|
||||
}
|
||||
|
||||
digitalWrapper.appendChild(weekWrapper);
|
||||
}
|
||||
|
||||
|
@@ -181,6 +181,7 @@ Module.register("newsfeed", {
|
||||
* Gets a feed property by name
|
||||
* @param {object} feed A feed object.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {*} The value of the specified property for the feed.
|
||||
*/
|
||||
getFeedProperty (feed, property) {
|
||||
let res = this.config[property];
|
||||
|
@@ -128,10 +128,13 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
|
||||
* Schedule the timer for the next update.
|
||||
*/
|
||||
const scheduleTimer = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchNews();
|
||||
}, reloadIntervalMS);
|
||||
if (process.env.JEST_WORKER_ID === undefined) {
|
||||
// only set timer when not running in jest
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchNews();
|
||||
}, reloadIntervalMS);
|
||||
}
|
||||
};
|
||||
|
||||
/* public methods */
|
||||
|
@@ -133,10 +133,10 @@ class Updater {
|
||||
});
|
||||
}
|
||||
|
||||
// restart rules (pm2 or npm start)
|
||||
// restart rules (pm2 or node --run start)
|
||||
restart () {
|
||||
if (this.usePM2) this.pm2Restart();
|
||||
else this.npmRestart();
|
||||
else this.nodeRestart();
|
||||
}
|
||||
|
||||
// restart MagicMiror with "pm2": use PM2Id for restart it
|
||||
@@ -150,12 +150,12 @@ class Updater {
|
||||
});
|
||||
}
|
||||
|
||||
// restart MagicMiror with "npm start"
|
||||
npmRestart () {
|
||||
// restart MagicMiror with "node --run start"
|
||||
nodeRestart () {
|
||||
Log.info("updatenotification: Restarting MagicMirror...");
|
||||
const out = process.stdout;
|
||||
const err = process.stderr;
|
||||
const subprocess = Spawn("npm start", { cwd: this.root_path, shell: true, detached: true, stdio: ["ignore", out, err] });
|
||||
const subprocess = Spawn("node --run start", { cwd: this.root_path, shell: true, detached: true, stdio: ["ignore", out, err] });
|
||||
subprocess.unref(); // detach the newly launched process from the master process
|
||||
process.exit();
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
* @param {boolean} useCorsProxy A flag to indicate
|
||||
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
|
||||
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
|
||||
* @param {string} basePath, default /
|
||||
* @param {string} basePath The base path, default is "/"
|
||||
* @returns {Promise} resolved when the fetch is done. The response headers is placed in a headers-property (provided the response does not already contain a headers-property).
|
||||
*/
|
||||
async function performWebRequest (url, type = "json", useCorsProxy = false, requestHeaders = undefined, expectedResponseHeaders = undefined, basePath = "/") {
|
||||
@@ -38,7 +38,7 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
|
||||
* @param {string} url the url to fetch from
|
||||
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
|
||||
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
|
||||
* @param {string} basePath, default /
|
||||
* @param {string} basePath The base path, default is "/"
|
||||
* @returns {string} to be used as URL when calling CORS-method on server.
|
||||
*/
|
||||
const getCorsUrl = function (url, requestHeaders, expectedResponseHeaders, basePath = "/") {
|
||||
|
Reference in New Issue
Block a user