Enabled repeatables

This commit is contained in:
Keith Holliday
2017-03-15 14:13:15 -06:00
parent 164177f010
commit 8c68f450c6
2 changed files with 68 additions and 43 deletions

View File

@@ -4,8 +4,10 @@
Cron and time / day functions Cron and time / day functions
------------------------------------------------------ ------------------------------------------------------
*/ */
import _ from 'lodash'; // eslint-disable-line lodash/import-scope import defaults from 'lodash/defaults';
import invert from 'lodash/invert';
import moment from 'moment'; import moment from 'moment';
import 'moment-recur';
export const DAY_MAPPING = { export const DAY_MAPPING = {
0: 'su', 0: 'su',
@@ -17,6 +19,8 @@ export const DAY_MAPPING = {
6: 's', 6: 's',
}; };
export const DAY_MAPPING_STRING_TO_NUMBER = invert(DAY_MAPPING);
/* /*
Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences. Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences.
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values. Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values.
@@ -25,13 +29,13 @@ export const DAY_MAPPING = {
function sanitizeOptions (o) { function sanitizeOptions (o) {
let ref = Number(o.dayStart || 0); let ref = Number(o.dayStart || 0);
let dayStart = !_.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0; let dayStart = !Number.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0;
let timezoneOffset; let timezoneOffset;
let timezoneOffsetDefault = Number(moment().zone()); let timezoneOffsetDefault = Number(moment().zone());
if (_.isFinite(o.timezoneOffsetOverride)) { if (Number.isFinite(o.timezoneOffsetOverride)) {
timezoneOffset = Number(o.timezoneOffsetOverride); timezoneOffset = Number(o.timezoneOffsetOverride);
} else if (_.isFinite(o.timezoneOffset)) { } else if (Number.isFinite(o.timezoneOffset)) {
timezoneOffset = Number(o.timezoneOffset); timezoneOffset = Number(o.timezoneOffset);
} else { } else {
timezoneOffset = timezoneOffsetDefault; timezoneOffset = timezoneOffsetDefault;
@@ -81,44 +85,65 @@ export function startOfDay (options = {}) {
export function daysSince (yesterday, options = {}) { export function daysSince (yesterday, options = {}) {
let o = sanitizeOptions(options); let o = sanitizeOptions(options);
return startOfDay(_.defaults({ now: o.now }, o)).diff(startOfDay(_.defaults({ now: yesterday }, o)), 'days'); return startOfDay(defaults({ now: o.now }, o)).diff(startOfDay(defaults({ now: yesterday }, o)), 'days');
} }
/* /*
Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart? Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
*/ */
export function shouldDo (day, dailyTask, options = {}) { export function shouldDo (day, dailyTask) {
if (dailyTask.type !== 'daily') { if (dailyTask.type !== 'daily') {
return false; return false;
} }
let o = sanitizeOptions(options);
let startOfDayWithCDSTime = startOfDay(_.defaults({ now: day }, o));
// The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it. day = moment(day).startOf('day').toDate();
// Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times. let startDate = moment(dailyTask.startDate).startOf('day').toDate();
// NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS.
let taskStartDate = moment(dailyTask.startDate).zone(o.timezoneOffset);
taskStartDate = moment(taskStartDate).startOf('day'); let daysOfTheWeek = [];
if (taskStartDate > startOfDayWithCDSTime.startOf('day')) {
return false; // Daily starts in the future if (dailyTask.repeat) {
} for (let [repeatDay, active] of Object.entries(dailyTask.repeat)) {
if (dailyTask.frequency === 'daily') { // "Every X Days" if (active) daysOfTheWeek.push(parseInt(DAY_MAPPING_STRING_TO_NUMBER[repeatDay], 10));
if (!dailyTask.everyX) {
return false; // error condition
} }
let daysSinceTaskStart = startOfDayWithCDSTime.startOf('day').diff(taskStartDate, 'days');
return daysSinceTaskStart % dailyTask.everyX === 0;
} else if (dailyTask.frequency === 'weekly') { // "On Certain Days of the Week"
if (!dailyTask.repeat) {
return false; // error condition
}
let dayOfWeekNum = startOfDayWithCDSTime.day(); // e.g., 0 for Sunday
return dailyTask.repeat[DAY_MAPPING[dayOfWeekNum]];
} else {
return false; // error condition - unexpected frequency string
} }
}
if (dailyTask.frequency === 'daily') {
if (!dailyTask.everyX) return false; // error condition
let schedule = moment(startDate).recur()
.every(dailyTask.everyX).days();
return schedule.matches(day);
} else if (dailyTask.frequency === 'weekly') {
let schedule = moment(startDate).recur();
if (dailyTask.everyX > 1) {
schedule = schedule.every(dailyTask.everyX).weeks();
}
schedule = schedule.every(daysOfTheWeek).daysOfWeek();
return schedule.matches(day);
} else if (dailyTask.frequency === 'monthly') {
let schedule = moment(startDate).recur();
let differenceInMonths = moment(day).month() + 1 - moment(startDate).month() + 1;
let matchEveryX = differenceInMonths % dailyTask.everyX === 0;
if (dailyTask.weeksOfMonth && dailyTask.weeksOfMonth.length > 0) {
schedule = schedule.every(daysOfTheWeek).daysOfWeek()
.every(dailyTask.weeksOfMonth).weeksOfMonthByDay();
} else if (dailyTask.daysOfMonth && dailyTask.daysOfMonth.length > 0) {
schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth();
}
return schedule.matches(day) && matchEveryX;
} else if (dailyTask.frequency === 'yearly') {
let schedule = moment(startDate).recur();
schedule = schedule.every(dailyTask.everyX).years();
return schedule.matches(day);
}
return false;
}

View File

@@ -8,15 +8,15 @@ fieldset.option-group.advanced-option(ng-show="task.type === 'daily'")
br br
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
//- option(value='daily')=env.t('daily')
//- option(value='weekly')=env.t('weekly')
//- option(value='monthly')=env.t('monthly')
//- option(value='yearly')=env.t('yearly')
select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)') select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
option(value='weekly')=env.t('repeatWeek') option(value='daily')=env.t('daily')
option(value='daily')=env.t('repeatDays') option(value='weekly')=env.t('weekly')
option(value='monthly')=env.t('monthly')
option(value='yearly')=env.t('yearly')
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
//- option(value='weekly')=env.t('repeatWeek')
//- option(value='daily')=env.t('repeatDays')
include ./dailies/repeat_options include ./dailies/repeat_options
@@ -29,6 +29,6 @@ fieldset.option-group.advanced-option(ng-show="task.type === 'daily'")
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfWeek') input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfWeek')
=env.t('dayOfWeek') =env.t('dayOfWeek')
//- .form-group .form-group
//- legend.option-title=env.t('summary') legend.option-title=env.t('summary')
//- div {{summary}} div {{summary}}