// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors // // SPDX-License-Identifier: AGPL-3.0-or-later define([ '/common/common-util.js', ], function (Util) { var Rec = {}; var debug = function () {}; // Get week number with any "WKST" (firts day of the week) // Week 1 is the first week of the year containing at least 4 days in this year // It depends on which day is considered the first day of the week (default Monday) // In our case, wkst is a number matching the JS rule: 0 == Sunday var getWeekNo = Rec.getWeekNo = function (date, wkst) { if (typeof(wkst) !== "number") { wkst = 1; } // Default monday var newYear = new Date(date.getFullYear(),0,1); var day = newYear.getDay() - wkst; //the day of week the year begins on day = (day >= 0 ? day : day + 7); var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1; var weeknum; // Week 1 / week 53 if (day < 4) { weeknum = Math.floor((daynum+day-1)/7) + 1; if (weeknum > 52) { var nYear = new Date(date.getFullYear() + 1,0,1); var nday = nYear.getDay() - wkst; nday = nday >= 0 ? nday : nday + 7; weeknum = nday < 4 ? 1 : 53; } } else { weeknum = Math.floor((daynum+day-1)/7); } return weeknum; }; var getYearDay = function (date) { var start = new Date(date.getFullYear(), 0, 0); var diff = (date - start) + ((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000); var oneDay = 1000 * 60 * 60 * 24; return Math.floor(diff / oneDay); }; var setYearDay = function (date, day) { if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; } if (day < 0) { var max = getYearDay(new Date(date.getFullYear(), 11, 31)); day = max + day + 1; } date.setMonth(0); date.setDate(day); return true; }; var getEndData = function (s, e) { if (s > e) { return void console.error("Wrong data"); } var days; if (e.getFullYear() === s.getFullYear()) { days = getYearDay(e) - getYearDay(s); } else { // eYear < sYear var tmp = new Date(s.getFullYear(), 11, 31); var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st var de = getYearDay(e); days = d1 + de; while ((tmp.getFullYear()+1) < e.getFullYear()) { tmp.setFullYear(tmp.getFullYear()+1); days += getYearDay(tmp); } } return { h: e.getHours(), m: e.getMinutes(), days: days }; }; var setEndData = function (s, e, data) { e.setTime(+s); if (!data) { return; } e.setHours(data.h); e.setMinutes(data.m); e.setSeconds(0); e.setDate(s.getDate() + data.days); }; var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]; var getDayData = function (str) { var pos = Number(str.slice(0,-2)); var day = DAYORDER.indexOf(str.slice(-2)); return pos ? [pos, day] : day; }; var goToFirstWeekDay = function (date, wkst) { var d = date.getDay(); wkst = typeof(wkst) === "number" ? wkst : 1; if (d >= wkst) { date.setDate(date.getDate() - (d-wkst)); } else { date.setDate(date.getDate() - (7+d-wkst)); } }; var getDateStr = function (date) { return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate(); }; var FREQ = {}; FREQ['daily'] = function (s, i) { s.setDate(s.getDate()+i); }; FREQ['weekly'] = function (s,i) { s.setDate(s.getDate()+(i*7)); }; FREQ['monthly'] = function (s,i) { s.setMonth(s.getMonth()+i); }; FREQ['yearly'] = function (s,i) { s.setFullYear(s.getFullYear()+i); }; // EXPAND is used to create iterations added from a BYxxx rule // dateA is the start date and b is the number or id of the BYxxx rule item var EXPAND = {}; EXPAND['month'] = function (dateS, origin, b) { var oS = new Date(origin.start); var a = dateS.getMonth() + 1; var toAdd = (b-a+12)%12; var m = dateS.getMonth() + toAdd; dateS.setMonth(m); dateS.setDate(oS.getDate()); if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month return true; }; EXPAND['weekno'] = function (dateS, origin, week, rule) { var wkst = rule && rule.wkst; if (typeof(wkst) !== "number") { wkst = 1; } // Default monday var oS = new Date(origin.start); var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53 var doubleOne = lastW === 1; if (lastW === 1) { lastW = 52; } var a = getWeekNo(dateS, wkst); if (!week || week > lastW) { return false; } // Week 53 may not exist this year if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive var toAdd = week - a; var weekS = new Date(+dateS); // Go to the selected week weekS.setDate(weekS.getDate() + (toAdd * 7)); goToFirstWeekDay(weekS, wkst); // Then make sure we are in the correct start day var all = 'aaaaaaa'.split('').map(function (o, i) { var date = new Date(+weekS); date.setDate(date.getDate() + i); if (date.getFullYear() !== dateS.getFullYear()) { return; } return date.toLocaleDateString() !== oS.toLocaleDateString() && date; }).filter(Boolean); // If we're looking for week 1 and the last week is a week 1, add the days if (week === 1 && doubleOne) { goToFirstWeekDay(lastD, wkst); 'aaaaaaa'.split('').some(function (o, i) { var date = new Date(+lastD); date.setDate(date.getDate() + i); if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; } if (date.getFullYear() > dateS.getFullYear()) { return true; } all.push(date); }); } return all.length ? all : undefined; }; EXPAND['yearday'] = function (dateS, origin, b) { var y = dateS.getFullYear(); var state = setYearDay(dateS, b); if (!state) { return; } // Invalid day "b" if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year return true; }; EXPAND['monthday'] = function (dateS, origin, b, rule) { if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; } var setMonthDay = function (date, day) { var m = date.getMonth(); if (day < 0) { var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day day = tmp.getDate() + day + 1; } date.setDate(day); return date.getMonth() === m; // Don't push if day 31 moved us to the next month }; // Monthly events if (rule.freq === 'monthly') { return setMonthDay(dateS, b); } var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) { var date = new Date(dateS.getFullYear(), i, 1); var ok = setMonthDay(date, b); return ok ? date : undefined; }).filter(Boolean); return all.length ? all : undefined; }; EXPAND['day'] = function (dateS, origin, b, rule) { // Here "b" can be a single day ("TU") or a position and a day ("1MO") var day = getDayData(b); var pos; if (Array.isArray(day)) { pos = day[0]; day = day[1]; } var all = []; if (![0,1,2,3,4,5,6].includes(day)) { return false; } var filterPos = function (m) { if (!pos) { return; } var _all = []; 'aaaaaaaaaaaa'.split('').some(function (a, i) { if (typeof(m) !== "undefined" && i !== m) { return; } var _pos; var tmp = all.filter(function (d) { return d.getMonth() === i; }); if (pos < 0) { _pos = tmp.length + pos; } else { _pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1 } _all.push(tmp[_pos]); return typeof(m) !== "undefined" && i === m; }); all = _all.filter(Boolean); // The "5th" {day} won't always exist }; var tmp; if (rule.freq === 'yearly') { tmp = new Date(+dateS); var y = dateS.getFullYear(); while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); } while (tmp.getFullYear() === y) { all.push(new Date(+tmp)); tmp.setDate(tmp.getDate()+7); } filterPos(); return all; } if (rule.freq === 'monthly') { tmp = new Date(+dateS); var m = dateS.getMonth(); while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); } while (tmp.getMonth() === m) { all.push(new Date(+tmp)); tmp.setDate(tmp.getDate()+7); } filterPos(m); return all; } if (rule.freq === 'weekly') { while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); } } return true; }; var LIMIT = {}; LIMIT['month'] = function (events, rule) { return events.filter(function (s) { return rule.includes(s.getMonth()+1); }); }; LIMIT['weekno'] = function (events, weeks, rules) { return events.filter(function (s) { var wkst = rules && rules.wkst; if (typeof(wkst) !== "number") { wkst = 1; } // Default monday var lastD = new Date(s.getFullYear(), 11, 31); // December 31st var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53 if (lastW === 1) { lastW = 52; } var w = getWeekNo(s, wkst); return weeks.some(function (week) { if (week > 0) { return week === w; } return w === (lastW + week + 1); }); }); }; LIMIT['yearday'] = function (events, days) { return events.filter(function (s) { var d = getYearDay(s); var max = getYearDay(new Date(s.getFullYear(), 11, 31)); return days.some(function (day) { if (day > 0) { return day === d; } return d === (max + day + 1); }); }); }; LIMIT['monthday'] = function (events, rule) { return events.filter(function (s) { var r = Util.clone(rule); // Transform the negative monthdays into positive for this specific month r = r.map(function (b) { if (b < 0) { var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day b = tmp.getDate() + b + 1; } return b; }); return r.includes(s.getDate()); }); }; LIMIT['day'] = function (events, days, rules) { return events.filter(function (s) { var dayStr = s.toLocaleDateString(); // Check how to handle position in BYDAY rules (last day of the month or the year?) var type = 'yearly'; if (rules.freq === 'monthly' || (rules.freq === 'yearly' && rules.by && rules.by.month)) { type = 'monthly'; } // Check if this event matches one of the allowed days return days.some(function (r) { // rule elements are strings with pos and day var day = getDayData(r); var pos; if (Array.isArray(day)) { pos = day[0]; day = day[1]; } if (!pos) { return s.getDay() === day; } // If we have a position, we can use EXPAND.day to get the nth {day} of the // year/month and compare if it matches with var d = new Date(s.getFullYear(), s.getMonth(), 1); if (type === 'yearly') { d.setMonth(0); } var res = EXPAND["day"](d, {}, r, {freq: type}); return res.some(function (date) { return date.toLocaleDateString() === dayStr; }); }); }); }; LIMIT['setpos'] = function (events, rule) { var init = events.slice(); var rules = Util.deduplicateString(rule.slice().map(function (n) { if (n > 0) { return (n-1); } if (n === 0) { return; } return init.length + n; })); return events.filter(function (ev) { var idx = init.indexOf(ev); return rules.includes(idx); }); }; var BYORDER = ['month','weekno','yearday','monthday','day']; var BYDAYORDER = ['month','monthday','day']; Rec.getMonthId = function (d) { return d.getFullYear() + '-' + d.getMonth(); }; var cache = window.CP_calendar_cache = {}; var recurringAcross = {}; Rec.resetCache = function () { cache = window.CP_calendar_cache = {}; recurringAcross = {}; }; var iterate = function (rule, _origin, s) { // "origin" is the original event to detect the start of BYxxx var origin = Util.clone(_origin); var oS = new Date(origin.start); var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule // "uid" is used for the cache var uid = s.toLocaleDateString(); cache[id] = cache[id] || {}; var inter = rule.interval || 1; var freq = rule.freq; var all = []; var limit = function (byrule, n) { all = LIMIT[byrule](all, n, rule); }; var expand = function (byrule) { return function (n) { // Set the start date at the beginning of the current FREQ var _s = new Date(+s); if (rule.freq === 'yearly') { // January 1st _s.setMonth(0); _s.setDate(1); } else if (rule.freq === 'monthly') { _s.setDate(1); } else if (rule.freq === 'weekly') { goToFirstWeekDay(_s, rule.wkst); } else if (rule.freq === 'daily') { // We don't have < byday rules so we can't expand daily rules } var add = EXPAND[byrule](_s, origin, n, rule); if (!add) { return; } if (Array.isArray(add)) { add = add.filter(function (dateS) { return dateS.toLocaleDateString() !== oS.toLocaleDateString(); }); Array.prototype.push.apply(all, add); } else { if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; } all.push(_s); } }; }; // Manage interval for the next iteration var it = Util.once(function () { FREQ[freq](s, inter); }); var addDefault = function () { if (freq === "monthly") { s.setDate(15); } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) { s.setDate(28); } it(); var _s = new Date(+s); if (freq === "monthly" || freq === "yearly") { _s.setDate(oS.getDate()); if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; } // FIXME if there is a recUpdate that moves the 31st to the 30th, the event // will still only be displayed on months with 31 days } all.push(_s); }; if (Array.isArray(cache[id][uid])) { debug('Get cache', id, uid); if (freq === "monthly") { s.setDate(15); } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) { s.setDate(28); } it(); return cache[id][uid]; } if (rule.by && freq === 'yearly') { var order = BYORDER.slice(); var monthLimit = false; if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) { order.shift(); monthLimit = true; } var first = true; order.forEach(function (_order) { var r = rule.by[_order]; if (!r) { return; } if (first) { r.forEach(expand(_order)); first = false; } else if (_order === "day") { if (rule.by.yearday || rule.by.monthday || rule.by.weekno) { limit('day', rule.by.day); } else { rule.by.day.forEach(expand('day')); } } else { limit(_order, r); } }); if (rule.by.month && monthLimit) { limit('month', rule.by.month); } } if (rule.by && freq === 'monthly') { // We're going to compute all the entries for the coming month if (!rule.by.monthday && !rule.by.day) { addDefault(); } else if (rule.by.monthday) { rule.by.monthday.forEach(expand('monthday')); } else if (rule.by.day) { rule.by.day.forEach(expand('day')); } if (rule.by.month) { limit('month', rule.by.month); } if (rule.by.day && rule.by.monthday) { limit('day', rule.by.day); } } if (rule.by && freq === 'weekly') { // We're going to compute all the entries for the coming week if (!rule.by.day) { addDefault(); } else { rule.by.day.forEach(expand('day')); } if (rule.by.month) { limit('month', rule.by.month); } } if (rule.by && freq === 'daily') { addDefault(); BYDAYORDER.forEach(function (_order) { var r = rule.by[_order]; if (!r) { return; } limit(_order, r); }); } all.sort(function (a, b) { return a-b; }); if (rule.by && rule.by.setpos) { limit('setpos', rule.by.setpos); } if (!rule.by || !Object.keys(rule.by).length) { addDefault(); } else { it(); } var done = []; all = all.filter(function (newS) { var start = new Date(+newS).toLocaleDateString(); if (done.includes(start)) { return false; } done.push(start); return true; }); debug('Set cache', id, uid); cache[id][uid] = all; return all; }; var getNextRules = function (obj) { if (!obj.recUpdate) { return []; } var _allRules = {}; var _obj = obj.recUpdate.from; Object.keys(_obj || {}).forEach(function (d) { var u = _obj[d]; if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; } }); return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); }) .map(function (k) { var r = Util.clone(_allRules[k]); if (!FREQ[r.freq]) { return; } if (r.interval && r.interval < 1) { return; } r._start = Number(k); return r; }).filter(Boolean); }; var fixTimeZone = function (evTimeZone, origin, target) { var getOffset = function (date, tz) { // Get an ISO string using Canadian local format let iso = date.toLocaleString('en-CA', { timeZone:tz, hour12: false }).replace(', ', 'T'); iso += '.' + date.getMilliseconds().toString().padStart(3, '0'); // Get a UTC version of this time let utcDate = new Date(iso + 'Z'); // Return the difference in timestamps, as minutes (60*1000) return -(utcDate - date); }; var myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; var offset = getOffset(origin, evTimeZone) - getOffset(target, evTimeZone); var myOffset = getOffset(origin, myTimeZone) - getOffset(target, myTimeZone); return myOffset - offset; }; Rec.getRecurring = function (months, events) { if (window.CP_DEV_MODE) { debug = console.warn; } var toAdd = []; months.forEach(function (monthId) { // from 1st day of the month at 00:00 to last day at 23:59:59:999 var ms = monthId.split('-'); var _startMonth = new Date(ms[0], ms[1]); var _endMonth = new Date(+_startMonth); _endMonth.setMonth(_endMonth.getMonth() + 1); _endMonth.setMilliseconds(-1); debug('Compute month', _startMonth.toLocaleDateString()); var rec = events || []; rec.forEach(function (obj) { var _start = new Date(obj.start); var _end = new Date(obj.end); var _origin = obj; var rule = obj.recurrenceRule; if (!rule) { return; } var nextRules = getNextRules(obj); var nextRule = nextRules.shift(); if (_start >= _endMonth) { return; } // Check the "until" date of the latest rule we can use and stop now // if the recurrence ends before the current month var until = rule.until; var _nextRules = nextRules.slice(); var _nextRule = nextRule; while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) { until = nextRule.until; _nextRule = _nextRules.shift(); } if (until < _startMonth) { return; } var endData = getEndData(_start, _end); if (rule.interval && rule.interval < 1) { return; } if (!FREQ[rule.freq]) { return; } /* // Rule examples rule.by = { //month: [1, 4, 5, 8, 12], //weekno: [1, 2, 4, 5, 32, 34, 35, 50], //yearday: [1, 2, 29, 30, -2, -1, 250], //monthday: [1, 2, 3, -3, -2, -1], //day: ["MO", "WE", "FR"], //setpos: [1, 2, -1, -2] }; rule.wkst = 0; rule.interval = 2; rule.freq = 'yearly'; rule.count = 10; */ debug('Iterate over', obj.title, obj); debug('Use rule', rule); var count = rule.count; var c = 1; var next = function (start) { var evS = new Date(+start); if (count && c >= count) { return; } debug('Start iteration', evS.toLocaleDateString()); var _toAdd = iterate(rule, obj, evS); debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();}))); // Make sure to continue if the current year doesn't provide any result if (!_toAdd.length) { if (evS.getFullYear() < _startMonth.getFullYear() || evS < _endMonth) { return void next(evS); } return; } var stop = false; var newrule = false; _toAdd.some(function (_newS) { // Make event with correct start and end time var _ev = Util.clone(obj); _ev.id = _origin.id + '|' + (+_newS); var _evS = new Date(+_newS); var _evE = new Date(+_newS); setEndData(_evS, _evE, endData); _ev.start = +_evS; _ev.end = +_evE; _ev._count = c; if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); } if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); } if (nextRule && _ev.start === nextRule._start) { newrule = true; } var useNewRule = function () { if (!newrule) { return; } debug('Use new rule', nextRule); _ev._count = c; count = nextRule.count; c = 1; evS = +_evS; obj = _ev; rule = nextRule; nextRule = nextRules.shift(); }; if (c >= count) { // Limit reached debug(_evS.toLocaleDateString(), 'count'); stop = true; return true; } if (_evS >= _endMonth) { // Won't affect us anymore debug(_evS.toLocaleDateString(), 'endMonth'); stop = true; return true; } if (rule.until && _evS > rule.until) { debug(_evS.toLocaleDateString(), 'until'); stop = true; return true; } if (_evS < _start) { // "Expand" rules may create events before the _start debug(_evS.toLocaleDateString(), 'start'); return; } c++; if (_evE < _startMonth) { // Ended before the current month // Nothing to display but continue the recurrence debug(_evS.toLocaleDateString(), 'startMonth'); if (newrule) { useNewRule(); } return; } // If a recurring event start and end in different months, make sure // it is only added once if ((_evS < _endMonth && _evE >= _endMonth) || (_evS < _startMonth && _evE >= _startMonth)) { if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) { return; } else { recurringAcross[_ev.id] = recurringAcross[_ev.id] || []; recurringAcross[_ev.id].push(_ev.start); } } // Add this event if (_origin.timeZone && !_ev.isAllDay) { var offset = fixTimeZone(_origin.timeZone, _start, _evS); _ev.start += offset; _ev.end += offset; } toAdd.push(_ev); if (newrule) { useNewRule(); return true; } }); if (!stop) { next(evS); } }; next(_start); debug('Added this month (all events)', toAdd.map(function (ev) { return new Date(ev.start).toLocaleDateString(); })); }); }); return toAdd; }; Rec.getAllOccurrences = function (ev) { if (!ev.recurrenceRule) { return [ev.start]; } var r = ev.recurrenceRule; // In case of infinite recursion, we can't get all if (!r.until && !r.count) { return false; } var all = [ev.start]; var d = new Date(ev.start); d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28 var toAdd = []; var i = 0; var check = function () { return r.count ? (all.length < r.count) : (+d <= r.until); }; while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) { Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; })); d.setMonth(d.getMonth() + 1); i++; } return all; }; Rec.diffDate = function (oldTime, newTime) { var n = new Date(newTime); var o = new Date(oldTime); // Diff Days var d = 0; var mult = n < o ? -1 : 1; while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) { n.setDate(n.getDate() - mult); d++; } d = mult * d; // Diff hours n = new Date(newTime); var h = n.getHours() - o.getHours(); // Diff minutes var m = n.getMinutes() - o.getMinutes(); return { d: d, h: h, m: m }; }; var sortUpdate = function (obj) { return Object.keys(obj).sort(function (d1, d2) { return Number(d1) - Number(d2); }); }; Rec.applyUpdates = function (events) { events.forEach(function (ev) { ev.raw = { start: ev.start, end: ev.end, }; if (!ev.recUpdate) { return; } var from = ev.recUpdate.from || {}; var one = ev.recUpdate.one || {}; var s = ev.start; // Add "until" date to our recurrenceRule if it has been modified in future occurences var nextRules = getNextRules(ev).filter(function (r) { return r._start > s; }); var nextRule = nextRules.shift(); var applyDiff = function (obj, k) { var diff = obj[k]; // Diff is always compared to origin start/end var d = new Date(ev.raw[k]); d.setDate(d.getDate() + diff.d); d.setHours(d.getHours() + diff.h); d.setMinutes(d.getMinutes() + diff.m); ev[k] = +d; }; sortUpdate(from).forEach(function (d) { if (s < Number(d)) { return; } Object.keys(from[d]).forEach(function (k) { if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); } if (k === "recurrenceRule" && !from[d][k]) { return; } ev[k] = from[d][k]; }); }); Object.keys(one[s] || {}).forEach(function (k) { if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); } if (k === "recurrenceRule" && !one[s][k]) { return; } ev[k] = one[s][k]; }); if (ev.deleted) { Object.keys(ev).forEach(function (k) { delete ev[k]; }); } if (nextRule && ev.recurrenceRule) { ev.recurrenceRule._next = nextRule._start - 1; } if (ev.reminders) { ev.raw.reminders = ev.reminders; } }); return events; }; return Rec; });