2016-04-15 12:18:59 +02:00
/ * M a g i c M i r r o r
* Node Helper : Calendar - CalendarFetcher
*
2020-04-28 23:05:28 +02:00
* By Michael Teeuw https : //michaelteeuw.nl
2016-04-15 12:18:59 +02:00
* MIT Licensed .
* /
2020-05-31 22:12:26 +02:00
const Log = require ( "../../../js/logger.js" ) ;
2020-09-22 07:25:48 -05:00
const ical = require ( "node-ical" ) ;
2020-06-20 08:32:54 +02:00
const request = require ( "request" ) ;
2016-04-15 12:18:59 +02:00
2020-08-03 11:19:54 +02:00
/ * *
* Moment date
*
* @ external Moment
* @ see { @ link http : //momentjs.com}
* /
const moment = require ( "moment" ) ;
/ * *
*
* @ param { string } url The url of the calendar to fetch
* @ param { number } reloadInterval Time in ms the calendar is fetched again
* @ param { string [ ] } excludedEvents An array of words / phrases from event titles that will be excluded from being shown .
* @ param { number } maximumEntries The maximum number of events fetched .
* @ param { number } maximumNumberOfDays The maximum number of days an event should be in the future .
* @ param { object } auth The object containing options for authentication against the calendar .
* @ param { boolean } includePastEvents If true events from the past maximumNumberOfDays will be fetched too
* @ class
* /
2020-07-18 17:51:21 +02:00
const CalendarFetcher = function ( url , reloadInterval , excludedEvents , maximumEntries , maximumNumberOfDays , auth , includePastEvents ) {
2020-06-17 21:37:49 +02:00
const self = this ;
2016-04-15 12:18:59 +02:00
2020-06-18 21:54:51 +02:00
let reloadTimer = null ;
let events = [ ] ;
2016-04-15 12:18:59 +02:00
2020-06-17 21:37:49 +02:00
let fetchFailedCallback = function ( ) { } ;
let eventsReceivedCallback = function ( ) { } ;
2016-04-15 12:18:59 +02:00
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Initiates calendar fetch .
* /
2020-06-17 21:17:26 +02:00
const fetchCalendar = function ( ) {
2016-04-15 12:18:59 +02:00
clearTimeout ( reloadTimer ) ;
reloadTimer = null ;
2020-06-17 21:17:26 +02:00
const nodeVersion = Number ( process . version . match ( /^v(\d+\.\d+)/ ) [ 1 ] ) ;
const opts = {
2016-06-04 20:32:55 -06:00
headers : {
2020-05-11 22:22:32 +02:00
"User-Agent" : "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global . version + " (https://github.com/MichMich/MagicMirror/)"
2018-09-08 23:05:19 +08:00
} ,
2020-06-20 08:32:54 +02:00
gzip : true
2016-09-08 17:36:30 +02:00
} ;
2017-03-07 00:12:43 +01:00
if ( auth ) {
2020-05-11 22:22:32 +02:00
if ( auth . method === "bearer" ) {
2020-06-20 08:32:54 +02:00
opts . auth = {
bearer : auth . pass
} ;
2019-06-05 09:32:10 +02:00
} else {
2020-06-20 08:32:54 +02:00
opts . auth = {
user : auth . user ,
pass : auth . pass ,
sendImmediately : auth . method !== "digest"
} ;
2016-09-08 17:36:30 +02:00
}
2016-06-04 20:32:55 -06:00
}
2016-09-08 17:36:30 +02:00
2020-06-20 08:32:54 +02:00
request ( url , opts , function ( err , r , requestData ) {
if ( err ) {
fetchFailedCallback ( self , err ) ;
scheduleTimer ( ) ;
2020-06-20 08:45:03 +02:00
return ;
2020-06-20 08:32:54 +02:00
} else if ( r . statusCode !== 200 ) {
fetchFailedCallback ( self , r . statusCode + ": " + r . statusMessage ) ;
scheduleTimer ( ) ;
2020-06-20 08:45:03 +02:00
return ;
}
2017-02-07 23:51:13 +01:00
2020-11-24 21:32:16 +01:00
let data = [ ] ;
try {
data = ical . parseICS ( requestData ) ;
} catch ( error ) {
fetchFailedCallback ( self , error . message ) ;
scheduleTimer ( ) ;
return ;
}
2020-12-08 15:17:45 +01:00
Log . debug ( " parsed data=" + JSON . stringify ( data ) ) ;
2020-11-24 21:32:16 +01:00
2020-06-20 08:45:03 +02:00
const newEvents = [ ] ;
2019-04-01 15:39:25 -04:00
2020-06-20 08:45:03 +02:00
// 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 ;
} ;
2016-04-15 12:18:59 +02:00
2020-06-20 08:45:03 +02:00
const eventDate = function ( event , time ) {
2020-11-16 10:17:48 -05:00
return isFullDayEvent ( event ) ? moment ( event [ time ] , "YYYYMMDD" ) : moment ( new Date ( event [ time ] ) ) ;
2020-06-20 08:45:03 +02:00
} ;
2020-11-28 19:13:56 -06:00
Log . debug ( "there are " + Object . entries ( data ) . length + " calendar entries" ) ;
2020-06-20 09:01:35 +02:00
Object . entries ( data ) . forEach ( ( [ key , event ] ) => {
const now = new Date ( ) ;
const today = moment ( ) . startOf ( "day" ) . toDate ( ) ;
const future = moment ( ) . startOf ( "day" ) . add ( maximumNumberOfDays , "days" ) . subtract ( 1 , "seconds" ) . toDate ( ) ; // Subtract 1 second so that events that start on the middle of the night will not repeat.
let past = today ;
2020-11-28 19:13:56 -06:00
Log . debug ( "have entries " ) ;
2020-06-20 09:01:35 +02:00
if ( includePastEvents ) {
past = moment ( ) . startOf ( "day" ) . subtract ( maximumNumberOfDays , "days" ) . toDate ( ) ;
}
2018-03-18 23:33:48 -04:00
2020-06-20 09:01:35 +02:00
// FIXME: Ugly fix to solve the facebook birthday issue.
// Otherwise, the recurring events only show the birthday for next year.
let isFacebookBirthday = false ;
if ( typeof event . uid !== "undefined" ) {
if ( event . uid . indexOf ( "@facebook.com" ) !== - 1 ) {
isFacebookBirthday = true ;
2020-06-20 08:45:03 +02:00
}
2020-06-20 09:01:35 +02:00
}
2020-06-18 21:54:51 +02:00
2020-06-20 09:01:35 +02:00
if ( event . type === "VEVENT" ) {
let startDate = eventDate ( event , "start" ) ;
let endDate ;
2020-11-28 19:23:15 -06:00
2020-11-28 19:13:56 -06:00
Log . debug ( "\nevent=" + JSON . stringify ( event ) ) ;
2020-06-20 09:01:35 +02:00
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 ) {
2020-11-16 10:17:48 -05:00
// make copy of start date, separate storage area
endDate = moment ( startDate . format ( "x" ) , "x" ) ;
2020-06-20 08:45:03 +02:00
} else {
2020-06-20 09:01:35 +02:00
endDate = moment ( startDate ) . add ( 1 , "days" ) ;
2020-06-20 08:45:03 +02:00
}
2020-06-20 09:01:35 +02:00
}
2017-07-27 17:59:23 +02:00
2020-11-16 10:17:48 -05:00
Log . debug ( " start=" + startDate . toDate ( ) + " end=" + endDate . toDate ( ) ) ;
2020-06-20 09:01:35 +02:00
// calculate the duration of the event for use with recurring events.
let duration = parseInt ( endDate . format ( "x" ) ) - parseInt ( startDate . format ( "x" ) ) ;
2016-11-05 21:21:23 -03:00
2020-06-20 09:01:35 +02:00
if ( event . start . length === 8 ) {
startDate = startDate . startOf ( "day" ) ;
}
2018-10-29 20:26:54 +01:00
2020-06-20 09:01:35 +02:00
const title = getTitleFromEvent ( event ) ;
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
let excluded = false ,
dateFilter = null ;
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
for ( let f in excludedEvents ) {
let filter = excludedEvents [ f ] ,
testTitle = title . toLowerCase ( ) ,
until = null ,
useRegex = false ,
regexFlags = "g" ;
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
if ( filter instanceof Object ) {
if ( typeof filter . until !== "undefined" ) {
until = filter . until ;
}
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
if ( typeof filter . regex !== "undefined" ) {
useRegex = filter . regex ;
}
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
// 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" ;
2020-06-20 08:45:03 +02:00
} else {
2020-06-20 09:01:35 +02:00
filter = filter . filterBy . toLowerCase ( ) ;
2020-06-20 08:45:03 +02:00
}
2020-06-20 09:01:35 +02:00
} else {
filter = filter . toLowerCase ( ) ;
}
2018-10-29 20:26:54 +01:00
2020-06-20 09:01:35 +02:00
if ( testTitleByFilter ( testTitle , filter , useRegex , regexFlags ) ) {
if ( until ) {
dateFilter = until ;
} else {
excluded = true ;
2019-06-13 12:46:10 -05:00
}
2020-06-20 09:01:35 +02:00
break ;
2020-06-20 08:45:03 +02:00
}
2020-06-20 09:01:35 +02:00
}
2019-06-13 12:46:10 -05:00
2020-06-20 09:01:35 +02:00
if ( excluded ) {
return ;
}
2019-06-13 12:46:10 -05:00
2020-06-20 09:01:35 +02:00
const location = event . location || false ;
const geo = event . geo || false ;
const description = event . description || false ;
2019-06-13 12:46:10 -05:00
2020-06-20 09:01:35 +02:00
if ( typeof event . rrule !== "undefined" && event . rrule !== null && ! isFacebookBirthday ) {
const rule = event . rrule ;
let addedEvents = 0 ;
2020-06-18 21:54:51 +02:00
2020-06-20 09:01:35 +02:00
const pastMoment = moment ( past ) ;
const futureMoment = moment ( future ) ;
2019-06-13 12:46:10 -05:00
2020-06-20 09:01:35 +02:00
// 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 ) ;
}
2019-06-13 12:46:10 -05:00
2020-06-20 09:01:35 +02:00
// 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
2020-09-01 21:00:14 +01:00
let pastLocal = 0 ;
2020-09-01 15:13:42 -05:00
let futureLocal = 0 ;
if ( isFullDayEvent ( event ) ) {
2020-09-01 14:04:35 +01:00
// if full day event, only use the date part of the ranges
pastLocal = pastMoment . toDate ( ) ;
2020-09-01 15:13:42 -05:00
futureLocal = futureMoment . toDate ( ) ;
} else {
2020-11-28 19:13:56 -06:00
// if we want past events
if ( includePastEvents ) {
// use the calculated past time for the between from
pastLocal = pastMoment . toDate ( ) ;
} else {
// otherwise use NOW.. cause we shouldnt use any before now
pastLocal = moment ( ) . toDate ( ) ; //now
}
futureLocal = futureMoment . toDate ( ) ; // future
2020-09-01 14:04:35 +01:00
}
2020-11-16 10:17:48 -05:00
Log . debug ( " between=" + pastLocal + " to " + futureLocal ) ;
2020-10-05 11:06:21 -05:00
const dates = rule . between ( pastLocal , futureLocal , true , limitFunction ) ;
2020-11-16 10:17:48 -05:00
Log . debug ( "title=" + event . summary + " dates=" + JSON . stringify ( dates ) ) ;
2020-06-20 09:01:35 +02:00
// 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.
if ( event . recurrences !== undefined ) {
for ( let r in event . recurrences ) {
// Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events.
if ( moment ( new Date ( r ) ) . isBetween ( pastMoment , futureMoment ) !== true ) {
dates . push ( new Date ( r ) ) ;
2020-06-17 21:17:26 +02:00
}
2020-06-20 08:45:03 +02:00
}
2020-06-20 09:01:35 +02:00
}
// Loop through the set of date entries to see which recurrences should be added to our event list.
for ( let d in dates ) {
2020-11-16 10:17:48 -05:00
let date = dates [ d ] ;
2020-06-20 09:01:35 +02:00
// 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 )
const dateKey = date . toISOString ( ) . substring ( 0 , 10 ) ;
let curEvent = event ;
let showRecurrence = true ;
2020-11-16 10:17:48 -05:00
// 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 ) ;
2020-10-04 12:10:19 -05:00
}
}
2020-11-16 10:17:48 -05:00
startDate = moment ( date ) ;
let adjustDays = getCorrection ( event , date ) ;
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
if ( curEvent . recurrences !== undefined && curEvent . recurrences [ dateKey ] !== undefined ) {
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
curEvent = curEvent . recurrences [ dateKey ] ;
startDate = moment ( curEvent . start ) ;
duration = parseInt ( moment ( curEvent . end ) . format ( "x" ) ) - parseInt ( startDate . format ( "x" ) ) ;
}
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if ( curEvent . exdate !== undefined && curEvent . exdate [ dateKey ] !== undefined ) {
// This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false ;
}
2020-11-16 10:17:48 -05:00
Log . debug ( "duration=" + duration ) ;
2020-10-05 11:06:21 -05:00
2020-06-20 09:01:35 +02:00
endDate = moment ( parseInt ( startDate . format ( "x" ) ) + duration , "x" ) ;
if ( startDate . format ( "x" ) === endDate . format ( "x" ) ) {
endDate = endDate . endOf ( "day" ) ;
}
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
const recurrenceTitle = getTitleFromEvent ( curEvent ) ;
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
// it to the event list.
if ( endDate . isBefore ( past ) || startDate . isAfter ( future ) ) {
showRecurrence = false ;
}
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
if ( timeFilterApplies ( now , endDate , dateFilter ) ) {
showRecurrence = false ;
}
2018-03-18 23:33:48 -04:00
2020-06-20 11:01:37 -07:00
if ( showRecurrence === true ) {
2020-11-28 19:13:56 -06:00
Log . debug ( "saving event =" + description ) ;
2020-06-20 09:01:35 +02:00
addedEvents ++ ;
newEvents . push ( {
title : recurrenceTitle ,
2020-11-16 10:17:48 -05:00
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" ) ,
2020-06-20 09:01:35 +02:00
fullDayEvent : isFullDayEvent ( event ) ,
2020-07-08 21:53:34 +02:00
recurringEvent : true ,
2020-06-20 09:01:35 +02:00
class : event . class ,
firstYear : event . start . getFullYear ( ) ,
location : location ,
geo : geo ,
description : description
} ) ;
}
}
// end recurring event parsing
} else {
// Single event.
const fullDayEvent = isFacebookBirthday ? true : isFullDayEvent ( event ) ;
2020-11-16 10:17:48 -05:00
// Log.debug("full day event")
2020-11-16 09:50:09 -06:00
2020-06-20 09:01:35 +02:00
if ( includePastEvents ) {
// Past event is too far in the past, so skip.
if ( endDate < past ) {
return ;
2020-06-20 08:45:03 +02:00
}
} else {
2020-06-20 09:01:35 +02:00
// It's not a fullday event, and it is in the past, so skip.
if ( ! fullDayEvent && endDate < new Date ( ) ) {
return ;
2020-06-20 08:45:03 +02:00
}
2020-06-17 21:17:26 +02:00
2020-06-20 09:01:35 +02:00
// It's a fullday event, and it is before today, So skip.
if ( fullDayEvent && endDate <= today ) {
return ;
2016-04-20 15:19:36 +02:00
}
2020-06-20 09:01:35 +02:00
}
2020-06-20 08:45:03 +02:00
2020-06-20 09:01:35 +02:00
// It exceeds the maximumNumberOfDays limit, so skip.
if ( startDate > future ) {
return ;
}
2020-06-20 08:45:03 +02:00
2020-06-20 09:01:35 +02:00
if ( timeFilterApplies ( now , endDate , dateFilter ) ) {
return ;
}
2020-06-20 08:45:03 +02:00
2020-06-20 09:01:35 +02:00
// Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if ( fullDayEvent && startDate <= today ) {
startDate = moment ( today ) ;
2016-04-15 12:18:59 +02:00
}
2020-10-04 12:10:19 -05:00
// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
if ( fullDayEvent && startDate . format ( "x" ) === endDate . format ( "x" ) ) {
endDate = endDate . endOf ( "day" ) ;
}
2020-11-16 10:17:48 -05:00
// get correction for date saving and dst change between now and then
let adjustDays = getCorrection ( event , startDate . toDate ( ) ) ;
2020-06-20 09:01:35 +02:00
// Every thing is good. Add it to the list.
newEvents . push ( {
title : title ,
2020-11-16 10:17:48 -05:00
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" ) ,
2020-06-20 09:01:35 +02:00
fullDayEvent : fullDayEvent ,
class : event . class ,
location : location ,
geo : geo ,
description : description
} ) ;
2016-04-15 12:18:59 +02:00
}
}
2020-06-20 09:01:35 +02:00
} ) ;
2016-04-15 12:18:59 +02:00
2020-06-20 08:45:03 +02:00
newEvents . sort ( function ( a , b ) {
return a . startDate - b . startDate ;
} ) ;
2016-04-15 12:18:59 +02:00
2020-11-25 23:29:10 +01:00
// include up to maximumEntries current or upcoming events
// If past events should be included, include all past events
2020-11-25 21:59:58 +01:00
const now = moment ( ) ;
2020-11-25 23:29:10 +01:00
var entries = 0 ;
events = [ ] ;
for ( let ne of newEvents ) {
if ( moment ( ne . endDate , "x" ) . isBefore ( now ) ) {
if ( includePastEvents ) events . push ( ne ) ;
2020-11-25 21:53:34 +01:00
continue ;
2020-11-24 23:06:41 +01:00
}
2020-11-25 21:53:34 +01:00
entries ++ ;
// If max events has been saved, skip the rest
if ( entries > maximumEntries ) break ;
2020-11-25 23:29:10 +01:00
events . push ( ne ) ;
2020-11-24 23:06:41 +01:00
}
2016-04-15 12:18:59 +02:00
2020-06-20 08:45:03 +02:00
self . broadcastEvents ( ) ;
scheduleTimer ( ) ;
2020-06-20 08:32:54 +02:00
} ) ;
2016-04-15 12:18:59 +02:00
} ;
2020-11-16 10:17:48 -05:00
/ *
*
* 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
2020-11-16 10:32:29 -05:00
let tz = getIanaTZFromMS ( event . start . tz ) ;
2020-11-16 10:17:48 -05:00
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 ] ) ;
2020-11-16 10:32:29 -05:00
start _offset *= event . start . tz [ 1 ] === "-" ? - 1 : 1 ;
2020-11-16 10:17:48 -05:00
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 ;
} ;
2020-10-04 12:10:19 -05:00
/ * *
*
* lookup iana tz from windows
* /
let zoneTable = null ;
2020-11-16 10:32:29 -05:00
const getIanaTZFromMS = function ( msTZName ) {
2020-10-04 12:10:19 -05:00
if ( ! zoneTable ) {
const p = require ( "path" ) ;
zoneTable = require ( p . join ( _ _dirname , "windowsZones.json" ) ) ;
}
// Get hash entry
const he = zoneTable [ msTZName ] ;
// If found return iana name, else null
return he ? he . iana [ 0 ] : null ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Schedule the timer for the next update .
* /
2020-06-17 21:37:49 +02:00
const scheduleTimer = function ( ) {
2016-04-15 12:18:59 +02:00
clearTimeout ( reloadTimer ) ;
2020-05-11 22:22:32 +02:00
reloadTimer = setTimeout ( function ( ) {
2016-04-15 12:18:59 +02:00
fetchCalendar ( ) ;
} , reloadInterval ) ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-19 10:34:14 +02:00
* Checks if an event is a fullday event .
*
2020-08-03 11:19:54 +02:00
* @ param { object } event The event object to check .
* @ returns { boolean } True if the event is a fullday event , false otherwise
2016-04-19 10:34:14 +02:00
* /
2020-06-17 21:37:49 +02:00
const isFullDayEvent = function ( event ) {
2020-11-16 10:17:48 -05:00
if ( event . start . length === 8 || event . start . dateOnly || event . datetype === "date" ) {
2016-04-19 10:34:14 +02:00
return true ;
}
2020-06-17 21:37:49 +02:00
const start = event . start || 0 ;
const startDate = new Date ( start ) ;
const end = event . end || 0 ;
2020-05-11 22:22:32 +02:00
if ( ( end - start ) % ( 24 * 60 * 60 * 1000 ) === 0 && startDate . getHours ( ) === 0 && startDate . getMinutes ( ) === 0 ) {
2016-04-19 10:34:14 +02:00
// Is 24 hours, and starts on the middle of the night.
2016-11-05 21:21:23 -03:00
return true ;
2016-04-19 10:34:14 +02:00
}
return false ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2018-03-18 23:33:48 -04:00
* Determines if the user defined time filter should apply
*
2020-08-03 11:19:54 +02:00
* @ param { Date } now Date object using previously created object for consistency
* @ param { Moment } endDate Moment object representing the event end date
* @ param { string } filter The time to subtract from the end date to determine if an event should be shown
* @ returns { boolean } True if the event should be filtered out , false otherwise
2018-03-18 23:33:48 -04:00
* /
2020-06-17 21:37:49 +02:00
const timeFilterApplies = function ( now , endDate , filter ) {
2018-03-18 23:33:48 -04:00
if ( filter ) {
2020-06-17 21:37:49 +02:00
const until = filter . split ( " " ) ,
2018-03-18 23:33:48 -04:00
value = parseInt ( until [ 0 ] ) ,
2020-06-20 08:45:22 +02:00
increment = until [ 1 ] . slice ( - 1 ) === "s" ? until [ 1 ] : until [ 1 ] + "s" , // Massage the data for moment js
2018-03-18 23:33:48 -04:00
filterUntil = moment ( endDate . format ( ) ) . subtract ( value , increment ) ;
return now < filterUntil . format ( "x" ) ;
}
return false ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2020-05-11 22:22:32 +02:00
* Gets the title from the event .
*
2020-08-03 11:19:54 +02:00
* @ param { object } event The event object to check .
* @ returns { string } The title of the event , or "Event" if no title is found .
2020-05-11 22:22:32 +02:00
* /
2020-06-17 21:37:49 +02:00
const getTitleFromEvent = function ( event ) {
let title = "Event" ;
2019-06-13 12:46:10 -05:00
if ( event . summary ) {
2020-05-11 22:22:32 +02:00
title = typeof event . summary . val !== "undefined" ? event . summary . val : event . summary ;
2019-06-13 12:46:10 -05:00
} else if ( event . description ) {
title = event . description ;
}
return title ;
} ;
2020-06-17 21:37:49 +02:00
const testTitleByFilter = function ( title , filter , useRegex , regexFlags ) {
2018-06-03 17:34:16 -04:00
if ( useRegex ) {
// Assume if leading slash, there is also trailing slash
if ( filter [ 0 ] === "/" ) {
// Strip leading and trailing slashes
filter = filter . substr ( 1 ) . slice ( 0 , - 1 ) ;
}
filter = new RegExp ( filter , regexFlags ) ;
return filter . test ( title ) ;
} else {
return title . includes ( filter ) ;
}
2019-06-05 09:32:10 +02:00
} ;
2018-06-03 17:34:16 -04:00
2016-04-15 12:18:59 +02:00
/* public methods */
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Initiate fetchCalendar ( ) ;
* /
2020-05-11 22:22:32 +02:00
this . startFetch = function ( ) {
2016-04-15 12:18:59 +02:00
fetchCalendar ( ) ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-06-04 20:32:55 -06:00
* Broadcast the existing events .
2016-04-15 12:18:59 +02:00
* /
2020-05-25 18:57:15 +02:00
this . broadcastEvents = function ( ) {
2020-06-01 16:40:20 +02:00
Log . info ( "Calendar-Fetcher: Broadcasting " + events . length + " events." ) ;
2016-04-15 12:18:59 +02:00
eventsReceivedCallback ( self ) ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Sets the on success callback
*
2020-08-03 11:19:54 +02:00
* @ param { Function } callback The on success callback .
2016-04-15 12:18:59 +02:00
* /
2020-05-11 22:22:32 +02:00
this . onReceive = function ( callback ) {
2016-04-15 12:18:59 +02:00
eventsReceivedCallback = callback ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Sets the on error callback
*
2020-08-03 11:19:54 +02:00
* @ param { Function } callback The on error callback .
2016-04-15 12:18:59 +02:00
* /
2020-05-11 22:22:32 +02:00
this . onError = function ( callback ) {
2016-04-15 12:18:59 +02:00
fetchFailedCallback = callback ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Returns the url of this fetcher .
*
2020-08-03 11:19:54 +02:00
* @ returns { string } The url of this fetcher .
2016-04-15 12:18:59 +02:00
* /
2020-05-11 22:22:32 +02:00
this . url = function ( ) {
2016-04-15 12:18:59 +02:00
return url ;
} ;
2020-08-03 11:19:54 +02:00
/ * *
2016-04-15 12:18:59 +02:00
* Returns current available events for this fetcher .
*
2020-08-03 11:19:54 +02:00
* @ returns { object [ ] } The current available events for this fetcher .
2016-04-15 12:18:59 +02:00
* /
2020-05-11 22:22:32 +02:00
this . events = function ( ) {
2016-04-15 12:18:59 +02:00
return events ;
} ;
} ;
2016-09-08 17:36:30 +02:00
module . exports = CalendarFetcher ;