expression.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344
  1. var test = require('tap').test;
  2. var CronExpression = require('../lib/expression');
  3. var CronDate = require('../lib/date');
  4. test('empty expression test', function(t) {
  5. try {
  6. var interval = CronExpression.parse('');
  7. t.ok(interval, 'Interval parsed');
  8. var date = new CronDate();
  9. date.addMinute();
  10. var next = interval.next();
  11. t.ok(next, 'Found next scheduled interval');
  12. t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  13. t.end();
  14. } catch (err) {
  15. t.ifError(err, 'Interval parse error');
  16. }
  17. });
  18. test('default expression test', function(t) {
  19. try {
  20. var interval = CronExpression.parse('* * * * *');
  21. t.ok(interval, 'Interval parsed');
  22. var date = new CronDate();
  23. date.addMinute();
  24. var next = interval.next();
  25. t.ok(next, 'Found next scheduled interval');
  26. t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  27. } catch (err) {
  28. t.ifError(err, 'Interval parse error');
  29. }
  30. t.end();
  31. });
  32. test('default expression (tab separate) test', function(t) {
  33. try {
  34. var interval = CronExpression.parse('* * * * *');
  35. t.ok(interval, 'Interval parsed');
  36. var date = new CronDate();
  37. date.addMinute();
  38. var next = interval.next();
  39. t.ok(next, 'Found next scheduled interval');
  40. t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  41. } catch (err) {
  42. t.ifError(err, 'Interval parse error');
  43. }
  44. t.end();
  45. });
  46. test('default expression (multi-space separated) test 1', function(t) {
  47. try {
  48. var interval = CronExpression.parse('* \t*\t\t *\t * \t\t*');
  49. t.ok(interval, 'Interval parsed');
  50. var date = new CronDate();
  51. date.addMinute();
  52. var next = interval.next();
  53. t.ok(next, 'Found next scheduled interval');
  54. t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  55. } catch (err) {
  56. t.ifError(err, 'Interval parse error');
  57. }
  58. t.end();
  59. });
  60. test('default expression (multi-space separated) test 1', function(t) {
  61. try {
  62. var interval = CronExpression.parse('* \t *\t \t * * \t \t *');
  63. t.ok(interval, 'Interval parsed');
  64. var date = new CronDate();
  65. date.addMinute();
  66. var next = interval.next();
  67. t.ok(next, 'Found next scheduled interval');
  68. t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  69. } catch (err) {
  70. t.ifError(err, 'Interval parse error');
  71. }
  72. t.end();
  73. });
  74. test('value out of the range', function(t) {
  75. t.throws(function() {
  76. CronExpression.parse('61 * * * * *');
  77. }, new Error('Constraint error, got value 61 expected range 0-59'));
  78. t.end();
  79. });
  80. test('second value out of the range', function(t) {
  81. t.throws(function() {
  82. CronExpression.parse('-1 * * * * *');
  83. }, new Error('Constraint error, got value -1 expected range 0-59'));
  84. t.end();
  85. });
  86. test('invalid range', function(t) {
  87. t.throws(function() {
  88. CronExpression.parse('- * * * * *');
  89. }, new Error('Invalid range: -'));
  90. t.end();
  91. });
  92. test('minute value out of the range', function(t) {
  93. t.throws(function() {
  94. CronExpression.parse('* 32,72 * * * *');
  95. }, new Error('Constraint error, got value 72 expected range 0-59'));
  96. t.end();
  97. });
  98. test('hour value out of the range', function(t) {
  99. t.throws(function() {
  100. CronExpression.parse('* * 12-36 * * *');
  101. }, new Error('Constraint error, got range 12-36 expected range 0-23'));
  102. t.end();
  103. });
  104. test('day of the month value out of the range', function(t) {
  105. t.throws(function() {
  106. CronExpression.parse('* * * 10-15,40 * *');
  107. }, ('Constraint error, got value 40 expected range 1-31'));
  108. t.end();
  109. });
  110. test('month value out of the range', function(t) {
  111. t.throws(function() {
  112. CronExpression.parse('* * * * */10,12-13 *');
  113. }, new Error('Constraint error, got range 12-13 expected range 1-12'));
  114. t.end();
  115. });
  116. test('day of the week value out of the range', function(t) {
  117. t.throws(function() {
  118. CronExpression.parse('* * * * * 9');
  119. }, new Error('Constraint error, got value 9 expected range 0-7'));
  120. t.end();
  121. });
  122. test('invalid expression that contains too many fields', function (t) {
  123. t.throws(function() {
  124. CronExpression.parse('* * * * * * * *ASD');
  125. }, new Error('Invalid cron expression'));
  126. t.end();
  127. });
  128. test('invalid explicit day of month definition', function(t) {
  129. t.throws(function() {
  130. const iter = CronExpression.parse('0 0 31 4 *');
  131. iter.next();
  132. }, new Error('Invalid explicit day of month definition'));
  133. t.end();
  134. });
  135. test('incremental minutes expression test', function(t) {
  136. try {
  137. var interval = CronExpression.parse('*/3 * * * *');
  138. t.ok(interval, 'Interval parsed');
  139. var next = interval.next();
  140. t.ok(next, 'Found next scheduled interval');
  141. t.equal(next.getMinutes() % 3, 0, 'Schedule matches');
  142. } catch (err) {
  143. t.ifError(err, 'Interval parse error');
  144. }
  145. t.end();
  146. });
  147. test('fixed expression test', function(t) {
  148. try {
  149. var interval = CronExpression.parse('10 2 12 8 0');
  150. t.ok(interval, 'Interval parsed');
  151. var next = interval.next();
  152. t.ok(next, 'Found next scheduled interval');
  153. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of Month matches');
  154. t.equal(next.getMonth(), 7, 'Month matches');
  155. t.equal(next.getHours(), 2, 'Hour matches');
  156. t.equal(next.getMinutes(), 10, 'Minute matches');
  157. } catch (err) {
  158. t.ifError(err, 'Interval parse error');
  159. }
  160. t.end();
  161. });
  162. test('invalid characters test - symbol', function(t) {
  163. t.throws(function() {
  164. CronExpression.parse('10 ! 12 8 0');
  165. }, new Error('Invalid characters, got value: !'));
  166. t.end();
  167. });
  168. test('invalid characters test - letter', function(t) {
  169. t.throws(function() {
  170. CronExpression.parse('10 x 12 8 0');
  171. }, new Error('Invalid characters, got value: x'));
  172. t.end();
  173. });
  174. test('invalid characters test - parentheses', function(t) {
  175. t.throws(function() {
  176. CronExpression.parse('10 ) 12 8 0');
  177. }, new Error('Invalid characters, got value: )'));
  178. t.end();
  179. });
  180. test('interval with invalid characters test', function(t) {
  181. t.throws(function() {
  182. CronExpression.parse('10 */A 12 8 0');
  183. }, new Error('Invalid characters, got value: */A'));
  184. t.end();
  185. });
  186. test('range with invalid characters test', function(t) {
  187. t.throws(function() {
  188. CronExpression.parse('10 0-z 12 8 0');
  189. }, new Error('Invalid characters, got value: 0-z'));
  190. t.end();
  191. });
  192. test('group with invalid characters test', function(t) {
  193. t.throws(function() {
  194. CronExpression.parse('10 0,1,z 12 8 0');
  195. }, new Error('Invalid characters, got value: 0,1,z'));
  196. t.end();
  197. });
  198. test('invalid expression which has repeat 0 times', function(t) {
  199. t.throws(function() {
  200. CronExpression.parse('0 */0 * * *');
  201. }, new Error('Constraint error, cannot repeat at every 0 time.'));
  202. t.end();
  203. });
  204. test('invalid expression which has repeat negative number times', function(t) {
  205. t.throws(function() {
  206. CronExpression.parse('0 */-5 * * *');
  207. }, new Error('Constraint error, cannot repeat at every -5 time.'));
  208. t.end();
  209. });
  210. test('range test with iterator', function(t) {
  211. try {
  212. var interval = CronExpression.parse('10-30 2 12 8 0');
  213. t.ok(interval, 'Interval parsed');
  214. var intervals = interval.iterate(20);
  215. t.ok(intervals, 'Found intervals');
  216. for (var i = 0, c = intervals.length; i < c; i++) {
  217. var next = intervals[i];
  218. t.ok(next, 'Found next scheduled interval');
  219. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  220. t.equal(next.getMonth(), 7, 'Month matches');
  221. t.equal(next.getHours(), 2, 'Hour matches');
  222. t.equal(next.getMinutes(), 10 + i, 'Minute matches');
  223. }
  224. } catch (err) {
  225. t.ifError(err, 'Interval parse error');
  226. }
  227. t.end();
  228. });
  229. test('incremental range test with iterator', function(t) {
  230. try {
  231. var interval = CronExpression.parse('10-30/2 2 12 8 0');
  232. t.ok(interval, 'Interval parsed');
  233. var intervals = interval.iterate(10);
  234. t.ok(intervals, 'Found intervals');
  235. for (var i = 0, c = intervals.length; i < c; i++) {
  236. var next = intervals[i];
  237. t.ok(next, 'Found next scheduled interval');
  238. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  239. t.equal(next.getMonth(), 7, 'Month matches');
  240. t.equal(next.getHours(), 2, 'Hour matches');
  241. t.equal(next.getMinutes(), 10 + (i * 2), 'Minute matches');
  242. }
  243. } catch (err) {
  244. t.ifError(err, 'Interval parse error');
  245. }
  246. t.end();
  247. });
  248. test('predefined expression', function(t) {
  249. try {
  250. var interval = CronExpression.parse('@yearly');
  251. t.ok(interval, 'Interval parsed');
  252. var date = new CronDate();
  253. date.addYear();
  254. var next = interval.next();
  255. t.ok(next, 'Found next scheduled interval');
  256. t.equal(next.getFullYear(), date.getFullYear(), 'Year matches');
  257. } catch (err) {
  258. t.ifError(err, 'Interval parse error');
  259. }
  260. t.end();
  261. });
  262. test('expression limited with start and end date', function(t) {
  263. try {
  264. var options = {
  265. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53'),
  266. startDate: new CronDate('Wed, 26 Dec 2012 12:40:00'),
  267. endDate: new CronDate('Wed, 26 Dec 2012 16:40:00')
  268. };
  269. var interval = CronExpression.parse('*/20 * * * *', options);
  270. t.ok(interval, 'Interval parsed');
  271. var dates = interval.iterate(10);
  272. t.equal(dates.length, 7, 'Dates count matches for positive iteration');
  273. interval.reset();
  274. var dates = interval.iterate(-10);
  275. t.equal(dates.length, 6, 'Dates count matches for negative iteration');
  276. interval.reset();
  277. // Forward iteration
  278. var next = interval.next();
  279. t.equal(next.getHours(), 14, 'Hour matches');
  280. t.equal(next.getMinutes(), 40, 'Minute matches');
  281. next = interval.next();
  282. t.equal(next.getHours(), 15, 'Hour matches');
  283. t.equal(next.getMinutes(), 0, 'Minute matches');
  284. next = interval.next();
  285. t.equal(next.getHours(), 15, 'Hour matches');
  286. t.equal(next.getMinutes(), 20, 'Minute matches');
  287. next = interval.next();
  288. t.equal(next.getHours(), 15, 'Hour matches');
  289. t.equal(next.getMinutes(), 40, 'Minute matches');
  290. next = interval.next();
  291. t.equal(next.getHours(), 16, 'Hour matches');
  292. t.equal(next.getMinutes(), 0, 'Minute matches');
  293. next = interval.next();
  294. t.equal(next.getHours(), 16, 'Hour matches');
  295. t.equal(next.getMinutes(), 20, 'Minute matches');
  296. next = interval.next();
  297. t.equal(next.getHours(), 16, 'Hour matches');
  298. t.equal(next.getMinutes(), 40, 'Minute matches');
  299. try {
  300. interval.next();
  301. t.ok(false, 'Should fail');
  302. } catch (e) {
  303. t.ok(true, 'Failed as expected');
  304. }
  305. next = interval.prev();
  306. t.equal(next.getHours(), 16, 'Hour matches');
  307. t.equal(next.getMinutes(), 20, 'Minute matches');
  308. interval.reset();
  309. // Backward iteration
  310. var prev = interval.prev();
  311. t.equal(prev.getHours(), 14, 'Hour matches');
  312. t.equal(prev.getMinutes(), 20, 'Minute matches');
  313. prev = interval.prev();
  314. t.equal(prev.getHours(), 14, 'Hour matches');
  315. t.equal(prev.getMinutes(), 0, 'Minute matches');
  316. prev = interval.prev();
  317. t.equal(prev.getHours(), 13, 'Hour matches');
  318. t.equal(prev.getMinutes(), 40, 'Minute matches');
  319. prev = interval.prev();
  320. t.equal(prev.getHours(), 13, 'Hour matches');
  321. t.equal(prev.getMinutes(), 20, 'Minute matches');
  322. prev = interval.prev();
  323. t.equal(prev.getHours(), 13, 'Hour matches');
  324. t.equal(prev.getMinutes(), 0, 'Minute matches');
  325. prev = interval.prev();
  326. t.equal(prev.getHours(), 12, 'Hour matches');
  327. t.equal(prev.getMinutes(), 40, 'Minute matches');
  328. try {
  329. interval.prev();
  330. t.ok(false, 'Should fail');
  331. } catch (e) {
  332. t.ok(true, 'Failed as expected');
  333. }
  334. } catch (err) {
  335. t.ifError(err, 'Interval parse error');
  336. }
  337. t.end();
  338. });
  339. test('reset to given date', function(t){
  340. try {
  341. var options = {
  342. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53')
  343. };
  344. var interval = CronExpression.parse('*/20 * * * *', options);
  345. t.ok(interval, 'Interval parsed');
  346. // Forward iteration
  347. var next = interval.next();
  348. t.equal(next.getHours(), 14, 'Hour matches');
  349. t.equal(next.getMinutes(), 40, 'Minute matches');
  350. interval.reset(); // defaults to initial currentDate
  351. next = interval.next();
  352. t.equal(next.getHours(), 14, 'Hour matches');
  353. t.equal(next.getMinutes(), 40, 'Minute matches');
  354. interval.reset(new CronDate('Wed, 26 Dec 2012 17:23:53'));
  355. next = interval.next();
  356. t.equal(next.getHours(), 17, 'Hour matches');
  357. t.equal(next.getMinutes(), 40, 'Minute matches');
  358. next = interval.next();
  359. t.equal(next.getHours(), 18, 'Hour matches');
  360. t.equal(next.getMinutes(), 0, 'Minute matches');
  361. interval.reset(new Date('2019-06-18T08:18:36.000'));
  362. next = interval.prev();
  363. t.equal(next.getDate(), 18, 'Date matches');
  364. t.equal(next.getHours(), 8, 'Hour matches');
  365. t.equal(next.getMinutes(), 0, 'Minute matches');
  366. next = interval.prev();
  367. t.equal(next.getDate(), 18, 'Date matches');
  368. t.equal(next.getHours(), 7, 'Hour matches');
  369. t.equal(next.getMinutes(), 40, 'Minute matches');
  370. t.end();
  371. } catch (err) {
  372. t.ifError(err, 'Reset error');
  373. }
  374. });
  375. test('parse expression as UTC', function(t) {
  376. try {
  377. var options = {
  378. utc: true
  379. };
  380. var interval = CronExpression.parse('0 0 10 * * *', options);
  381. var date = interval.next();
  382. t.equal(date.getUTCHours(), 10, 'Correct UTC hour value');
  383. t.equal(date.getHours(), 10, 'Correct UTC hour value');
  384. interval = CronExpression.parse('0 */5 * * * *', options);
  385. var date = interval.next(), now = new Date();
  386. now.setMinutes(now.getMinutes() + 5 - (now.getMinutes() % 5));
  387. t.equal(date.getHours(), now.getUTCHours(), 'Correct local time for 5 minute interval');
  388. } catch (err) {
  389. t.ifError(err, 'UTC parse error');
  390. }
  391. t.end();
  392. });
  393. test('expression using days of week strings', function(t) {
  394. try {
  395. var interval = CronExpression.parse('15 10 * * MON-TUE');
  396. t.ok(interval, 'Interval parsed');
  397. var intervals = interval.iterate(8);
  398. t.ok(intervals, 'Found intervals');
  399. for (var i = 0, c = intervals.length; i < c; i++) {
  400. var next = intervals[i];
  401. var day = next.getDay();
  402. t.ok(next, 'Found next scheduled interval');
  403. t.ok(day == 1 || day == 2, 'Day matches')
  404. t.equal(next.getHours(), 10, 'Hour matches');
  405. t.equal(next.getMinutes(), 15, 'Minute matches');
  406. }
  407. } catch (err) {
  408. t.ifError(err, 'Interval parse error');
  409. }
  410. t.end();
  411. });
  412. test('expression using mixed days of week strings', function(t) {
  413. try {
  414. var options = {
  415. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53')
  416. };
  417. var interval = CronExpression.parse('15 10 * jAn-FeB mOn-tUE', options);
  418. t.ok(interval, 'Interval parsed');
  419. var intervals = interval.iterate(8);
  420. t.ok(intervals, 'Found intervals');
  421. for (var i = 0, c = intervals.length; i < c; i++) {
  422. var next = intervals[i];
  423. var day = next.getDay();
  424. var month = next.getMonth();
  425. t.ok(next, 'Found next scheduled interval');
  426. t.ok(month == 0 || month == 2, 'Month Matches');
  427. t.ok(day == 1 || day == 2, 'Day matches');
  428. t.equal(next.getHours(), 10, 'Hour matches');
  429. t.equal(next.getMinutes(), 15, 'Minute matches');
  430. }
  431. } catch (err) {
  432. t.ifError(err, 'Interval parse error');
  433. }
  434. t.end();
  435. });
  436. test('expression using non-standard second field (wildcard)', function(t) {
  437. try {
  438. var options = {
  439. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
  440. endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
  441. };
  442. var interval = CronExpression.parse('* * * * * *', options);
  443. t.ok(interval, 'Interval parsed');
  444. var intervals = interval.iterate(10);
  445. t.ok(intervals, 'Found intervals');
  446. for (var i = 0, c = intervals.length; i < c; i++) {
  447. var next = intervals[i];
  448. t.ok(next, 'Found next scheduled interval');
  449. t.equal(next.getSeconds(), i + 1, 'Second matches');
  450. }
  451. } catch (err) {
  452. t.ifError(err, 'Interval parse error');
  453. }
  454. t.end();
  455. });
  456. test('expression using non-standard second field (step)', function(t) {
  457. try {
  458. var options = {
  459. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
  460. endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
  461. };
  462. var interval = CronExpression.parse('*/20 * * * * *', options);
  463. t.ok(interval, 'Interval parsed');
  464. var intervals = interval.iterate(3);
  465. t.ok(intervals, 'Found intervals');
  466. t.equal(intervals[0].getSeconds(), 20, 'Second matches');
  467. t.equal(intervals[1].getSeconds(), 40, 'Second matches');
  468. t.equal(intervals[2].getSeconds(), 0, 'Second matches');
  469. } catch (err) {
  470. t.ifError(err, 'Interval parse error');
  471. }
  472. t.end();
  473. });
  474. test('expression using non-standard second field (range)', function(t) {
  475. try {
  476. var options = {
  477. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
  478. endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
  479. };
  480. var interval = CronExpression.parse('20-40/10 * * * * *', options);
  481. t.ok(interval, 'Interval parsed');
  482. var intervals = interval.iterate(3);
  483. t.ok(intervals, 'Found intervals');
  484. for (var i = 0, c = intervals.length; i < c; i++) {
  485. var next = intervals[i];
  486. t.ok(next, 'Found next scheduled interval');
  487. t.equal(next.getSeconds(), 20 + (i * 10), 'Second matches');
  488. }
  489. } catch (err) {
  490. t.ifError(err, 'Interval parse error');
  491. }
  492. t.end();
  493. });
  494. test('expression using explicit month definition and */5 day of month step', function(t) {
  495. var firstIterator = CronExpression.parse('0 12 */5 6 *', {
  496. currentDate: '2019-06-01T11:00:00.000'
  497. });
  498. var firstExpectedDates = [
  499. new CronDate('2019-06-01T12:00:00.000'),
  500. new CronDate('2019-06-06T12:00:00.000'),
  501. new CronDate('2019-06-11T12:00:00.000'),
  502. new CronDate('2019-06-16T12:00:00.000'),
  503. new CronDate('2019-06-21T12:00:00.000'),
  504. new CronDate('2019-06-26T12:00:00.000'),
  505. new CronDate('2020-06-01T12:00:00.000')
  506. ];
  507. firstExpectedDates.forEach(function(expectedDate) {
  508. t.equal(expectedDate.toISOString(), firstIterator.next().toISOString());
  509. });
  510. var secondIterator = CronExpression.parse('0 15 */5 5 *', {
  511. currentDate: '2019-05-01T11:00:00.000'
  512. });
  513. var secondExpectedDates = [
  514. new CronDate('2019-05-01T15:00:00.000'),
  515. new CronDate('2019-05-06T15:00:00.000'),
  516. new CronDate('2019-05-11T15:00:00.000'),
  517. new CronDate('2019-05-16T15:00:00.000'),
  518. new CronDate('2019-05-21T15:00:00.000'),
  519. new CronDate('2019-05-26T15:00:00.000'),
  520. new CronDate('2019-05-31T15:00:00.000'),
  521. new CronDate('2020-05-01T15:00:00.000')
  522. ];
  523. secondExpectedDates.forEach(function(expectedDate) {
  524. t.equal(expectedDate.toISOString(), secondIterator.next().toISOString());
  525. });
  526. t.end();
  527. });
  528. test('day of month and week are both set', function(t) {
  529. try {
  530. var interval = CronExpression.parse('10 2 12 8 0');
  531. t.ok(interval, 'Interval parsed');
  532. var next = interval.next();
  533. t.ok(next, 'Found next scheduled interval');
  534. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  535. t.equal(next.getMonth(), 7, 'Month matches');
  536. next = interval.next();
  537. t.ok(next, 'Found next scheduled interval');
  538. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  539. t.equal(next.getMonth(), 7, 'Month matches');
  540. next = interval.next();
  541. t.ok(next, 'Found next scheduled interval');
  542. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  543. t.equal(next.getMonth(), 7, 'Month matches');
  544. next = interval.next();
  545. t.ok(next, 'Found next scheduled interval');
  546. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  547. t.equal(next.getMonth(), 7, 'Month matches');
  548. } catch (err) {
  549. t.ifError(err, 'Interval parse error');
  550. }
  551. t.end();
  552. });
  553. test('day of month is unspecified', function(t) {
  554. try {
  555. var interval = CronExpression.parse('10 2 ? * 3');
  556. t.ok(interval, 'Interval parsed');
  557. var next = interval.next();
  558. t.ok(next, 'Found next scheduled interal');
  559. t.ok(next.getDay() === 3, 'day of week matches');
  560. next = interval.next();
  561. t.ok(next, 'Found next scheduled interal');
  562. t.ok(next.getDay() === 3, 'day of week matches');
  563. next = interval.next();
  564. t.ok(next, 'Found next scheduled interal');
  565. t.ok(next.getDay() === 3, 'day of week matches');
  566. next = interval.next();
  567. t.ok(next, 'Found next scheduled interal');
  568. t.ok(next.getDay() === 3, 'day of week matches');
  569. } catch (err) {
  570. t.ifError(err, 'Interval parse error');
  571. }
  572. t.end();
  573. });
  574. test('day of week is unspecified', function(t) {
  575. try {
  576. var interval = CronExpression.parse('10 2 3,6 * ?');
  577. t.ok(interval, 'Interval parsed');
  578. var next = interval.next();
  579. t.ok(next, 'Found next scheduled interal');
  580. t.ok(next.getDate() === 3 || next.getDate() === 6, 'date matches');
  581. var prevDate = next.getDate();
  582. next = interval.next();
  583. t.ok(next, 'Found next scheduled interal');
  584. t.ok((next.getDate() === 3 || next.getDate() === 6) &&
  585. next.getDate() !== prevDate, 'date matches and is not previous date');
  586. prevDate = next.getDate();
  587. next = interval.next();
  588. t.ok(next, 'Found next scheduled interal');
  589. t.ok((next.getDate() === 3 || next.getDate() === 6) &&
  590. next.getDate() !== prevDate, 'date matches and is not previous date');
  591. prevDate = next.getDate();
  592. next = interval.next();
  593. t.ok(next, 'Found next scheduled interal');
  594. t.ok((next.getDate() === 3 || next.getDate() === 6) &&
  595. next.getDate() !== prevDate, 'date matches and is not previous date');
  596. } catch (err) {
  597. t.ifError(err, 'Interval parse error');
  598. }
  599. t.end();
  600. });
  601. test('Summertime bug test', function(t) {
  602. try {
  603. var month = new CronDate().getMonth() + 1;
  604. var interval = CronExpression.parse('0 0 0 1 '+month+' *');
  605. t.ok(interval, 'Interval parsed');
  606. var next = interval.next();
  607. // Before fix the bug it was getting a timeout error if you are
  608. // in a timezone that changes the DST to ST in the hour 00:00h.
  609. t.ok(next, 'Found next scheduled interval');
  610. } catch (err) {
  611. t.ifError(err, 'Interval parse error');
  612. }
  613. t.end();
  614. });
  615. test('day of month and week are both set and dow is 7', function(t) {
  616. try {
  617. var interval = CronExpression.parse('10 2 12 8 7');
  618. t.ok(interval, 'Interval parsed');
  619. var next = interval.next();
  620. t.ok(next, 'Found next scheduled interval');
  621. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  622. t.equal(next.getMonth(), 7, 'Month matches');
  623. next = interval.next();
  624. t.ok(next, 'Found next scheduled interval');
  625. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  626. t.equal(next.getMonth(), 7, 'Month matches');
  627. next = interval.next();
  628. t.ok(next, 'Found next scheduled interval');
  629. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  630. t.equal(next.getMonth(), 7, 'Month matches');
  631. next = interval.next();
  632. t.ok(next, 'Found next scheduled interval');
  633. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  634. t.equal(next.getMonth(), 7, 'Month matches');
  635. } catch (err) {
  636. t.ifError(err, 'Interval parse error');
  637. }
  638. t.end();
  639. });
  640. test('day of month and week are both set and dow is 6,0', function(t) {
  641. try {
  642. var interval = CronExpression.parse('10 2 12 8 6,0');
  643. t.ok(interval, 'Interval parsed');
  644. var next = interval.next();
  645. t.ok(next, 'Found next scheduled interval');
  646. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  647. t.equal(next.getMonth(), 7, 'Month matches');
  648. next = interval.next();
  649. t.ok(next, 'Found next scheduled interval');
  650. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  651. t.equal(next.getMonth(), 7, 'Month matches');
  652. next = interval.next();
  653. t.ok(next, 'Found next scheduled interval');
  654. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  655. t.equal(next.getMonth(), 7, 'Month matches');
  656. next = interval.next();
  657. t.ok(next, 'Found next scheduled interval');
  658. t.ok(next.getDay() === 0 || next.getDate() === 12, 'Day or day of month matches');
  659. t.equal(next.getMonth(), 7, 'Month matches');
  660. } catch (err) {
  661. t.ifError(err, 'Interval parse error');
  662. }
  663. t.end();
  664. });
  665. test('day of month and week are both set and dow is 6-7', function(t) {
  666. try {
  667. var interval = CronExpression.parse('10 2 12 8 6-7');
  668. t.ok(interval, 'Interval parsed');
  669. var next = interval.next();
  670. t.ok(next, 'Found next scheduled interval');
  671. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  672. t.equal(next.getMonth(), 7, 'Month matches');
  673. next = interval.next();
  674. t.ok(next, 'Found next scheduled interval');
  675. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  676. t.equal(next.getMonth(), 7, 'Month matches');
  677. next = interval.next();
  678. t.ok(next, 'Found next scheduled interval');
  679. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  680. t.equal(next.getMonth(), 7, 'Month matches');
  681. // next = interval.next();
  682. t.ok(next, 'Found next scheduled interval');
  683. t.ok(next.getDay() === 6 || next.getDate() === 12, 'Day or day of month matches');
  684. t.equal(next.getMonth(), 7, 'Month matches');
  685. } catch (err) {
  686. t.ifError(err, 'Interval parse error');
  687. }
  688. t.end();
  689. });
  690. test('day of month validation should be ignored when day of month is wildcard and month is set', function (t) {
  691. try {
  692. var interval = CronExpression.parse('* * * * 2 *');
  693. t.ok(interval, 'Interval parsed');
  694. var next = interval.next();
  695. t.ok(next, 'Found next scheduled interval');
  696. t.equal(next.getHours(), 0, 'Hours matches');
  697. t.equal(next.getDate(), 1, 'Day of month matches');
  698. t.equal(next.getMonth() + 1, 2, 'Month matches');
  699. t.ok(next, 'Found next scheduled interval');
  700. } catch (err) {
  701. t.ifError(err, 'Interval parse error');
  702. }
  703. t.end();
  704. });
  705. test('day and date in week should matches', function(t){
  706. try {
  707. var interval = CronExpression.parse('0 1 1 1 * 1');
  708. t.ok(interval, 'Interval parsed');
  709. var next = interval.next();
  710. t.ok(next, 'Found next scheduled interval');
  711. t.equal(next.getHours(), 1, 'Hours matches');
  712. t.ok(next.getDay() === 1 || next.getDate() === 1, 'Day or day of month matches');
  713. next = interval.next();
  714. t.ok(next, 'Found next scheduled interval');
  715. t.equal(next.getHours(), 1, 'Hours matches');
  716. t.ok(next.getDay() === 1 || next.getDate() === 1, 'Day or day of month matches');
  717. next = interval.next();
  718. t.ok(next, 'Found next scheduled interval');
  719. t.equal(next.getHours(), 1, 'Hours matches');
  720. t.ok(next.getDay() === 1 || next.getDate() === 1, 'Day or day of month matches');
  721. } catch (err) {
  722. t.ifError(err, 'Interval parse error');
  723. }
  724. t.end();
  725. });
  726. test('valid ES6 iterator should be returned if iterator options is set to true', function(t) {
  727. try {
  728. var options = {
  729. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53'),
  730. endDate: new CronDate('Wed, 26 Dec 2012 15:40:00'),
  731. iterator: true
  732. };
  733. var val = null;
  734. var interval = CronExpression.parse('*/25 * * * *', options);
  735. t.ok(interval, 'Interval parsed');
  736. val = interval.next();
  737. t.ok(val, 'Next iteration resolved');
  738. t.ok(val.value, 'Iterator value is set');
  739. t.notOk(val.done, 'Iterator is not finished');
  740. val = interval.next();
  741. t.ok(val, 'Next iteration resolved');
  742. t.ok(val.value, 'Iterator value is set');
  743. t.notOk(val.done, 'Iterator is not finished');
  744. val = interval.next();
  745. t.ok(val, 'Next iteration resolved');
  746. t.ok(val.value, 'Iterator value is set');
  747. t.ok(val.done, 'Iterator is finished');
  748. } catch (err) {
  749. t.ifError(err, 'Interval parse error');
  750. }
  751. t.end();
  752. });
  753. test('dow 6,7 6,0 0,6 7,6 should be equivalent', function(t) {
  754. try {
  755. var options = {
  756. currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53'),
  757. };
  758. var expressions = [
  759. '30 16 * * 6,7',
  760. '30 16 * * 6,0',
  761. '30 16 * * 0,6',
  762. '30 16 * * 7,6'
  763. ];
  764. expressions.forEach(function(expression) {
  765. var interval = CronExpression.parse(expression, options);
  766. t.ok(interval, 'Interval parsed');
  767. var val = interval.next();
  768. t.equal(val.getDay(), 6, 'Day matches');
  769. val = interval.next();
  770. t.equal(val.getDay(), 0, 'Day matches');
  771. val = interval.next();
  772. t.equal(val.getDay(), 6, 'Day matches');
  773. });
  774. } catch (err) {
  775. t.ifError(err, 'Interval parse error');
  776. }
  777. t.end();
  778. });
  779. test('hour 0 9,11,1 * * * and 0 1,9,11 * * * should be equivalent', function(t) {
  780. try {
  781. var options = {
  782. currentDate: new CronDate('Wed, 26 Dec 2012 00:00:00'),
  783. };
  784. var expressions = [
  785. '0 9,11,1 * * *',
  786. '0 1,9,11 * * *'
  787. ];
  788. expressions.forEach(function(expression) {
  789. var interval = CronExpression.parse(expression, options);
  790. t.ok(interval, 'Interval parsed');
  791. var val = interval.next();
  792. t.equal(val.getHours(), 1, 'Hour matches');
  793. val = interval.next();
  794. t.equal(val.getHours(), 9, 'Hour matches');
  795. val = interval.next();
  796. t.equal(val.getHours(), 11, 'Hour matches');
  797. val = interval.next();
  798. t.equal(val.getHours(), 1, 'Hour matches');
  799. val = interval.next();
  800. t.equal(val.getHours(), 9, 'Hour matches');
  801. val = interval.next();
  802. t.equal(val.getHours(), 11, 'Hour matches');
  803. });
  804. } catch (err) {
  805. t.ifError(err, 'Interval parse error');
  806. }
  807. t.end();
  808. });
  809. test('it will work with #139 issue case', function(t) {
  810. var options = {
  811. currentDate : new Date('2018-11-15T16:15:33.522Z'),
  812. tz: 'Europe/Madrid'
  813. };
  814. var interval = CronExpression.parse('0 0 0 1,2 * *', options);
  815. var date = interval.next();
  816. t.equal(date.getFullYear(), 2018);
  817. t.equal(date.getDate(), 1);
  818. t.equal(date.getMonth(), 11);
  819. t.end();
  820. });
  821. test('should work for valid first/second/third/fourth/fifth occurence dayOfWeek (# char)', function(t) {
  822. try {
  823. var options = {
  824. currentDate: new CronDate('2019-04-30')
  825. };
  826. var expectedFirstDates = [
  827. new CronDate('2019-05-05'),
  828. new CronDate('2019-06-02'),
  829. new CronDate('2019-07-07'),
  830. new CronDate('2019-08-04')
  831. ];
  832. var expectedSecondDates = [
  833. new CronDate('2019-05-12'),
  834. new CronDate('2019-06-9'),
  835. new CronDate('2019-07-14'),
  836. new CronDate('2019-08-11')
  837. ];
  838. var expectedThirdDates = [
  839. new CronDate('2019-05-19'),
  840. new CronDate('2019-06-16'),
  841. new CronDate('2019-07-21'),
  842. new CronDate('2019-08-18')
  843. ];
  844. var expectedFourthDates = [
  845. new CronDate('2019-05-26'),
  846. new CronDate('2019-06-23'),
  847. new CronDate('2019-07-28'),
  848. new CronDate('2019-08-25')
  849. ];
  850. var expectedFifthDates = [
  851. new CronDate('2019-6-30'),
  852. new CronDate('2019-9-29'),
  853. new CronDate('2019-12-29'),
  854. new CronDate('2020-03-29')
  855. ];
  856. var allExpectedDates = [
  857. expectedFirstDates,
  858. expectedSecondDates,
  859. expectedThirdDates,
  860. expectedFourthDates,
  861. expectedFifthDates
  862. ];
  863. var expressions = [
  864. '0 0 0 ? * 0#1',
  865. '0 0 0 ? * 0#2',
  866. '0 0 0 ? * 0#3',
  867. '0 0 0 ? * 0#4',
  868. '0 0 0 ? * 0#5'
  869. ];
  870. expressions.forEach(function(expression, index) {
  871. var interval = CronExpression.parse(expression, options);
  872. var expectedDates = allExpectedDates[index];
  873. expectedDates.forEach(function(expected) {
  874. var date = interval.next();
  875. t.equal(
  876. date.toISOString(),
  877. expected.toISOString(),
  878. 'Expression "' + expression + '" has next() that matches expected: ' + expected.toISOString()
  879. );
  880. });
  881. expectedDates
  882. .slice(0, expectedDates.length - 1)
  883. .reverse()
  884. .forEach(function(expected) {
  885. var date = interval.prev();
  886. t.equal(
  887. date.toISOString(),
  888. expected.toISOString(),
  889. 'Expression "' + expression + '" has prev() that matches expected: ' + expected.toISOString()
  890. );
  891. });
  892. });
  893. } catch (err) {
  894. t.ifError(err, 'Interval parse error');
  895. }
  896. t.end();
  897. });
  898. test('should work for valid second sunday in may', function(t) {
  899. try {
  900. var options = {
  901. currentDate: new CronDate('2019-01-30')
  902. };
  903. var expectedDates = [
  904. new CronDate('2019-05-12'),
  905. new CronDate('2020-05-10'),
  906. new CronDate('2021-05-09'),
  907. new CronDate('2022-05-08')
  908. ];
  909. var interval = CronExpression.parse('0 0 0 ? MAY 0#2', options);
  910. expectedDates.forEach(function(expected) {
  911. var date = interval.next();
  912. t.equal(
  913. date.toISOString(),
  914. expected.toISOString(),
  915. 'Expression "0 0 0 ? MAY 0#2" has next() that matches expected: ' + expected.toISOString()
  916. );
  917. });
  918. expectedDates
  919. .slice(0, expectedDates.length - 1)
  920. .reverse()
  921. .forEach(function(expected) {
  922. var date = interval.prev();
  923. t.equal(
  924. date.toISOString(),
  925. expected.toISOString(),
  926. 'Expression "0 0 0 ? MAY 0#2" has prev() that matches expected: ' + expected.toISOString()
  927. );
  928. });
  929. } catch (err) {
  930. t.ifError(err, 'Interval parse error');
  931. }
  932. t.end();
  933. });
  934. test('should work for valid second sunday at noon in may', function(t) {
  935. try {
  936. var options = {
  937. currentDate: new CronDate('2019-05-12T11:59:00.000')
  938. };
  939. var expected = new CronDate('2019-05-12T12:00:00.000');
  940. var interval = CronExpression.parse('0 0 12 ? MAY 0#2', options);
  941. var date = interval.next();
  942. t.equal(
  943. date.toISOString(),
  944. expected.toISOString(),
  945. 'Expression "0 0 12 ? MAY 0#2" has next() that matches expected: ' + expected.toISOString()
  946. );
  947. } catch (err) {
  948. t.ifError(err, 'Interval parse error');
  949. }
  950. t.end();
  951. });
  952. test('should work for valid second sunday at noon in may (UTC+3)', function(t) {
  953. try {
  954. var options = {
  955. currentDate: new CronDate('2019-05-12T11:59:00.000', 'Europe/Sofia')
  956. };
  957. var expected = new CronDate('2019-05-12T12:00:00.000', 'Europe/Sofia');
  958. var interval = CronExpression.parse('0 0 12 ? MAY 0#2', options);
  959. var date = interval.next();
  960. t.equal(
  961. date.toISOString(),
  962. expected.toISOString(),
  963. 'Expression "0 0 12 ? MAY 0#2" has next() that matches expected: ' + expected.toISOString()
  964. );
  965. } catch (err) {
  966. t.ifError(err, 'Interval parse error');
  967. }
  968. t.end();
  969. });
  970. test('should work with both dayOfMonth and nth occurence of dayOfWeek', function(t) {
  971. try {
  972. var options = {
  973. currentDate: new CronDate('2019-04-01')
  974. };
  975. var expectedDates = [
  976. new CronDate('2019-04-16'),
  977. new CronDate('2019-04-17'),
  978. new CronDate('2019-04-18'),
  979. new CronDate('2019-05-15'),
  980. new CronDate('2019-05-16'),
  981. new CronDate('2019-05-18'),
  982. ];
  983. var interval = CronExpression.parse('0 0 0 16,18 * 3#3', options);
  984. expectedDates.forEach(function(expected) {
  985. var date = interval.next();
  986. t.equal(
  987. date.toISOString(),
  988. expected.toISOString(),
  989. 'Expression "0 0 0 16,18 * 3#3" has next() that matches expected: ' + expected.toISOString()
  990. );
  991. });
  992. expectedDates
  993. .slice(0, expectedDates.length - 1)
  994. .reverse()
  995. .forEach(function(expected) {
  996. var date = interval.prev();
  997. t.equal(
  998. date.toISOString(),
  999. expected.toISOString(),
  1000. 'Expression "0 0 0 16,18 * 3#3" has prev() that matches expected: ' + expected.toISOString()
  1001. );
  1002. });
  1003. } catch (err) {
  1004. t.ifError(err, 'Interval parse error');
  1005. }
  1006. t.end();
  1007. });
  1008. test('should error when passed invalid occurence value', function(t) {
  1009. var expressions = [
  1010. '0 0 0 ? * 1#',
  1011. '0 0 0 ? * 1#0',
  1012. '0 0 0 ? * 4#6',
  1013. '0 0 0 ? * 0##4',
  1014. ];
  1015. expressions.forEach(function(expression) {
  1016. t.throws(function() {
  1017. CronExpression.parse(expression);
  1018. }, new Error('Constraint error, invalid dayOfWeek occurrence number (#)'), expression);
  1019. });
  1020. t.end();
  1021. });
  1022. // The Quartz documentation says that if the # character is used then no other expression can be used in the dayOfWeek term: http://www.quartz-scheduler.org/api/2.3.0/index.html
  1023. test('cannot combine `-` range and # occurrence special characters', function(t) {
  1024. var expression = '0 0 0 ? * 2-4#2';
  1025. t.throws(function() {
  1026. CronExpression.parse(expression);
  1027. }, new Error('Constraint error, invalid dayOfWeek `#` and `-` special characters are incompatible'));
  1028. t.end();
  1029. });
  1030. test('cannot combine `/` repeat interval and # occurrence special characters', function(t) {
  1031. var expression = '0 0 0 ? * 1/2#3';
  1032. t.throws(function() {
  1033. CronExpression.parse(expression);
  1034. }, new Error('Constraint error, invalid dayOfWeek `#` and `/` special characters are incompatible'));
  1035. t.end();
  1036. });
  1037. test('cannot combine `,` list and # occurrence special characters', function(t) {
  1038. var expression = '0 0 0 ? * 0,6#4';
  1039. t.throws(function() {
  1040. CronExpression.parse(expression);
  1041. }, new Error('Constraint error, invalid dayOfWeek `#` and `,` special characters are incompatible'));
  1042. t.end();
  1043. });