// From crontab.guru. // 10 let describe = (function (e, t, n) { 'use strict'; function u(e) { var t = parseInt(e); switch (20 < t ? t % 10 : t) { case 1: return e + 'st'; case 2: return e + 'nd'; case 3: return e + 'rd'; default: return e + 'th' } } function i(e, t, n, r) { return '*' === e ? 'every ' + t : function (e, t, n, r) { var o = e.match(/\d+|./g).map(function (e) { var t = Number(e); return isNaN(t) ? e : t }), a = o[0]; if (Number.isInteger(a)) { if (1 === o.length) return '' + (n[a] || a); if (3 === o.length && '/' === o[1] && Number.isInteger(o[2])) return 'every ' + u(o[2]) + ' ' + t + ' from ' + (n[a] || a) + ' through ' + (n[r] || r); if (3 === o.length && '-' === o[1] && Number.isInteger(o[2]) && o[2] >= a) return 'every ' + t + ' from ' + (n[a] || a) + ' through ' + (n[o[2]] || o[2]); if (5 === o.length && '-' === o[1] && Number.isInteger(o[2]) && o[2] >= a && '/' === o[3] && Number.isInteger(o[4]) && 1 <= o[4]) return 'every ' + u(o[4]) + ' ' + t + ' from ' + (n[a] || a) + ' through ' + (n[o[2]] || o[2]) } else if (3 === o.length && '/' === o[1] && Number.isInteger(o[2]) && '*' === o[0]) return 'every ' + u(o[2]) + ' ' + t; return '' }(e, t, n, r) } function h(e, t, n, r, o) { var a = e.split(','); return ((o ? '' : t + ' ') + function (e) { switch (e.length) { case 0: return ''; case 1: return e[0]; case 2: return e[0] + ' and ' + e[1]; default: return e.slice(0, e.length - 1).join(', ') + ', and ' + e[e.length - 1] } }(a.map(function (e) { return i(e, t, n, r) }))).replace('every 1st', 'every').replace(t + ' every', 'every').replace(', ' + t, ', ').replace(', and ' + t, ', and ') } var v = [ null, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; var y = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; var b = /^0*\d\d?$/; var g = 'After rebooting.'; return function (e) { if ('@reboot' === e.originalParts[0]) return { full: g, special: g }; var t, n, r, o = e.parts, a = '*' === (r = o[2]) ? '' : 'on ' + h(r, 'day-of-month', { }, 31), u = '*' === (n = o[3]) ? '' : 'in ' + h(n, 'month', v, 12, !0), i = '*' === (t = o[4]) ? '' : 'on ' + h(t, 'day-of-week', y, 7, !0), s = ''; a && i && (s = e.daysAnded ? 'if it\'s' : 'and'); var c, l, d = (c = o[0], l = o[1], b.test(c) && b.test(l) ? [ ('0' + c).slice( - 2), ('0' + l).slice( - 2) ] : null); if (d) return { start: 'At', minutes: d[0], hours: d[1], isTime: !0, dates: a || null, datesWeekdays: s || null, weekdays: i || null, months: u || null, end: '.', full: ('At ' + d[1] + ':' + d[0] + ' ' + a + ' ' + s + ' ' + i + ' ' + u).replace(/ +/g, ' ').trim() + '.' }; var m, f = h(o[0], 'minute', { }, 59), p = '*' === (m = o[1]) ? '' : 'past ' + h(m, 'hour', { }, 23); return { start: 'At', minutes: f || null, hours: p || null, dates: a || null, datesWeekdays: s || null, weekdays: i || null, months: u || null, end: '.', full: ('At ' + f + ' ' + p + ' ' + a + ' ' + s + ' ' + i + ' ' + u).replace(/ +/g, ' ').trim() + '.' } }; })(); // 13 let normalize = ( function (e, t, n) { 'use strict'; function h(e, t) { return e - t } function v(e) { return e.reduce(function (e, t) { return e.indexOf(t) < 0 && e.push(t), e }, [ ]) } function r(e) { return e.reduce(function (e, t) { return e.concat(Array.isArray(t) ? r(t) : t) }, [ ]) } function o(e, t, n) { for (var r = [ ], o = e; o <= t; o += n) r.push(o); return r } var a = /(^|[,-/])\*($|[,-/])/g; function y(e, t) { var n = '$1' + t + '$2'; return e.replace(a, n).replace(a, n) } function b(e, t) { var n = e.split(',').map(function (e) { return function (e, t) { var n = e ? e.match(/\d+|./g).map(function (e) { var t = Number(e); return isNaN(t) ? e : t }) : [ ], r = n[0]; if (Number.isInteger(r)) { if (1 === n.length) return { list: [ r ] }; if (3 === n.length && '/' === n[1] && Number.isInteger(n[2]) && 1 <= n[2]) return { list: o(r, t, n[2]), warnings: [ 'nonstandard' ] }; if (3 === n.length && '-' === n[1] && Number.isInteger(n[2]) && n[2] >= r) return { list: o(r, n[2], 1) }; if (5 === n.length && '-' === n[1] && Number.isInteger(n[2]) && n[2] >= r && '/' === n[3] && Number.isInteger(n[4]) && 1 <= n[4]) return { list: o(r, n[2], n[4]) } } return { errors: [ 'invalid part' ] } }(e, t) }); return { list: v(r(n.map(function (e) { return e.list || [ ] }))).sort(h).filter(function (e) { return !isNaN(e) }), errors: v(r(n.map(function (e) { return e.errors || [ ] }))), warnings: v(r(n.map(function (e) { return e.warnings || [ ] }))) } } function g(e, t, n) { return e.length && (e[0] < t || e[e.length - 1] > n) } var w = /[^\d\-\/\,]/i; return function (e) { var t = e.parts.map(function (e) { return e.slice(0) }).map(function (e) { return e.replace(/\*\/1(?!\d)/g, '*') }); if (0 === t.length && e.originalParts.length) return { }; var n = { errors: [ ], warnings: [ ] }; if (void 0 !== e.daysAnded && (n.daysAnded = e.daysAnded), 5 !== t.length && n.errors.push('fields'), t[0] && t[0].length) { var r = y(t[0], '0-59'), o = b(r, 59); n.minutes = o.list, (o.errors.length || g(n.minutes, 0, 59) || w.test(r)) && (n.minutes = [ ], n.errors.push('minutes')), o.warnings.length && n.warnings.push('minutes') } else void 0 === t[0] && n.errors.push('minutes'); if (t[1] && t[1].length) { var a = y(t[1], '0-23'), u = b(a, 23); n.hours = u.list, (u.errors.length || g(n.hours, 0, 23) || w.test(a)) && (n.hours = [ ], n.errors.push('hours')), u.warnings.length && n.warnings.push('hours') } else void 0 === t[1] && n.errors.push('hours'); if (t[2] && t[2].length) { var i = y(t[2], '1-31'), s = b(i, 31); n.dates = s.list, (s.errors.length || g(n.dates, 1, 31) || w.test(i)) && (n.dates = [ ], n.errors.push('dates')), s.warnings.length && n.warnings.push('dates') } else void 0 === t[2] && n.errors.push('dates'); if (t[3] && t[3].length) { var c = y(t[3], '1-12'), l = e.originalParts[3], d = b(c, 12); n.months = d.list, (d.errors.length || g(n.months, 1, 12) || w.test(c)) && (n.months = [ ], n.errors.push('months')), (d.warnings.length || l && t[3] !== l && 3 < l.length && /\D/.test(l)) && n.warnings.push('months') } else void 0 === t[3] && n.errors.push('months'); if (t[4] && t[4].length) { var m = y(t[4], '0-6'), f = e.originalParts[4], p = b(m, 7); n.weekdays = v(p.list.map(function (e) { return 7 === e ? 0 : e })).sort(h), (p.errors.length || g(n.weekdays, 0, 6) || w.test(m)) && (n.weekdays = [ ], n.errors.push('weekdays')), (p.warnings.length || p.list.includes(7) || f && t[4] !== f && 3 < f.length && /\D/.test(f)) && n.warnings.push('weekdays') } else void 0 === t[4] && n.errors.push('weekdays'); return n.errors.length || delete n.errors, n.warnings.length || delete n.warnings, n } } )(); // 15 let parse = (function (e, t, n) { 'use strict'; function a(e, i) { return Object.keys(i).reduce(function (e, t) { return n = e, o = i[r = t], a = new RegExp('(^|[ ,-/])' + r + '($|[ ,-/])', 'gi'), u = '$1' + o + '$2', n.replace(a, u).replace(a, u); var n, r, o, a, u }, e) } var u = { sun: '0', mon: '1', tue: '2', wed: '3', thu: '4', fri: '5', sat: '6' }; var i = { jan: '1', feb: '2', mar: '3', apr: '4', may: '5', jun: '6', jul: '7', aug: '8', sep: '9', oct: '10', nov: '11', dec: '12' }; var s = { '@yearly': [ '0', '0', '1', '1', '*' ], '@annually': [ '0', '0', '1', '1', '*' ], '@monthly': [ '0', '0', '1', '*', '*' ], '@weekly': [ '0', '0', '*', '*', '0' ], '@daily': [ '0', '0', '*', '*', '*' ], '@midnight': [ '0', '0', '*', '*', '*' ], '@hourly': [ '0', '*', '*', '*', '*' ] }; return function (e) { var t = e.trim().split(/\s+/).filter(function (e) { return e }); if (1 === t.length && '@reboot' === t[0]) return { originalParts: t, parts: [ ] }; var n, r, o = (1 === t.length ? (n = t[0], r = s[n], void 0 !== r ? r : [ n ]) : t).map(function (e, t) { switch (t) { case 3: return a(e, i); case 4: return a(e, u); default: return e } }); return { originalParts: t, parts: o, daysAnded: !!o[2] && '*' === o[2][0] || !!o[4] && '*' === o[4][0] } } })(); let parsed, schedule; try { parsed = parse(process.argv[2]); } catch (error) { console.log("Could not parse crontab (#1)."); process.exit(1); } try { schedule = normalize(parsed); if (schedule.errors) { if (schedule.errors.includes('fields')) { console.log('Errors with crontab fields. Should be: minute hour day-of-month month day-of-week'); } else { let copy = parsed.parts; let r = [ 'minutes', 'hours', 'dates', 'months', 'weekdays' ]; for (let f of schedule.errors) { let c = r.indexOf(f); copy[c] = '\x0304' + copy[c] + '\x03'; } console.log('Error with crontab: ' + copy.join(' ') + ' (' + schedule.errors.join(', ') + ')') } process.exit(0); } } catch (error) { console.log("Could not parse crontab (#2)."); process.exit(1); } try { console.log(describe(parsed).full); } catch (error) { console.log("Could not describe crontab."); process.exit(1); }