Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

calendarutils.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /* Magic Mirror
  2. * Calendar Util Methods
  3. *
  4. * By Michael Teeuw https://michaelteeuw.nl
  5. * MIT Licensed.
  6. */
  7. /**
  8. * @external Moment
  9. */
  10. const moment = require("moment");
  11. const path = require("path");
  12. const zoneTable = require(path.join(__dirname, "windowsZones.json"));
  13. const Log = require("../../../js/logger.js");
  14. const CalendarUtils = {
  15. /**
  16. * Calculate the time correction, either dst/std or full day in cases where
  17. * utc time is day before plus offset
  18. *
  19. * @param {object} event the event which needs adjustement
  20. * @param {Date} date the date on which this event happens
  21. * @returns {number} the necessary adjustment in hours
  22. */
  23. calculateTimezoneAdjustment: function (event, date) {
  24. let adjustHours = 0;
  25. // if a timezone was specified
  26. if (!event.start.tz) {
  27. Log.debug(" if no tz, guess based on now");
  28. event.start.tz = moment.tz.guess();
  29. }
  30. Log.debug("initial tz=" + event.start.tz);
  31. // if there is a start date specified
  32. if (event.start.tz) {
  33. // if this is a windows timezone
  34. if (event.start.tz.includes(" ")) {
  35. // use the lookup table to get theIANA name as moment and date don't know MS timezones
  36. let tz = CalendarUtils.getIanaTZFromMS(event.start.tz);
  37. Log.debug("corrected TZ=" + tz);
  38. // watch out for unregistered windows timezone names
  39. // if we had a successful lookup
  40. if (tz) {
  41. // change the timezone to the IANA name
  42. event.start.tz = tz;
  43. // Log.debug("corrected timezone="+event.start.tz)
  44. }
  45. }
  46. Log.debug("corrected tz=" + event.start.tz);
  47. let current_offset = 0; // offset from TZ string or calculated
  48. let mm = 0; // date with tz or offset
  49. let start_offset = 0; // utc offset of created with tz
  50. // if there is still an offset, lookup failed, use it
  51. if (event.start.tz.startsWith("(")) {
  52. const regex = /[+|-]\d*:\d*/;
  53. const start_offsetString = event.start.tz.match(regex).toString().split(":");
  54. let start_offset = parseInt(start_offsetString[0]);
  55. start_offset *= event.start.tz[1] === "-" ? -1 : 1;
  56. adjustHours = start_offset;
  57. Log.debug("defined offset=" + start_offset + " hours");
  58. current_offset = start_offset;
  59. event.start.tz = "";
  60. Log.debug("ical offset=" + current_offset + " date=" + date);
  61. mm = moment(date);
  62. let x = parseInt(moment(new Date()).utcOffset());
  63. Log.debug("net mins=" + (current_offset * 60 - x));
  64. mm = mm.add(x - current_offset * 60, "minutes");
  65. adjustHours = (current_offset * 60 - x) / 60;
  66. event.start = mm.toDate();
  67. Log.debug("adjusted date=" + event.start);
  68. } else {
  69. // get the start time in that timezone
  70. let es = moment(event.start);
  71. // check for start date prior to start of daylight changing date
  72. if (es.format("YYYY") < 2007) {
  73. es.set("year", 2013); // if so, use a closer date
  74. }
  75. Log.debug("start date/time=" + es.toDate());
  76. start_offset = moment.tz(es, event.start.tz).utcOffset();
  77. Log.debug("start offset=" + start_offset);
  78. Log.debug("start date/time w tz =" + moment.tz(moment(event.start), event.start.tz).toDate());
  79. // get the specified date in that timezone
  80. mm = moment.tz(moment(date), event.start.tz);
  81. Log.debug("event date=" + mm.toDate());
  82. current_offset = mm.utcOffset();
  83. }
  84. Log.debug("event offset=" + current_offset + " hour=" + mm.format("H") + " event date=" + mm.toDate());
  85. // if the offset is greater than 0, east of london
  86. if (current_offset !== start_offset) {
  87. // big offset
  88. Log.debug("offset");
  89. let h = parseInt(mm.format("H"));
  90. // check if the event time is less than the offset
  91. if (h > 0 && h < Math.abs(current_offset) / 60) {
  92. // if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time)
  93. // we need to fix that
  94. adjustHours = 24;
  95. // Log.debug("adjusting date")
  96. }
  97. //-300 > -240
  98. //if (Math.abs(current_offset) > Math.abs(start_offset)){
  99. if (current_offset > start_offset) {
  100. adjustHours -= 1;
  101. Log.debug("adjust down 1 hour dst change");
  102. //} else if (Math.abs(current_offset) < Math.abs(start_offset)) {
  103. } else if (current_offset < start_offset) {
  104. adjustHours += 1;
  105. Log.debug("adjust up 1 hour dst change");
  106. }
  107. }
  108. }
  109. Log.debug("adjustHours=" + adjustHours);
  110. return adjustHours;
  111. },
  112. /**
  113. * Filter the events from ical according to the given config
  114. *
  115. * @param {object} data the calendar data from ical
  116. * @param {object} config The configuration object
  117. * @returns {string[]} the filtered events
  118. */
  119. filterEvents: function (data, config) {
  120. const newEvents = [];
  121. // limitFunction doesn't do much limiting, see comment re: the dates
  122. // array in rrule section below as to why we need to do the filtering
  123. // ourselves
  124. const limitFunction = function (date, i) {
  125. return true;
  126. };
  127. const eventDate = function (event, time) {
  128. return CalendarUtils.isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
  129. };
  130. Log.debug("There are " + Object.entries(data).length + " calendar entries.");
  131. Object.entries(data).forEach(([key, event]) => {
  132. Log.debug("Processing entry...");
  133. const now = new Date();
  134. const today = moment().startOf("day").toDate();
  135. const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
  136. let past = today;
  137. if (config.includePastEvents) {
  138. past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
  139. }
  140. // FIXME: Ugly fix to solve the facebook birthday issue.
  141. // Otherwise, the recurring events only show the birthday for next year.
  142. let isFacebookBirthday = false;
  143. if (typeof event.uid !== "undefined") {
  144. if (event.uid.indexOf("@facebook.com") !== -1) {
  145. isFacebookBirthday = true;
  146. }
  147. }
  148. if (event.type === "VEVENT") {
  149. Log.debug("\nEvent: " + JSON.stringify(event));
  150. let startDate = eventDate(event, "start");
  151. let endDate;
  152. if (typeof event.end !== "undefined") {
  153. endDate = eventDate(event, "end");
  154. } else if (typeof event.duration !== "undefined") {
  155. endDate = startDate.clone().add(moment.duration(event.duration));
  156. } else {
  157. if (!isFacebookBirthday) {
  158. // make copy of start date, separate storage area
  159. endDate = moment(startDate.format("x"), "x");
  160. } else {
  161. endDate = moment(startDate).add(1, "days");
  162. }
  163. }
  164. Log.debug("startDate (local): " + startDate.toDate());
  165. Log.debug("endDate (local): " + endDate.toDate());
  166. // Calculate the duration of the event for use with recurring events.
  167. let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
  168. Log.debug("duration: " + duration);
  169. // FIXME: Since the parsed json object from node-ical comes with time information
  170. // this check could be removed (?)
  171. if (event.start.length === 8) {
  172. startDate = startDate.startOf("day");
  173. }
  174. const title = CalendarUtils.getTitleFromEvent(event);
  175. Log.debug("title: " + title);
  176. let excluded = false,
  177. dateFilter = null;
  178. for (let f in config.excludedEvents) {
  179. let filter = config.excludedEvents[f],
  180. testTitle = title.toLowerCase(),
  181. until = null,
  182. useRegex = false,
  183. regexFlags = "g";
  184. if (filter instanceof Object) {
  185. if (typeof filter.until !== "undefined") {
  186. until = filter.until;
  187. }
  188. if (typeof filter.regex !== "undefined") {
  189. useRegex = filter.regex;
  190. }
  191. // If additional advanced filtering is added in, this section
  192. // must remain last as we overwrite the filter object with the
  193. // filterBy string
  194. if (filter.caseSensitive) {
  195. filter = filter.filterBy;
  196. testTitle = title;
  197. } else if (useRegex) {
  198. filter = filter.filterBy;
  199. testTitle = title;
  200. regexFlags += "i";
  201. } else {
  202. filter = filter.filterBy.toLowerCase();
  203. }
  204. } else {
  205. filter = filter.toLowerCase();
  206. }
  207. if (CalendarUtils.titleFilterApplies(testTitle, filter, useRegex, regexFlags)) {
  208. if (until) {
  209. dateFilter = until;
  210. } else {
  211. excluded = true;
  212. }
  213. break;
  214. }
  215. }
  216. if (excluded) {
  217. return;
  218. }
  219. const location = event.location || false;
  220. const geo = event.geo || false;
  221. const description = event.description || false;
  222. if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
  223. const rule = event.rrule;
  224. let addedEvents = 0;
  225. const pastMoment = moment(past);
  226. const futureMoment = moment(future);
  227. // can cause problems with e.g. birthdays before 1900
  228. if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) {
  229. rule.origOptions.dtstart.setYear(1900);
  230. rule.options.dtstart.setYear(1900);
  231. }
  232. // For recurring events, get the set of start dates that fall within the range
  233. // of dates we're looking for.
  234. // kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
  235. let pastLocal = 0;
  236. let futureLocal = 0;
  237. if (CalendarUtils.isFullDayEvent(event)) {
  238. Log.debug("fullday");
  239. // if full day event, only use the date part of the ranges
  240. pastLocal = pastMoment.toDate();
  241. futureLocal = futureMoment.toDate();
  242. Log.debug("pastLocal: " + pastLocal);
  243. Log.debug("futureLocal: " + futureLocal);
  244. } else {
  245. // if we want past events
  246. if (config.includePastEvents) {
  247. // use the calculated past time for the between from
  248. pastLocal = pastMoment.toDate();
  249. } else {
  250. // otherwise use NOW.. cause we shouldn't use any before now
  251. pastLocal = moment().toDate(); //now
  252. }
  253. futureLocal = futureMoment.toDate(); // future
  254. }
  255. Log.debug("Search for recurring events between: " + pastLocal + " and " + futureLocal);
  256. const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
  257. Log.debug("Title: " + event.summary + ", with dates: " + JSON.stringify(dates));
  258. // The "dates" array contains the set of dates within our desired date range range that are valid
  259. // for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
  260. // had its date changed from outside the range to inside the range. For the time being,
  261. // we'll handle this by adding *all* recurrence entries into the set of dates that we check,
  262. // because the logic below will filter out any recurrences that don't actually belong within
  263. // our display range.
  264. // Would be great if there was a better way to handle this.
  265. Log.debug("event.recurrences: " + event.recurrences);
  266. if (event.recurrences !== undefined) {
  267. for (let r in event.recurrences) {
  268. // Only add dates that weren't already in the range we added from the rrule so that
  269. // we don"t double-add those events.
  270. if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) {
  271. dates.push(new Date(r));
  272. }
  273. }
  274. }
  275. // Loop through the set of date entries to see which recurrences should be added to our event list.
  276. for (let d in dates) {
  277. let date = dates[d];
  278. // Remove the time information of each date by using its substring, using the following method:
  279. // .toISOString().substring(0,10).
  280. // since the date is given as ISOString with YYYY-MM-DDTHH:MM:SS.SSSZ
  281. // (see https://momentjs.com/docs/#/displaying/as-iso-string/).
  282. const dateKey = date.toISOString().substring(0, 10);
  283. let curEvent = event;
  284. let showRecurrence = true;
  285. // Get the offset of today where we are processing
  286. // This will be the correction, we need to apply.
  287. let nowOffset = new Date().getTimezoneOffset();
  288. // For full day events, the time might be off from RRULE/Luxon problem
  289. // Get time zone offset of the rule calculated event
  290. let dateoffset = date.getTimezoneOffset();
  291. // Reduce the time by the following offset.
  292. Log.debug(" recurring date is " + date + " offset is " + dateoffset);
  293. let dh = moment(date).format("HH");
  294. Log.debug(" recurring date is " + date + " offset is " + dateoffset / 60 + " Hour is " + dh);
  295. if (CalendarUtils.isFullDayEvent(event)) {
  296. Log.debug("Fullday");
  297. // If the offset is negative (east of GMT), where the problem is
  298. if (dateoffset < 0) {
  299. // Remove the offset, independently of the comparison between the date hour and the offset,
  300. // since in the case that *date houre < offset*, the *new Date* command will handle this by
  301. // representing the day before.
  302. // Reduce the time by the offset:
  303. // Apply the correction to the date/time to get it UTC relative
  304. date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
  305. // the duration was calculated way back at the top before we could correct the start time..
  306. // fix it for this event entry
  307. //duration = 24 * 60 * 60 * 1000;
  308. Log.debug("new recurring date1 is " + date);
  309. } else {
  310. // if the timezones are the same, correct date if needed
  311. if (event.start.tz === moment.tz.guess()) {
  312. // if the date hour is less than the offset
  313. if (24 - dh < Math.abs(dateoffset / 60)) {
  314. // apply the correction to the date/time back to right day
  315. date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
  316. // the duration was calculated way back at the top before we could correct the start time..
  317. // fix it for this event entry
  318. //duration = 24 * 60 * 60 * 1000;
  319. Log.debug("new recurring date2 is " + date);
  320. }
  321. }
  322. }
  323. } else {
  324. // not full day, but luxon can still screw up the date on the rule processing
  325. // we need to correct the date to get back to the right event for
  326. if (dateoffset < 0) {
  327. // if the date hour is less than the offset
  328. if (dh < Math.abs(dateoffset / 60)) {
  329. // Reduce the time by the offset:
  330. // Apply the correction to the date/time to get it UTC relative
  331. date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
  332. // the duration was calculated way back at the top before we could correct the start time..
  333. // fix it for this event entry
  334. //duration = 24 * 60 * 60 * 1000;
  335. Log.debug("new recurring date1 is " + date);
  336. }
  337. } else {
  338. // if the timezones are the same, correct date if needed
  339. if (event.start.tz === moment.tz.guess()) {
  340. // if the date hour is less than the offset
  341. if (24 - dh < Math.abs(dateoffset / 60)) {
  342. // apply the correction to the date/time back to right day
  343. date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
  344. // the duration was calculated way back at the top before we could correct the start time..
  345. // fix it for this event entry
  346. //duration = 24 * 60 * 60 * 1000;
  347. Log.debug("new recurring date2 is " + date);
  348. }
  349. }
  350. }
  351. }
  352. startDate = moment(date);
  353. Log.debug("Corrected startDate (local): " + startDate.toDate());
  354. let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date);
  355. // For each date that we're checking, it's possible that there is a recurrence override for that one day.
  356. if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
  357. // We found an override, so for this recurrence, use a potentially different title, start date, and duration.
  358. curEvent = curEvent.recurrences[dateKey];
  359. startDate = moment(curEvent.start);
  360. duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
  361. }
  362. // If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
  363. else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) {
  364. // This date is an exception date, which means we should skip it in the recurrence pattern.
  365. showRecurrence = false;
  366. }
  367. Log.debug("duration: " + duration);
  368. endDate = moment(parseInt(startDate.format("x")) + duration, "x");
  369. if (startDate.format("x") === endDate.format("x")) {
  370. endDate = endDate.endOf("day");
  371. }
  372. const recurrenceTitle = CalendarUtils.getTitleFromEvent(curEvent);
  373. // If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
  374. // it to the event list.
  375. if (endDate.isBefore(past) || startDate.isAfter(future)) {
  376. showRecurrence = false;
  377. }
  378. if (CalendarUtils.timeFilterApplies(now, endDate, dateFilter)) {
  379. showRecurrence = false;
  380. }
  381. if (showRecurrence === true) {
  382. Log.debug("saving event: " + description);
  383. addedEvents++;
  384. newEvents.push({
  385. title: recurrenceTitle,
  386. startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
  387. endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
  388. fullDayEvent: CalendarUtils.isFullDayEvent(event),
  389. recurringEvent: true,
  390. class: event.class,
  391. firstYear: event.start.getFullYear(),
  392. location: location,
  393. geo: geo,
  394. description: description
  395. });
  396. }
  397. }
  398. // End recurring event parsing.
  399. } else {
  400. // Single event.
  401. const fullDayEvent = isFacebookBirthday ? true : CalendarUtils.isFullDayEvent(event);
  402. // Log.debug("full day event")
  403. if (config.includePastEvents) {
  404. // Past event is too far in the past, so skip.
  405. if (endDate < past) {
  406. return;
  407. }
  408. } else {
  409. // It's not a fullday event, and it is in the past, so skip.
  410. if (!fullDayEvent && endDate < new Date()) {
  411. return;
  412. }
  413. // It's a fullday event, and it is before today, So skip.
  414. if (fullDayEvent && endDate <= today) {
  415. return;
  416. }
  417. }
  418. // It exceeds the maximumNumberOfDays limit, so skip.
  419. if (startDate > future) {
  420. return;
  421. }
  422. if (CalendarUtils.timeFilterApplies(now, endDate, dateFilter)) {
  423. return;
  424. }
  425. // Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
  426. if (fullDayEvent && startDate <= today) {
  427. startDate = moment(today);
  428. }
  429. // if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
  430. if (fullDayEvent && startDate.format("x") === endDate.format("x")) {
  431. endDate = endDate.endOf("day");
  432. }
  433. // get correction for date saving and dst change between now and then
  434. let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, startDate.toDate());
  435. // Every thing is good. Add it to the list.
  436. newEvents.push({
  437. title: title,
  438. startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
  439. endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
  440. fullDayEvent: fullDayEvent,
  441. class: event.class,
  442. location: location,
  443. geo: geo,
  444. description: description
  445. });
  446. }
  447. }
  448. });
  449. newEvents.sort(function (a, b) {
  450. return a.startDate - b.startDate;
  451. });
  452. // include up to maximumEntries current or upcoming events
  453. // If past events should be included, include all past events
  454. const now = moment();
  455. let entries = 0;
  456. let events = [];
  457. for (let ne of newEvents) {
  458. if (moment(ne.endDate, "x").isBefore(now)) {
  459. if (config.includePastEvents) events.push(ne);
  460. continue;
  461. }
  462. entries++;
  463. // If max events has been saved, skip the rest
  464. if (entries > config.maximumEntries) break;
  465. events.push(ne);
  466. }
  467. return events;
  468. },
  469. /**
  470. * Lookup iana tz from windows
  471. *
  472. * @param {string} msTZName the timezone name to lookup
  473. * @returns {string|null} the iana name or null of none is found
  474. */
  475. getIanaTZFromMS: function (msTZName) {
  476. // Get hash entry
  477. const he = zoneTable[msTZName];
  478. // If found return iana name, else null
  479. return he ? he.iana[0] : null;
  480. },
  481. /**
  482. * Gets the title from the event.
  483. *
  484. * @param {object} event The event object to check.
  485. * @returns {string} The title of the event, or "Event" if no title is found.
  486. */
  487. getTitleFromEvent: function (event) {
  488. let title = "Event";
  489. if (event.summary) {
  490. title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
  491. } else if (event.description) {
  492. title = event.description;
  493. }
  494. return title;
  495. },
  496. /**
  497. * Checks if an event is a fullday event.
  498. *
  499. * @param {object} event The event object to check.
  500. * @returns {boolean} True if the event is a fullday event, false otherwise
  501. */
  502. isFullDayEvent: function (event) {
  503. if (event.start.length === 8 || event.start.dateOnly || event.datetype === "date") {
  504. return true;
  505. }
  506. const start = event.start || 0;
  507. const startDate = new Date(start);
  508. const end = event.end || 0;
  509. if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
  510. // Is 24 hours, and starts on the middle of the night.
  511. return true;
  512. }
  513. return false;
  514. },
  515. /**
  516. * Determines if the user defined time filter should apply
  517. *
  518. * @param {Date} now Date object using previously created object for consistency
  519. * @param {Moment} endDate Moment object representing the event end date
  520. * @param {string} filter The time to subtract from the end date to determine if an event should be shown
  521. * @returns {boolean} True if the event should be filtered out, false otherwise
  522. */
  523. timeFilterApplies: function (now, endDate, filter) {
  524. if (filter) {
  525. const until = filter.split(" "),
  526. value = parseInt(until[0]),
  527. increment = until[1].slice(-1) === "s" ? until[1] : until[1] + "s", // Massage the data for moment js
  528. filterUntil = moment(endDate.format()).subtract(value, increment);
  529. return now < filterUntil.format("x");
  530. }
  531. return false;
  532. },
  533. /**
  534. * Determines if the user defined title filter should apply
  535. *
  536. * @param {string} title the title of the event
  537. * @param {string} filter the string to look for, can be a regex also
  538. * @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string
  539. * @param {string} regexFlags flags that should be applied to the regex
  540. * @returns {boolean} True if the title should be filtered out, false otherwise
  541. */
  542. titleFilterApplies: function (title, filter, useRegex, regexFlags) {
  543. if (useRegex) {
  544. // Assume if leading slash, there is also trailing slash
  545. if (filter[0] === "/") {
  546. // Strip leading and trailing slashes
  547. filter = filter.substr(1).slice(0, -1);
  548. }
  549. filter = new RegExp(filter, regexFlags);
  550. return filter.test(title);
  551. } else {
  552. return title.includes(filter);
  553. }
  554. }
  555. };
  556. if (typeof module !== "undefined") {
  557. module.exports = CalendarUtils;
  558. }