diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index f153f7c0..5701f69c 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -85,7 +85,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn }; const eventDate = function (event, time) { - return event[time].length === 8 ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time])); + return isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time])); }; Object.entries(data).forEach(([key, event]) => { @@ -110,19 +110,22 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn if (event.type === "VEVENT") { let startDate = eventDate(event, "start"); let endDate; - // console.log("\nevent="+JSON.stringify(event)) + // Log.debug("\nevent="+JSON.stringify(event)) if (typeof event.end !== "undefined") { endDate = eventDate(event, "end"); } else if (typeof event.duration !== "undefined") { endDate = startDate.clone().add(moment.duration(event.duration)); } else { if (!isFacebookBirthday) { - endDate = startDate; + // make copy of start date, separate storage area + endDate = moment(startDate.format("x"), "x"); } else { endDate = moment(startDate).add(1, "days"); } } + Log.debug(" start=" + startDate.toDate() + " end=" + endDate.toDate()); + // calculate the duration of the event for use with recurring events. let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x")); @@ -212,9 +215,9 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate(); futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate(); } - + Log.debug(" between=" + pastLocal + " to " + futureLocal); const dates = rule.between(pastLocal, futureLocal, true, limitFunction); - // console.log("title="+event.summary.val+" dates="+JSON.stringify(dates)) + Log.debug("title=" + event.summary + " dates=" + JSON.stringify(dates)); // The "dates" array contains the set of dates within our desired date range range that are valid // for the recurrence rule. *However*, it's possible for us to have a specific recurrence that // had its date changed from outside the range to inside the range. For the time being, @@ -231,10 +234,9 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn } } } - // Loop through the set of date entries to see which recurrences should be added to our event list. for (let d in dates) { - const date = dates[d]; + let date = dates[d]; // ical.js started returning recurrences and exdates as ISOStrings without time information. // .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same // (see https://github.com/peterbraden/ical.js/pull/84 ) @@ -242,54 +244,28 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn let curEvent = event; let showRecurrence = true; + // for full day events, the time might be off from RRULE/Luxon problem + if (isFullDayEvent(event)) { + Log.debug("fullday"); + // if the offset is negative, east of GMT where the problem is + if (date.getTimezoneOffset() < 0) { + // get the offset of today where we are processing + // this will be the correction we need to apply + let nowOffset = new Date().getTimezoneOffset(); + Log.debug("now offset is " + nowOffset); + // reduce the time by the offset + Log.debug(" recurring date is " + date + " offset is " + date.getTimezoneOffset()); + // apply the correction to the date/time to get it UTC relative + date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + duration = 24 * 60 * 60 * 1000; + Log.debug("new recurring date is " + date); + } + } startDate = moment(date); - // console.log("now timezone="+ moment.tz.guess()); - // whether we need to adjust for RRULE returning the wrong date, with the right time (forward of URC timezones) - let adjustDays = 0; - // if a timezone was specified - if (!event.start.tz) { - event.start.tz = moment.tz.guess(); - } - // console.log("tz="+event.start.tz) - if (event.start.tz) { - // if this is a windows timezone - if (event.start.tz.indexOf(" ") > 0) { - // use the lookup table to get theIANA name as moment and date don't know MS timezones - let tz = getIanaTZFromMS(event.start.tz); - // watch out for unregistered windows timezone names - // if we had a successfule lookup - if (tz) { - // change the timezone to the IANA name - event.start.tz = getIanaTZFromMS(event.start.tz); - // console.log("corrected timezone="+event.start.tz) - } - } - // get the start time in that timezone - let mms = moment.tz(moment(event.start), event.start.tz).utcOffset(); - // console.log("ms offset="+mms) - // get the specified date in that timezone - let mm = moment.tz(moment(date), event.start.tz); - let mmo = mm.utcOffset(); - // console.log("mm ofset="+ mmo+" hour="+mm.format("H")+" event date="+mm.toDate()) - // if the offset is greater than 0, east of london - if (mmo > 0) { - let h = parseInt(mm.format("H")); - // check if the event time is less than the offset - if (h > 0 && h < mmo / 60) { - // if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time) - // we need to fix that - adjustDays = 24; - // console.log("adjusting date") - } - if (mmo > mms) { - adjustDays += 1; - // console.log("adjust up 1 hour dst change") - } else if (mmo < mms) { - adjustDays -= 1; - //console.log("adjust down 1 hour dst change") - } - } - } + + let adjustDays = getCorrection(event, date); // For each date that we're checking, it's possible that there is a recurrence override for that one day. if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) { @@ -304,7 +280,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn showRecurrence = false; } - //console.log("duration="+duration) + Log.debug("duration=" + duration); endDate = moment(parseInt(startDate.format("x")) + duration, "x"); if (startDate.format("x") === endDate.format("x")) { @@ -327,8 +303,8 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn addedEvents++; newEvents.push({ title: recurrenceTitle, - startDate: (adjustDays ? startDate.subtract(adjustDays, "hours") : startDate).format("x"), - endDate: (adjustDays ? endDate.subtract(adjustDays, "hours") : endDate).format("x"), + startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"), + endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"), fullDayEvent: isFullDayEvent(event), recurringEvent: true, class: event.class, @@ -343,7 +319,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn } else { // Single event. const fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event); - // console.log("full day event") + // Log.debug("full day event") if (includePastEvents) { // Past event is too far in the past, so skip. if (endDate < past) { @@ -376,15 +352,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn } // if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00) if (fullDayEvent && startDate.format("x") === endDate.format("x")) { - //console.log("end same as start") + //Log.debug("end same as start") endDate = endDate.endOf("day"); } - + // get correction for date saving and dst change between now and then + let adjustDays = getCorrection(event, startDate.toDate()); // Every thing is good. Add it to the list. newEvents.push({ title: title, - startDate: startDate.format("x"), - endDate: endDate.format("x"), + startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"), + endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"), fullDayEvent: fullDayEvent, class: event.class, location: location, @@ -406,12 +383,107 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn }); }; + /* + * + * get the time correction, either dst/std or full day in cases where utc time is day before plus offset + * + */ + const getCorrection = function (event, date) { + let adjustHours = 0; + // if a timezone was specified + if (!event.start.tz) { + Log.debug(" if no tz, guess based on now"); + event.start.tz = moment.tz.guess(); + } + Log.debug("initial tz=" + event.start.tz); + + // if there is a start date specified + if (event.start.tz) { + // if this is a windows timezone + if (event.start.tz.includes(" ")) { + // use the lookup table to get theIANA name as moment and date don't know MS timezones + let tz = getIanaTZFrostart_offset(event.start.tz); + Log.debug("corrected TZ=" + tz); + // watch out for unregistered windows timezone names + // if we had a successfule lookup + if (tz) { + // change the timezone to the IANA name + event.start.tz = tz; + // Log.debug("corrected timezone="+event.start.tz) + } + } + Log.debug("corrected tz=" + event.start.tz); + let current_offset = 0; // offset from TZ string or calculated + let mm = 0; // date with tz or offset + let start_offset = 0; // utc offset of created with tz + // if there is still an offset, lookup failed, use it + if (event.start.tz.startsWith("(")) { + const regex = /[+|-]\d*:\d*/; + const start_offsetString = event.start.tz.match(regex).toString().split(":"); + let start_offset = parseInt(start_offsetString[0]); + start_offset *= event.start.tz[1] == "-" ? -1 : 1; + adjustHours = start_offset; + Log.debug("defined offset=" + start_offset + " hours"); + current_offset = start_offset; + event.start.tz = ""; + Log.debug("ical offset=" + current_offset + " date=" + date); + mm = moment(date); + let x = parseInt(moment(new Date()).utcOffset()); + Log.debug("net mins=" + (current_offset * 60 - x)); + + mm = mm.add(x - current_offset * 60, "minutes"); + adjustHours = (current_offset * 60 - x) / 60; + event.start = mm.toDate(); + Log.debug("adjusted date=" + event.start); + } else { + // get the start time in that timezone + Log.debug("start date/time=" + moment(event.start).toDate()); + start_offset = moment.tz(moment(event.start), event.start.tz).utcOffset(); + Log.debug("start offset=" + start_offset); + + Log.debug("start date/time w tz =" + moment.tz(moment(event.start), event.start.tz).toDate()); + + // get the specified date in that timezone + mm = moment.tz(moment(date), event.start.tz); + Log.debug("event date=" + mm.toDate()); + current_offset = mm.utcOffset(); + } + Log.debug("event offset=" + current_offset + " hour=" + mm.format("H") + " event date=" + mm.toDate()); + + // if the offset is greater than 0, east of london + if (current_offset !== start_offset) { + // big offset + Log.debug("offset"); + let h = parseInt(mm.format("H")); + // check if the event time is less than the offset + if (h > 0 && h < Math.abs(current_offset) / 60) { + // if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time) + // we need to fix that + adjustHours = 24; + // Log.debug("adjusting date") + } + //-300 > -240 + //if (Math.abs(current_offset) > Math.abs(start_offset)){ + if (current_offset > start_offset) { + adjustHours -= 1; + Log.debug("adjust down 1 hour dst change"); + //} else if (Math.abs(current_offset) < Math.abs(start_offset)) { + } else if (current_offset < start_offset) { + adjustHours += 1; + Log.debug("adjust up 1 hour dst change"); + } + } + } + Log.debug("adjustHours=" + adjustHours); + return adjustHours; + }; + /** * * lookup iana tz from windows */ let zoneTable = null; - const getIanaTZFromMS = function (msTZName) { + const getIanaTZFrostart_offset = function (msTZName) { if (!zoneTable) { const p = require("path"); zoneTable = require(p.join(__dirname, "windowsZones.json")); @@ -439,7 +511,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn * @returns {boolean} True if the event is a fullday event, false otherwise */ const isFullDayEvent = function (event) { - if (event.start.length === 8 || event.start.dateOnly) { + if (event.start.length === 8 || event.start.dateOnly || event.datetype === "date") { return true; }