cryptpad/www/calendar/recurrence.js
2023-12-13 15:13:14 +01:00

899 lines
32 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> 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;
});