UNPKG

quartzcron

Version:

version: `0.1.0` [CHANGELOG](https://github.com/fedeghe/quartzcron/blob/master/CHANGELOG.md)

568 lines (518 loc) 16.2 kB
/* quartzcron (v.0.1.0) */ const { monthEnds, labels, rx, errors, bounds } = require('./constants.js'), {daysLabels2Numbers} = require('./utils.js'); /** * Check if a input year is leap of not */ const isLeap = y => !(y % 4) && (!!(y % 100) || !(y % 400)); /** * given year and month returns the length of that month * if wd is passed then it returns the date of the last weekday (mon-fri) */ const lastMonthDay = (y, m, wd) => { let ret, day; if(m === 1){ ret = isLeap(y) ? 29 : monthEnds[m]; } else { ret = monthEnds[m]; } if (wd) { const da = new Date(Date.UTC(y,m,ret,0,0)); day = da.getUTCDay(); /* istanbul ignore else */ if (day==6){return ret-1;}// sat ? /* istanbul ignore else */ if (day==0){return ret-2;}// sun ? } return ret; }; /** * returns the date of the n-th day before the end of the month */ const nDaysBeforeEndOfMonth = (n, y, m) => { const end = lastMonthDay(y, m), pot = end - n; if (pot < 1) throw new Error(errors.notEnoughDays); return pot; }; /** * returns the n-th weekday present in that month of that year */ const nDayOfMonth = (n, wd, y, m) => { if (wd < 0 || wd > 6) throw new Error(errors.nonWeekday); if (n < 0 || n > 5) throw new Error(errors.monthsOutOfBounds); const end = lastMonthDay(y, m), da = new Date(Date.UTC(y,m,1,0,0)), first = da.getUTCDay(),// 0-6 distance = (first-1+7)%7, firstTarget = (wd + 7 - distance) % 7, nthTarget = (n-1)*7 + firstTarget; if (nthTarget > end) throw new Error(errors.monthOutOfBounds); return nthTarget; }; const getRangeSolver = ({ labelTransformer = v => v, bounds, rx }) => v => { let vstr = `${v}`, spl, mat; if(v ==='?') return null; if(vstr.startsWith('*')){ vstr = vstr.replace('*', `${bounds.min}-${bounds.max}`); } vstr = labelTransformer(vstr); // one if (vstr.match(/^\d*$/)) { return [parseInt(vstr, 10)]; } // commasep mixed spl = vstr.split(/,/); mat = spl.map(s => s.match(/^((\d*)|(\d*)\/(\d*))$/)).filter(Boolean); if(spl.length === mat.length) { return mat.reduce((acc, r) => { let p = parseInt(r[2], 10); if(r[2]) { /* istanbul ignore else */ if (p <= bounds.max)acc.push(p); } else { let cursor = parseInt(r[3], 10); const every = parseInt(r[4], 10); while(cursor <= bounds.max){ acc.push(cursor); cursor += every; } } return acc; }, []).sort(( a, b ) => a > b ? 1 : -1); } // range const range = vstr.match(rx); if (range) { const min = parseInt(range[1],10), max = parseInt(range[2],10), cadence = parseInt(range[4],10); let ret = []; if(cadence){ for(let i = min; i<=max; i+=cadence){ ret.push(i); } } else { // eslint-disable-next-line no-unused-vars ret = Array.from({length: max-min+1}, (_, i) => i+min); } return ret; } return null; }; const getSpecialSolver = solvers => (y, m, val) => { const d = new Date(Date.UTC(y, m-1, 1, 0, 0)), lastDate = lastMonthDay(y, m-1), // eslint-disable-next-line no-unused-vars allDays = Array.from({length: lastDate}, (_,i) => i+1); for(var i = 0, l = solvers.length, r; i < l; i++){ r = solvers[i]({y, m, val, d, allDays, lastDate}); if(r.length) return r; } return []; }; /* DOM * ? [1-31]/[1-31] [1-31]-[1-31] [1-31]-[1-31]/[1-31] L LW L-[1-31] [1-31]W */ const dom_solvers = [ // * ({val, allDays}) => val.match(/^(\*)$/) ? allDays : [], // [1-31] / [1-31] ({val, lastDate}) => { const mat = val.match(/^([1-9]|1[0-9]|2[0-9]|3[01]|\*)\/(([1-9]|1[0-9]|2[0-9]|3[01]|\*))$/); let res = []; if (mat) { let start = parseInt(mat[1], 10), step = parseInt(mat[2], 10); while(start <= lastDate){ res.push(start); start += step; } } return res; }, // [1-31] - [1-31] ({val, lastDate}) => { const mat = val.match(/^([1-9]|1[0-9]|2[0-9]|3[01]|\*)\-(([1-9]|1[0-9]|2[0-9]|3[01]|\*))$/); let res = []; if (mat) { let start = parseInt(mat[1], 10), end = parseInt(mat[2], 10); while(start <= end && start<=lastDate){ res.push(start); start += 1; } } return res; }, // [1-31] ({val, lastDate}) => { const mat = val.match(/^([1-9]|1[0-9]|2[0-9]|3[01])$/); if (mat) { var ret = parseInt(mat[1], 10); if(ret <= lastDate) return [ret]; } return []; }, // [1-31] , ... , [1-31]/[1-31] ({val, lastDate}) => { // let vals = val.split(/,/); // if( // vals.every(v=>{ // return v.match(/^([1-9]|1[0-9]|2[0-9]|3[01])$/); // }) // ) return vals.filter(v => v <=lastDate).map(v => parseInt(v,10)); // else return []; const spl = val.split(/,/), mat = spl.map(s => s.match(/^(([1-9]|1[0-9]|2[0-9]|3[01])|([1-9]|1[0-9]|2[0-9]|3[01])\/([1-9]|1[0-9]|2[0-9]|3[01]))$/)).filter(Boolean); if(spl.length === mat.length) { return mat.reduce((acc, r) => { let v2 = parseInt(r[2], 10); if(typeof r[2] !== 'undefined') { if(v2 <= lastDate) acc.push(v2); } else { let cursor = parseInt(r[3], 10); const every = parseInt(r[4], 10); while(cursor <= lastDate){ acc.push(cursor); cursor += every; } } return acc; }, []).sort(( a, b ) => a > b ? 1 : -1); } return []; }, // [1-31] - [1-31] / [1-31] ({val, lastDate}) => { const vals = val.match(rx.ranged['md-md/md']), res = []; if(vals){ const add = Math.min(parseInt(vals[3],10), lastDate); let tmp = Math.min(parseInt(vals[1],10),lastDate), lim = Math.min(parseInt(vals[2],10), lastDate); while(tmp <= lim) { res.push(tmp); tmp+=add; } } return res; }, // L ({val, lastDate}) => val.match(rx.dumb.L) ? [lastDate]: [], // LW ({val, lastDate, d}) => { if(val.match(rx.dumb.LW)){ d.setUTCDate(lastDate); let res = d.getUTCDay(), min = 0; while(`${(res - min + 7)%7 }`.match(rx.dumb['0OR6'])){ min++; } return [lastDate-min]; } return []; }, //nW // [1-31]W // /^([1-9]|1[0-9]|2[0-9]|3[01])W$/ ({val, lastDate, d}) => { let mat = val.match(rx.ranged.mdW); if(mat){ let v = parseInt(mat[1], 10), res; d.setUTCDate(v); res = d.getUTCDay(); // [0-6] if (v===1) { // if sat or sun return next mon if(res === 6) return [3]; if(res === 0) return [2]; return [1]; } //same on the other side if (v===lastDate) { if(res === 6) return [lastDate-1]; if(res === 0) return [lastDate-2]; return [lastDate]; } if(res === 0)return [v+1]; if(res === 6)return [v-1]; return [v]; } return []; }, // L-[1-31] ({val, lastDate}) => { const vals = val.match(rx.ranged['L-md']); if(vals){ const last = lastDate-parseInt(vals[1]); return last> 0 ? [last] : []; } return []; }, ]; const solve_dom = getSpecialSolver(dom_solvers); /* DOW * ? [1-7|*]/[1-7] [1-7] OR [SUN-SAt] 1,2,4 OR SUN,MON,WED 1-7 OR SUN-SAT 2-6/2 OR MON-FRI/2 aL wd#n */ const dow_solvers = [ // * ({val, allDays}) => // val.match(/^(\*)$/) val.match(rx.dumb.astrx) ? allDays : [], // [1-7]|* / [1-7] // a/b : every b days starting on first a // here * is like sunday (1) ({val, d, lastDate}) => { // const mat = val.match(/^([1-7]|\*)\/([1-7])$/); const mat = val.match(rx.ranged['wdOR*/wd']); let res = []; if (mat) { const fromFirstWd = mat[1]==='*' ? 1 : parseInt(mat[1], 10), step = parseInt(mat[2]); // now find first day wd let firstDayWd = d.getUTCDay() + 1,// [0-6] -> [1,7] toAddDate = 1; while(firstDayWd !== fromFirstWd){ firstDayWd = 1 + firstDayWd%7; toAddDate++; } while(toAddDate <= lastDate){ res.push(toAddDate); toAddDate+=step; } } return res; }, // one or more [1-7] OR [SUN-SAT] comma separated ({val, d, lastDate}) => { let vals = daysLabels2Numbers(val).split(rx.dumb.comma); const firstDayWd = d.getUTCDay()+1,// [0-6] -> [1-7] res = [], max = 7; let mat = vals.map( s => s.match(rx.ranged['wdORwd/wd']) ).filter(Boolean); // collect all weekdays, counting explicit ones // together with those coming from cadence // MON/2 => 2,4,6 // TUE => 3 if(mat.length === vals.length){ vals = mat.reduce((acc, r) => { let v2 = parseInt(r[2], 10); if(typeof r[2] !== 'undefined') { //not needed ..the rx grants it //if(v2 <= max) acc.push(v2); } else { let cursor = parseInt(r[3], 10); const every = parseInt(r[4], 10); while(cursor <= max){ acc.push(cursor); cursor = every + cursor; } } return acc; }, []).map(v => v.toString()); } // now for 2,3,4,6 in [1-7] scan the month to collect right dates if( // vals.every(v => v.match(/^([1-7])$/)) vals.every(v => v.match(rx.ranged.wd)) ) { let cursor = firstDayWd, toAddDate = 1; while(toAddDate <= lastDate) { if(vals.includes(`${cursor}`)) res.push(toAddDate); cursor = 1 + cursor%7; toAddDate+= 1; } } return res; }, // [1-7]-[1-7] OR [SUN-SAT]-[SUN-SAT] ({val, d, lastDate}) => { const mat = daysLabels2Numbers(val) .match(rx.ranged['wd-wd']); // .match(/^(([1-7])-([1-7]))$/); let res = []; if (mat) { let cursor = parseInt(mat[2], 10), firstDayWd = d.getUTCDay()+1, rangeFilled = false, toAddDate = 1; const to = parseInt(mat[3], 10), range = [cursor]; while(!rangeFilled) { cursor = 1 + cursor%7; range.push(cursor); rangeFilled = cursor === to; } while(toAddDate <= lastDate) { if(range.includes(firstDayWd)) res.push(toAddDate); firstDayWd = 1 + firstDayWd%7; toAddDate+= 1; } } return res; }, // [1-7]-[1-7]/[1-7] OR [SUN-SAT]-[SUN-SAT]/[1-7] // a-b/c every c days between as and bs ({val, d, lastDate}) => { const mat = daysLabels2Numbers(val) .match(rx.ranged['wd-wd/wd']), // .match(/^(([1-7])-([1-7])\/([1-7]))$/), res = []; let firstDayWd = d.getUTCDay()+1,// [0-6] -> [1,7] toAddDate = 1; if (mat) { const to = parseInt(mat[3], 10), cadence = parseInt(mat[4], 10); let rangeFilled = false, cursor = parseInt(mat[2], 10), range = [cursor]; while(!rangeFilled) { cursor = (1 + cursor)%7; range.push(cursor); rangeFilled = cursor === to; } // eslint-disable-next-line no-unused-vars range = range.filter((_,i) => { return i === 0 || i % cadence === 0; }); while(toAddDate <= lastDate) { if(range.includes(firstDayWd)) res.push(toAddDate); firstDayWd = 1 + firstDayWd%7; toAddDate+= 1; } } return res; }, // [1-7]L // aL the last a of the month ({val, d, lastDate}) => { const mat = val.match(rx.ranged.wdL); // const mat = val.match(/^([1-7])L$/); let res = [], trg = lastDate; d.setUTCDate(lastDate); // d.setUTCHours(0); // d.setUTCMinutes(0); // d.setUTCSeconds(0); // d.setUTCHours(0); let cursorDay = d.getUTCDay() + 1; // [0-6] -> [1,7] if (mat) { let trgWd = parseInt(mat[1], 10); while(cursorDay !== trgWd){ cursorDay = cursorDay-1>0 ? cursorDay-1 : 7; trg--; } return [trg]; } return res; }, // [1-7]#[1-5] // a#b the b-th a weekday of the month ({val, d, lastDate}) => { const mat = val.match(rx.ranged['wd#wdn']), // const mat = val.match(/^([1-7])#([1-5])$/), res = []; d.setUTCDate(1); let cursorDate = 1; cursorDay = d.getUTCDay() + 1; // [0-6] -> [1,7] if (mat) { const wd = parseInt(mat[1], 10), n = parseInt(mat[2], 10); while (cursorDay !== wd) { cursorDay = cursorDay+1 > 7 ? 1 : cursorDay+1; cursorDate++; } let ret = cursorDate+(n-1)*7; return ret <= lastDate ? [ret] : []; } return res; }, ]; const solve_dow = getSpecialSolver(dow_solvers); const solve_0_59_ranges = getRangeSolver({ bounds: bounds.seconds, rx: rx.ranges.wildRangeCadence }), solve_hours_ranges = getRangeSolver({ bounds: bounds.hour, rx: rx.ranges.wildRangeCadence }), solve_month_ranges = getRangeSolver({ bounds: bounds.month, labelTransformer: vstr => vstr.match(rx.next.hasMonths) ? labels.months.reduce( (acc, day, i) => acc.replace(day, i+1), vstr ) : vstr, rx: rx.ranges.wildRangeCadence }), solve_year_ranges = getRangeSolver({ bounds: bounds.year, rx: rx.ranges.wildRangeCadence }), // this will be no more needed solve_week_ranges = getRangeSolver({ bounds: bounds.week, labelTransformer: vstr => vstr.match(rx.next.hasWeekdays) ? labels.days.reduce( (acc, day, i) => acc.replace(day, i+1), vstr ) : vstr, rx: rx.ranges.wildRangeCadence }); module.exports = { isLeap, lastMonthDay, nDaysBeforeEndOfMonth, nDayOfMonth, // solveNumericRange, solve_0_59_ranges, solve_year_ranges, solve_month_ranges, solve_week_ranges, solve_hours_ranges, solve_dom, solve_dow };