diff --git a/test/common/shouldDo.test.js b/test/common/shouldDo.test.js index 5f2fe4494f..7f08076b25 100644 --- a/test/common/shouldDo.test.js +++ b/test/common/shouldDo.test.js @@ -5,6 +5,7 @@ import 'moment-recur'; describe('shouldDo', () => { let day, dailyTask; let options = {}; + let nextDue = []; beforeEach(() => { day = new Date(); @@ -223,6 +224,25 @@ describe('shouldDo', () => { expect(shouldDo(day, dailyTask, options)).to.equal(true); }); + it('should compute daily nextDue values', () => { + options.timezoneOffset = 0; + options.nextDue = true; + + day = moment('2017-05-01').toDate(); + dailyTask.frequency = 'daily'; + dailyTask.everyX = 2; + dailyTask.startDate = day; + + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-03').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-05').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-07').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-09').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-11').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-13').toDate()); + }); + context('On multiples of x', () => { it('returns true when Custom Day Start is midnight', () => { dailyTask.startDate = moment().subtract(7, 'days').toDate(); @@ -367,6 +387,73 @@ describe('shouldDo', () => { expect(shouldDo(day, dailyTask, options)).to.equal(true); }); + it('should compute weekly nextDue values', () => { + options.timezoneOffset = 0; + options.nextDue = true; + + day = moment('2017-05-01').toDate(); + dailyTask.frequency = 'weekly'; + dailyTask.everyX = 1; + dailyTask.repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + }; + dailyTask.startDate = day; + + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-02').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-03').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-04').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-05').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-06').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-07').toDate()); + + dailyTask.everyX = 2; + dailyTask.repeat = { + su: true, + m: false, + t: false, + w: false, + th: false, + f: true, + s: false, + }; + dailyTask.startDate = day; + + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-05').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-14').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-19').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-28').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-06-02').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-06-11').toDate()); + }); + + it('should not go into an infinite loop with invalid values', () => { + options.nextDue = true; + + day = moment('2017-05-01').toDate(); + dailyTask.frequency = 'weekly'; + dailyTask.everyX = 1; + dailyTask.startDate = null; + + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue).to.eql(false); + + dailyTask.startDate = day; + dailyTask.everyX = 0; + + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue).to.eql(false); + }); + context('Day of the week matches', () => { const weekdayMap = { 1: 'm', @@ -667,6 +754,96 @@ describe('shouldDo', () => { expect(shouldDo(day, dailyTask, options)).to.equal(true); }); + it('should compute monthly nextDue values', () => { + options.timezoneOffset = 0; + options.nextDue = true; + + day = moment('2017-05-01').toDate(); + + dailyTask.frequency = 'monthly'; + dailyTask.everyX = 3; + dailyTask.startDate = day; + dailyTask.daysOfMonth = [1]; + dailyTask.weeksOfMonth = []; + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-08-01').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-11-01').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-02-01').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-05-01').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-08-01').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-11-01').toDate()); + + dailyTask.daysOfMonth = []; + dailyTask.weeksOfMonth = [0]; + dailyTask.everyX = 1; + dailyTask.repeat = { + su: false, + m: true, + t: false, + w: false, + th: false, + f: false, + s: false, + }; + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-05').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-03').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-07').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-04').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-02').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-06').toDate()); + + day = moment('2017-05-08').toDate(); + + dailyTask.daysOfMonth = []; + dailyTask.weeksOfMonth = [1]; + dailyTask.startDate = day; + dailyTask.everyX = 1; + dailyTask.repeat = { + su: false, + m: true, + t: false, + w: false, + th: false, + f: false, + s: false, + }; + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-12').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-10').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-14').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-11').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-09').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-13').toDate()); + + day = moment('2017-05-29').toDate(); + + dailyTask.daysOfMonth = []; + dailyTask.weeksOfMonth = [4]; + dailyTask.startDate = day; + dailyTask.everyX = 1; + dailyTask.repeat = { + su: false, + m: true, + t: false, + w: false, + th: false, + f: false, + s: false, + }; + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-07-31').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-10-30').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-01-29').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-04-30').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-07-30').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-10-29').toDate()); + }); + context('Custom Day Start is 0 <= n < 24', () => { beforeEach(() => { options.dayStart = 7; @@ -918,6 +1095,25 @@ describe('shouldDo', () => { expect(shouldDo(day, dailyTask, options)).to.equal(true); }); + it('should compute yearly nextDue values', () => { + options.timezoneOffset = 0; + options.nextDue = true; + + day = moment('2017-05-01').toDate(); + + dailyTask.frequency = 'yearly'; + dailyTask.everyX = 5; + dailyTask.startDate = day; + nextDue = shouldDo(day, dailyTask, options); + expect(nextDue.length).to.eql(6); + expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2022-05-01').toDate()); + expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2027-05-01').toDate()); + expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2032-05-01').toDate()); + expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2037-05-01').toDate()); + expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2042-05-01').toDate()); + expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2047-05-01').toDate()); + }); + context('Custom Day Start is 0 <= n < 24', () => { beforeEach(() => { options.dayStart = 7; diff --git a/website/common/script/cron.js b/website/common/script/cron.js index 1205e15472..ca2cce98c3 100644 --- a/website/common/script/cron.js +++ b/website/common/script/cron.js @@ -94,7 +94,7 @@ export function daysSince (yesterday, options = {}) { */ export function shouldDo (day, dailyTask, options = {}) { - if (dailyTask.type !== 'daily') { + if (dailyTask.type !== 'daily' || dailyTask.startDate === null || dailyTask.everyX < 1 || dailyTask.everyX > 9999) { return false; } let o = sanitizeOptions(options); @@ -110,7 +110,6 @@ export function shouldDo (day, dailyTask, options = {}) { } let daysOfTheWeek = []; - if (dailyTask.repeat) { for (let [repeatDay, active] of Object.entries(dailyTask.repeat)) { if (!isFinite(DAY_MAPPING_STRING_TO_NUMBER[repeatDay])) continue; // eslint-disable-line no-continue @@ -123,7 +122,14 @@ export function shouldDo (day, dailyTask, options = {}) { let schedule = moment(startDate).recur() .every(dailyTask.everyX).days(); - if (options.nextDue) return schedule.fromDate(startOfDayWithCDSTime).next(6); + if (options.nextDue) { + let filteredDates = []; + for (let i = 1; filteredDates.length < 6; i++) { + let calcDate = moment(startDate).add(dailyTask.everyX * i, 'days'); + if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); + } + return filteredDates; + } return schedule.matches(startOfDayWithCDSTime); } else if (dailyTask.frequency === 'weekly') { @@ -134,15 +140,20 @@ export function shouldDo (day, dailyTask, options = {}) { if (daysOfTheWeek.length === 0) return false; schedule = schedule.every(daysOfTheWeek).daysOfWeek(); - if (options.nextDue) { - let dates = schedule.fromDate(startOfDayWithCDSTime.subtract('1', 'days')).next(6); - let filterDates = dates.filter((momentDate) => { - let weekDiff = momentDate.week() - moment(startDate).week(); - let matchX = weekDiff % dailyTask.everyX === 0; - return matchX; + let filteredDates = []; + for (let i = 0; filteredDates.length < 6; i++) { + for (let j = 0; j < daysOfTheWeek.length && filteredDates.length < 6; j++) { + let calcDate = moment(startDate).day(daysOfTheWeek[j]).add(dailyTask.everyX * i, 'weeks'); + if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); + } + } + let sortedDates = filteredDates.sort((date1, date2) => { + if (date1.toDate() > date2.toDate()) return 1; + if (date2.toDate() > date1.toDate()) return -1; + return 0; }); - return filterDates; + return sortedDates; } return schedule.matches(startOfDayWithCDSTime) && matchEveryX; @@ -154,19 +165,42 @@ export function shouldDo (day, dailyTask, options = {}) { if (dailyTask.weeksOfMonth && dailyTask.weeksOfMonth.length > 0) { schedule = schedule.every(daysOfTheWeek).daysOfWeek() - .every(dailyTask.weeksOfMonth).weeksOfMonthByDay(); + .every(dailyTask.weeksOfMonth).weeksOfMonthByDay(); + + if (options.nextDue) { + let filteredDates = []; + for (let i = 1; filteredDates.length < 6; i++) { + let recurDate = moment(startDate).add(dailyTask.everyX * i, 'months'); + let calcDate = recurDate.clone(); + calcDate.day(daysOfTheWeek[0]); + + let startDateWeek = Math.ceil(moment(startDate).date() / 7); + let calcDateWeek = Math.ceil(calcDate.date() / 7); + + // adjust week since weeks will rollover to other months + if (calcDate.month() < recurDate.month()) calcDate.add(1, 'weeks'); + else if (calcDate.month() > recurDate.month()) calcDate.subtract(1, 'weeks'); + else if (calcDateWeek > startDateWeek) calcDate.subtract(1, 'weeks'); + else if (calcDateWeek < startDateWeek) calcDate.add(1, 'weeks'); + + calcDateWeek = Math.ceil(calcDate.date() / 7); + + if (calcDate >= startOfDayWithCDSTime && + calcDateWeek === startDateWeek && calcDate.month() === recurDate.month()) filteredDates.push(calcDate); + } + return filteredDates; + } + return schedule.matches(startOfDayWithCDSTime) && matchEveryX; } else if (dailyTask.daysOfMonth && dailyTask.daysOfMonth.length > 0) { schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth(); - } - - if (options.nextDue) { - let dates = schedule.fromDate(startOfDayWithCDSTime).next(6); - let filterDates = dates.filter((momentDate) => { - let monthDiff = momentDate.month() - moment(startDate).month(); - let matchX = monthDiff % dailyTask.everyX === 0; - return matchX; - }); - return filterDates; + if (options.nextDue) { + let filteredDates = []; + for (let i = 1; filteredDates.length < 6; i++) { + let calcDate = moment(startDate).add(dailyTask.everyX * i, 'months'); + if (calcDate >= startOfDayWithCDSTime) filteredDates.push(calcDate); + } + return filteredDates; + } } return schedule.matches(startOfDayWithCDSTime) && matchEveryX; @@ -176,17 +210,15 @@ export function shouldDo (day, dailyTask, options = {}) { schedule = schedule.every(dailyTask.everyX).years(); if (options.nextDue) { - let dates = schedule.fromDate(startOfDayWithCDSTime).next(6); - let filterDates = dates.filter((momentDate) => { - let monthDiff = momentDate.years() - moment(startDate).years(); - let matchX = monthDiff % dailyTask.everyX === 0; - return matchX; - }); - return filterDates; + let filteredDates = []; + for (let i = 1; filteredDates.length < 6; i++) { + let calcDate = moment(startDate).add(dailyTask.everyX * i, 'years'); + if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); + } + return filteredDates; } return schedule.matches(startOfDayWithCDSTime); } - return false; }