cron-builder.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. var DEFAULT_INTERVAL = ['*'];
  2. var CronValidator = (function() {
  3. /**
  4. * Contains the position-to-name mapping of the cron expression
  5. * @type {Object}
  6. * @const
  7. */
  8. var MeasureOfTimeMap = {
  9. 0: 'minute',
  10. 1: 'hour',
  11. 2: 'dayOfTheMonth',
  12. 3: 'month',
  13. 4: 'dayOfTheWeek'
  14. },
  15. /**
  16. * contains every permissible 'measureOfTime' string constant
  17. * @const
  18. * @type {Array}
  19. */
  20. MeasureOfTimeValues = Object.keys(MeasureOfTimeMap).map(function (key) {
  21. return MeasureOfTimeMap[key];
  22. });
  23. /**
  24. * validates a given cron expression (object) for length, then calls validateValue on each value
  25. * @param {!{
  26. minute: Array.string,
  27. hour: Array.string,
  28. dayOfTheMonth: Array.string,
  29. month: Array.string,
  30. dayOfTheWeek: Array.string,
  31. * }} expression - rich object containing the state of the cron expression
  32. * @throws {Error} if expression contains more than 5 keys
  33. */
  34. var validateExpression = function(expression) {
  35. // don't care if it's less than 5, we'll just set those to the default '*'
  36. if (Object.keys(expression).length > 5) {
  37. throw new Error('Invalid cron expression; limited to 5 values.');
  38. }
  39. for (var measureOfTime in expression) {
  40. if (expression.hasOwnProperty(measureOfTime)) {
  41. this.validateValue(measureOfTime, expression[measureOfTime]);
  42. }
  43. }
  44. },
  45. /**
  46. * validates a given cron expression (string) for length, then calls validateValue on each value
  47. * @param {!String} expression - an optionally empty string containing at most 5 space delimited expressions.
  48. * @throws {Error} if the string contains more than 5 space delimited parts.
  49. */
  50. validateString = function(expression) {
  51. var splitExpression = expression.split(' ');
  52. if (splitExpression.length > 5) {
  53. throw new Error('Invalid cron expression; limited to 5 values.');
  54. }
  55. for (var i = 0; i < splitExpression.length; i++) {
  56. this.validateValue(MeasureOfTimeMap[i], splitExpression[i]);
  57. }
  58. },
  59. /**
  60. * validates any given measureOfTime and corresponding value
  61. * @param {!String} measureOfTime - as expected
  62. * @param {!String} value - the cron-ish interval specifier
  63. * @throws {Error} if measureOfTime is bogus
  64. * @throws {Error} if value contains an unsupported character
  65. */
  66. validateValue = function(measureOfTime, value) {
  67. var validatorObj = {
  68. minute: {min: 0, max: 59},
  69. hour: {min: 0, max: 23},
  70. dayOfTheMonth: {min: 1, max: 31},
  71. month: {min: 1, max: 12},
  72. dayOfTheWeek: {min: 1, max: 7}
  73. },
  74. range,
  75. validChars = /^[0-9*-]/;
  76. if (!validatorObj[measureOfTime]) {
  77. throw new Error('Invalid measureOfTime; Valid options are: ' + MeasureOfTimeValues.join(', '));
  78. }
  79. if (!validChars.test(value)) {
  80. throw new Error('Invalid value; Only numbers 0-9, "-", and "*" chars are allowed');
  81. }
  82. if (value !== '*') {
  83. // check to see if value is within range if value is not '*'
  84. if (value.indexOf('-') >= 0) {
  85. // value is a range and must be split into high and low
  86. range = value.split('-');
  87. if (!range[0] || range[0] < validatorObj[measureOfTime].min) {
  88. throw new Error('Invalid value; bottom of range is not valid for "' + measureOfTime + '". Limit is ' + validatorObj[measureOfTime].min + '.');
  89. }
  90. if (!range[1] || range[1] > validatorObj[measureOfTime].max) {
  91. throw new Error('Invalid value; top of range is not valid for "' + measureOfTime + '". Limit is ' + validatorObj[measureOfTime].max + '.');
  92. }
  93. } else {
  94. if (parseInt(value) < validatorObj[measureOfTime].min) {
  95. throw new Error('Invalid value; given value is not valid for "' + measureOfTime + '". Minimum value is "' + validatorObj[measureOfTime].min + '".');
  96. }
  97. if (parseInt(value) > validatorObj[measureOfTime].max) {
  98. throw new Error('Invalid value; given value is not valid for "' + measureOfTime + '". Maximum value is "' + validatorObj[measureOfTime].max + '".');
  99. }
  100. }
  101. }
  102. };
  103. return {
  104. measureOfTimeValues: MeasureOfTimeValues,
  105. validateExpression: validateExpression,
  106. validateString: validateString,
  107. validateValue: validateValue
  108. }
  109. }());
  110. /**
  111. * Initializes a CronBuilder with an optional initial cron expression.
  112. * @param {String=} initialExpression - if provided, it must be up to 5 space delimited parts
  113. * @throws {Error} if the initialExpression is bogus
  114. * @constructor
  115. */
  116. var CronBuilder = (function() {
  117. function CronBuilder(initialExpression) {
  118. var splitExpression,
  119. expression;
  120. if (initialExpression) {
  121. CronValidator.validateString(initialExpression);
  122. splitExpression = initialExpression.split(' ');
  123. // check to see if initial expression is valid
  124. expression = {
  125. minute: splitExpression[0] ? [splitExpression[0]] : DEFAULT_INTERVAL,
  126. hour: splitExpression[1] ? [splitExpression[1]] : DEFAULT_INTERVAL,
  127. dayOfTheMonth: splitExpression[2] ? [splitExpression[2]] : DEFAULT_INTERVAL,
  128. month: splitExpression[3] ? [splitExpression[3]] : DEFAULT_INTERVAL,
  129. dayOfTheWeek: splitExpression[4] ? [splitExpression[4]] : DEFAULT_INTERVAL,
  130. };
  131. } else {
  132. expression = {
  133. minute: DEFAULT_INTERVAL,
  134. hour: DEFAULT_INTERVAL,
  135. dayOfTheMonth: DEFAULT_INTERVAL,
  136. month: DEFAULT_INTERVAL,
  137. dayOfTheWeek: DEFAULT_INTERVAL,
  138. };
  139. }
  140. /**
  141. * builds a working cron expression based on the state of the cron object
  142. * @returns {string} - working cron expression
  143. */
  144. this.build = function () {
  145. return [
  146. expression.minute.join(','),
  147. expression.hour.join(','),
  148. expression.dayOfTheMonth.join(','),
  149. expression.month.join(','),
  150. expression.dayOfTheWeek.join(','),
  151. ].join(' ');
  152. };
  153. /**
  154. * adds a value to what exists currently (builds)
  155. * @param {!String} measureOfTime
  156. * @param {!Number} value
  157. * @throws {Error} if measureOfTime or value fail validation
  158. */
  159. this.addValue = function (measureOfTime, value) {
  160. CronValidator.validateValue(measureOfTime, value);
  161. if (expression[measureOfTime].length === 1 && expression[measureOfTime][0] === '*') {
  162. expression[measureOfTime] = [value];
  163. } else {
  164. if (expression[measureOfTime].indexOf(value) < 0) {
  165. expression[measureOfTime].push(value);
  166. }
  167. }
  168. };
  169. /**
  170. * removes a single explicit value (subtracts)
  171. * @param {!String} measureOfTime - as you might guess
  172. * @param {!String} value - the offensive value
  173. * @throws {Error} if measureOfTime is bogus.
  174. */
  175. this.removeValue = function (measureOfTime, value) {
  176. if (!expression[measureOfTime]) {
  177. throw new Error('Invalid measureOfTime: Valid options are: ' + CronValidator.measureOfTimeValues.join(', '));
  178. }
  179. if (expression[measureOfTime].length === 1 && expression[measureOfTime][0] === '*') {
  180. return 'The value for "' + measureOfTime + '" is already at the default value of "*" - this is a no-op.';
  181. }
  182. expression[measureOfTime] = expression[measureOfTime].filter(function (timeValue) {
  183. return value !== timeValue;
  184. });
  185. if (!expression[measureOfTime].length) {
  186. expression[measureOfTime] = DEFAULT_INTERVAL;
  187. }
  188. };
  189. /**
  190. * returns the current state of a given measureOfTime
  191. * @param {!String} measureOfTime one of "minute", "hour", etc
  192. * @returns {!String} comma separated blah blah
  193. * @throws {Error} if the measureOfTime is not one of the permitted values.
  194. */
  195. this.get = function (measureOfTime) {
  196. if (!expression[measureOfTime]) {
  197. throw new Error('Invalid measureOfTime: Valid options are: ' + CronValidator.measureOfTimeValues.join(', '));
  198. }
  199. return expression[measureOfTime].join(',');
  200. };
  201. /**
  202. * sets the state of a given measureOfTime
  203. * @param {!String} measureOfTime - yup
  204. * @param {!Array.<String>} value - the 5 tuple array of values to set
  205. * @returns {!String} the comma separated version of the value that you passed in
  206. * @throws {Error} if your "value" is not an Array&lt;String&gt;
  207. * @throws {Error} when any item in your value isn't a legal cron-ish descriptor
  208. */
  209. this.set = function (measureOfTime, value) {
  210. if (!Array.isArray(value)) {
  211. throw new Error('Invalid value; Value must be in the form of an Array.');
  212. }
  213. for(var i = 0; i < value.length; i++) {
  214. CronValidator.validateValue(measureOfTime, value[i]);
  215. }
  216. expression[measureOfTime] = value;
  217. return expression[measureOfTime].join(',');
  218. };
  219. /**
  220. * Returns a rich object that describes the current state of the cron expression.
  221. * @returns {!{
  222. minute: Array.string,
  223. hour: Array.string,
  224. dayOfTheMonth: Array.string,
  225. month: Array.string,
  226. dayOfTheWeek: Array.string,
  227. * }}
  228. */
  229. this.getAll = function () {
  230. return expression;
  231. };
  232. /**
  233. * sets the state for the entire cron expression
  234. * @param {!{
  235. minute: Array.string,
  236. hour: Array.string,
  237. dayOfTheMonth: Array.string,
  238. month: Array.string,
  239. dayOfTheWeek: Array.string,
  240. * }} expToSet - the entirety of the cron expression.
  241. * @throws {Error} as usual
  242. */
  243. this.setAll = function (expToSet) {
  244. CronValidator.validateExpression(expToSet);
  245. expression = expToSet;
  246. };
  247. }
  248. return CronBuilder;
  249. }());
  250. module.exports = CronBuilder;