cronli5
Version:
Cron Like I'm Five: A Cron to English Utility
560 lines (452 loc) • 13 kB
JavaScript
/**
* @license MIT, Copyright (c) 2016 Andrew Broz
*/
(function(root) {
// Options flags.
var AMPM = true;
var SECONDS = false;
var SHORT = false;
var YEARS = false;
// English number names for the integers zero through ten.
var numbers = [
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten'
];
// English ordinal names for the integers zero through ten.
var ordinals = [
null,
'first',
'second',
'third',
'fourth',
'fifth',
'sixth',
'seventh',
'eighth',
'ninth',
'tenth'
];
// Ordianl suffixes.
var suffixes = [
'th',
'st',
'nd',
'rd'
];
// English month names.
var monthNames = [
null,
['January', 'Jan'],
['February', 'Feb'],
['March', 'Mar'],
['April', 'Apr'],
['May', 'May'],
['June', 'Jun'],
['July', 'Jul'],
['August', 'Aug'],
['September', 'Sep'],
['October', 'Oct'],
['November', 'Nov'],
['December', 'Dec']
];
// English weekday names.
var weekdayNames = [
['Sunday', 'Sun'],
['Monday', 'Mon'],
['Tuesday', 'Tue'],
['Wednesday', 'Wed'],
['Thursday', 'Thu'],
['Friday', 'Fri'],
['Saturday', 'Sat']
];
// Month names by abbreviation.
var monthAbbreviations = {
'*': ['month', 'month'],
JAN: monthNames[1],
FEB: monthNames[2],
MAR: monthNames[3],
APR: monthNames[4],
MAY: monthNames[5],
JUN: monthNames[6],
JUL: monthNames[7],
AUG: monthNames[8],
SEP: monthNames[9],
OCT: monthNames[10],
NOV: monthNames[11],
DEC: monthNames[12]
};
// Weekday name by abbreviation.
var weekdayAbbreviations = {
'*': ['day', 'day'],
SUN: weekdayNames[0],
MON: weekdayNames[1],
TUE: weekdayNames[2],
WED: weekdayNames[3],
THU: weekdayNames[4],
FRI: weekdayNames[5],
SAT: weekdayNames[6]
};
// A cron pattern to English interpreter.
//
// `options` include:
// - ampm (boolean):
// use AM/PM instead of zero-padded 24-hour time
// - seconds (boolean):
// always treat the first value in a string or array as a second
// - short (boolean):
// use shorthand and numeric representations
// - year (boolean):
// parse with year
function cronli5(cronPattern, options) {
setOptions(options);
cronPattern = parseCronPattern(cronPattern);
return interpretSeconds(cronPattern) ||
interpretMinutes(cronPattern) ||
interpretHours(cronPattern) ||
interpretDates(cronPattern) + ' ' +
interpretMonths(cronPattern) + ' ' +
interpretWeekdays(cronPattern);
}
// Set option flags.
function setOptions(options) {
options = options || {};
AMPM = typeof options.ampm === 'boolean' ? options.ampm : true;
SECONDS = !!options.seconds;
SHORT = !!options.short;
YEARS = !!options.years;
}
// Take a cron pattern as, a cron pattern string, an array of cron fields, a
// cron-like object (see the final return statement for the format of a
// cron-like object), or a stringable object that evaluates to a cron pattern
// string. Returns a cron-like object.
function parseCronPattern(cronPattern) {
var isArray = cronPattern instanceof Array;
// Throw if null or empty.
if (!cronPattern || isArray && cronPattern.length === 0) {
throw new Error(
'`cronli5` expects a non-empty cron pattern as the first argument.');
}
if (isArray) {
return cronifyArray(cronPattern);
}
if (typeof cronPattern === 'object') {
return cronifyObject(cronPattern);
}
if (typeof cronPattern === 'string') {
return cronifyString(cronPattern);
}
throw new Error('`cronli5` was passed an unexpected type.');
}
// Turn a cronable array into a cron-like object.
function cronifyArray(cronlikeArray) {
var max = YEARS ? 7 : 6;
if (cronlikeArray.length > max) {
throw new Error('`cronli5` was passed a cron pattern with more than ' +
getNumber(max) + ' fields.');
}
if (!SECONDS && cronlikeArray.length < max) {
cronlikeArray.unshift('0');
}
return {
second: cronlikeArray[0] || '0',
minute: cronlikeArray[1] || '*',
hour: cronlikeArray[2] || '*',
date: cronlikeArray[3] || '*',
month: cronlikeArray[4] || '*',
weekday: cronlikeArray[5] || '*',
year: cronlikeArray[6] || '*'
};
}
// Turn an object that's already cron-like into a populated cron-like object.
function cronifyObject(cronable) { // eslint-disable-line complexity
if (!cronable.second && !cronable.minute && !cronable.hour) {
throw new Error(
'`cronli5` expects that any object being interpreted as a cron ' +
'pattern have at least one of the following properties: `second`, ' +
'`minute`, or `hour`');
}
var defaultMinute = cronable.second ? '*' : '0';
var defaultHour = cronable.second || cronable.minute ? '*' : '0';
return {
second: cronable.second || '0',
minute: cronable.minute || defaultMinute,
hour: cronable.hour || defaultHour,
date: cronable.date || '*',
month: cronable.month || '*',
weekday: cronable.weekday || '*',
year: cronable.year || '*'
};
}
// Turn a string into a cron-like object.
function cronifyString(cronString) {
var cronlikeArray = cronString.split(/\s+/);
return cronifyArray(cronlikeArray);
}
// Second field.
function interpretSeconds(cronPattern) {
var secondField = cronPattern.second;
return interpretRangeOfSeconds(secondField) ||
interpretRepeatingSeconds(secondField) ||
interpretMultipleSeconds(secondField) ||
interpretSingleSecond(secondField);
}
function interpretRangeOfSeconds(secondField) {
if (!includes(secondField, '-')) {
// Not a range pattern.
return;
}
var result = 'every second between ';
secondField = secondField.split('-');
var start = secondField[0];
var interval = secondField[1];
if (interval > 1) {
result += getNumber(interval) + ' seconds';
}
else if (interval == 1 || interval == 0) {
result += 'second';
}
if (start !== '*' && start !== '0') {
result += ' past second ' + getNumber(start);
}
return result;
}
function interpretRepeatingSeconds(secondField) {
if (!includes(secondField, '/')) {
// Not a repeating interval pattern.
return;
}
var result = 'every ';
secondField = secondField.split('/');
var start = secondField[0];
var interval = secondField[1];
if (interval > 1) {
result += getNumber(interval) + ' seconds';
}
else if (interval == 1 || interval == 0) {
result += 'second';
}
if (start !== '*' && start !== '0') {
result += ' past second ' + getNumber(start);
}
return result;
}
function interpretMultipleSeconds(secondField) {
if (!includes(secondField, ',')) {
// Not a multiple second pattern.
return;
}
return 'on seconds ' +
secondField.slice(0, -1).map(getNumber).join(', ') +
' and ' +
getNumber(secondField.slice(-1)[0]);
}
function interpretSingleSecond(secondField) {
if (secondField === '*') {
return 'every second';
}
if (secondField === '0') {
return '';
}
return 'on second ' + getNumber(secondField);
}
// Minute field.
function interpretMinutes(cronPattern) {
return interpretMultipleMinutes(cronPattern.minute) ||
interpretRepeatingMinutes(cronPattern.minute) ||
interpretSingleMinute(cronPattern.minute);
}
function interpretMultipleMinutes(minuteField) {
if (!includes(minuteField, ',')) {
// Not a multiple minute pattern.
return;
}
minuteField = minuteField.split(',');
return 'on minutes ' +
minuteField.slice(0, -1).map(getNumber).join(', ') +
' and ' +
getNumber(minuteField.slice(-1)[0]);
}
function interpretRepeatingMinutes(secondField) {
if (!includes(secondField, '/')) {
// Not a repeating interval pattern.
return;
}
var result = 'every ';
secondField = secondField.split('/');
var start = secondField[0];
var interval = secondField[1];
if (interval > 1) {
result += getNumber(interval) + ' minutes';
}
else if (interval == 1 || interval == 0) {
result += 'minute';
}
if (start !== '*' && start !== '0') {
result += ' past minute ' + getNumber(start);
}
return result;
}
function interpretSingleMinute(minuteField) {
if (minuteField === '*') {
return 'every minute';
}
if (minuteField === '0') {
return '';
}
return 'on minute ' + getNumber(minuteField);
}
// Hour field.
function interpretHours(cronPattern) {
return interpretMultipleHours(cronPattern) ||
interpretRepeatingHours(cronPattern) ||
interpretSingleHour(cronPattern);
}
function interpretMultipleHours(cronPattern) {
var hourField = cronPattern.hour;
if (!includes(hourField, ',')) {
// Not a multiple minute pattern.
return;
}
hourField = hourField.split(',');
return 'on hours ' +
hourField.slice(0, -1).map(getNumber).join(', ') +
' and ' +
getNumber(hourField.slice(-1)[0]);
}
function interpretRepeatingHours(cronPattern) {
var hourField = cronPattern.hour;
if (!includes(hourField, '/')) {
// Not a repeating interval pattern.
return;
}
hourField = hourField.split('/');
var interval = hourField[1];
var result = 'every ';
if (interval > 1) {
result += getNumber(interval) + ' hours';
}
else if (interval == 1 || interval == 0) {
result += 'hour';
}
var start = hourField[0];
if (start !== '*' && start !== '0') {
result += ' past hour ' + getNumber(start);
}
return result;
}
function interpretSingleHour(cronPattern) {
if (cronPattern.hour === '*') {
return 'every hour';
}
var prefix = interpretWeekdays(cronPattern) || '';
if (prefix) {
prefix = 'every ' + prefix + ' ';
}
return prefix + 'at ' + getHour(cronPattern.hour);
}
// Date field.
function interpretDates(cronPattern) {
return getOrdinal(cronPattern);
}
// Month field.
function interpretMonths(cronPattern) {
return getMonth(cronPattern.month);
}
// Weekday field.
function interpretWeekdays(cronPattern) {
var weekdayField = cronPattern.weekday;
if (includes(weekdayField, '-')) {
return weekdayField.split('-').map(getWeekday).join('-');
}
else if (includes(weekdayField, ',')) {
weekdayField = weekdayField.split(',').map(getWeekday);
return weekdayField.slice(0, -1).join(', ') + ', and ' +
weekdayField[weekdayField.length - 1];
}
return getWeekday(cronPattern.weekday);
}
// Turn a simple hour field into 12-hour representation.
function getHour(h) {
if (!AMPM) {
return pad(h) + ':00';
}
if (h >= 12) {
return (h - 12 || h) + ':00 PM';
}
if (h >= 0) {
return (+h || 12) + ':00 AM';
}
throw new Error('Tried to interpret "' + JSON.stringify(h) +
'" as an hour and failed.');
}
// Get English number names for the integers zero through ten.
function getNumber(n) {
if (SHORT) {
return n;
}
return numbers[n] || n;
}
// Get English ordinals from integers.
function getOrdinal(n) {
if (SHORT) {
return getSuffixedOrdinal(n);
}
return ordinals[n] || getSuffixedOrdinal(n);
}
// Get suffixed ordinals from integers.
function getSuffixedOrdinal(n) {
var m = Math.abs(n);
var suffix = suffixes[m];
if (!suffix) {
m = (m % 100 - 20) % 10;
suffix = suffixes[m] || suffixes[0];
}
return n + suffix;
}
// Get English month names from a number or from an abbreviation.
function getMonth(m) {
var month = monthNames[m] || monthAbbreviations[m];
return month && month[SHORT ? 1 : 0];
}
// Get English weekday names from a number or from an abbreviation.
function getWeekday(d) {
var weekday = weekdayNames[d] || weekdayAbbreviations[d];
return weekday && weekday[SHORT ? 1 : 0];
}
// Stringify and add a zero pad to integers.
function pad(n) {
n = '' + n;
if (n.length < 2) {
n = '0' + n;
}
return n;
}
function includes(str, sub) {
str += '';
return str.indexOf(sub) !== -1;
}
/* global define */
if (typeof define === 'function' && define.amd) {
// Export in AMD.
define([], function() {
return cronli5;
});
}
else if (typeof exports === 'object') {
// Export in Node.js.
module.exports = cronli5;
}
else {
// Export in the browser.
root.cronli5 = cronli5;
}
}(this));