cronTrigger.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * This is the trigger used to decode the cronTimer and calculate the next excution time of the cron Trigger.
  3. */
  4. var logger = require('log4js').getLogger(__filename);
  5. var SECOND = 0;
  6. var MIN = 1;
  7. var HOUR = 2;
  8. var DOM = 3;
  9. var MONTH = 4;
  10. var DOW = 5;
  11. var Limit = [[0,59],[0,59],[0,24],[1,31],[0,11],[0,6]];
  12. /**
  13. * The constructor of the CronTrigger
  14. * @param trigger The trigger str used to build the cronTrigger instance
  15. */
  16. var CronTrigger = function(trigger, job){
  17. this.trigger = this.decodeTrigger(trigger);
  18. this.nextTime = this.nextExcuteTime(Date.now());
  19. this.job = job;
  20. };
  21. var pro = CronTrigger.prototype;
  22. /**
  23. * Get the current excuteTime of trigger
  24. */
  25. pro.excuteTime = function(){
  26. return this.nextTime;
  27. };
  28. /**
  29. * Caculate the next valid cronTime after the given time
  30. * @param The given time point
  31. * @return The nearest valid time after the given time point
  32. */
  33. pro.nextExcuteTime = function(time){
  34. //add 1s to the time so it must be the next time
  35. time = !!time?time:this.nextTime;
  36. time += 1000;
  37. var cronTrigger = this.trigger;
  38. var date = new Date(time);
  39. date.setMilliseconds(0);
  40. outmost:
  41. while(true){
  42. if(date.getFullYear() > 2999){
  43. logger.error("Can't compute the next time, exceed the limit");
  44. return null;
  45. }
  46. if(!timeMatch(date.getMonth(), cronTrigger[MONTH])){
  47. var nextMonth = nextCronTime(date.getMonth(), cronTrigger[MONTH]);
  48. if(nextMonth == null)
  49. return null;
  50. if(nextMonth <= date.getMonth()){
  51. date.setYear(date.getFullYear() + 1);
  52. date.setMonth(0);
  53. date.setDate(1);
  54. date.setHours(0);
  55. date.setMinutes(0);
  56. date.setSeconds(0);
  57. continue;
  58. }
  59. date.setMonth(nextMonth);
  60. date.setDate(1);
  61. date.setHours(0);
  62. date.setMinutes(0);
  63. date.setSeconds(0);
  64. }
  65. if(!timeMatch(date.getDate(), cronTrigger[DOM]) || !timeMatch(date.getDay(), cronTrigger[DOW])){
  66. var domLimit = getDomLimit(date.getFullYear(), date.getMonth());
  67. do{
  68. var nextDom = nextCronTime(date.getDate(), cronTrigger[DOM]);
  69. if(nextDom == null)
  70. return null;
  71. //If the date is in the next month, add month
  72. if(nextDom <= date.getDate() || nextDom > domLimit){
  73. date.setMonth(date.getMonth() + 1);
  74. date.setDate(1);
  75. date.setHours(0);
  76. date.setMinutes(0);
  77. date.setSeconds(0);
  78. continue outmost;
  79. }
  80. date.setDate(nextDom);
  81. }while(!timeMatch(date.getDay(), cronTrigger[DOW]));
  82. date.setHours(0);
  83. date.setMinutes(0);
  84. date.setSeconds(0);
  85. }
  86. if(!timeMatch(date.getHours(), cronTrigger[HOUR])){
  87. var nextHour = nextCronTime(date.getHours(), cronTrigger[HOUR]);
  88. if(nextHour <= date.getHours()){
  89. date.setDate(date.getDate() + 1);
  90. date.setHours(nextHour);
  91. date.setMinutes(0);
  92. date.setSeconds(0);
  93. continue;
  94. }
  95. date.setHours(nextHour);
  96. date.setMinutes(0);
  97. date.setSeconds(0);
  98. }
  99. if(!timeMatch(date.getMinutes(), cronTrigger[MIN])){
  100. var nextMinute = nextCronTime(date.getMinutes(), cronTrigger[MIN]);
  101. if(nextMinute <= date.getMinutes()){
  102. date.setHours(date.getHours() + 1);
  103. date.setMinutes(nextMinute);
  104. date.setSeconds(0);
  105. continue;
  106. }
  107. date.setMinutes(nextMinute);
  108. date.setSeconds(0);
  109. }
  110. if(!timeMatch(date.getSeconds(), cronTrigger[SECOND])){
  111. var nextSecond = nextCronTime(date.getSeconds(), cronTrigger[SECOND]);
  112. if(nextSecond <= date.getSeconds()){
  113. date.setMinutes(date.getMinutes() + 1);
  114. date.setSeconds(nextSecond);
  115. continue;
  116. }
  117. date.setSeconds(nextSecond);
  118. }
  119. break;
  120. }
  121. this.nextTime = date.getTime();
  122. return this.nextTime;
  123. };
  124. /**
  125. * return the next match time of the given value
  126. * @param value The time value
  127. * @param cronTime The cronTime need to match
  128. * @return The match value or null if unmatch(it offten means an error occur).
  129. */
  130. function nextCronTime(value, cronTime){
  131. value += 1;
  132. if(typeof(cronTime) == 'number'){
  133. if(cronTime == -1)
  134. return value;
  135. else
  136. return cronTime;
  137. }else if(typeof(cronTime) == 'object' && cronTime instanceof Array){
  138. if(value <= cronTime[0] || value > cronTime[cronTime.length -1])
  139. return cronTime[0];
  140. for(var i = 0; i < cronTime.length; i++)
  141. if(value <= cronTime[i])
  142. return cronTime[i];
  143. }
  144. logger.warn('Compute next Time error! value :' + value + ' cronTime : ' + cronTime);
  145. return null;
  146. }
  147. /**
  148. * Match the given value to the cronTime
  149. * @param value The given value
  150. * @param cronTime The cronTime
  151. * @return The match result
  152. */
  153. function timeMatch(value, cronTime){
  154. if(typeof(cronTime) == 'number'){
  155. if(cronTime == -1)
  156. return true;
  157. if(value == cronTime)
  158. return true;
  159. return false;
  160. }else if(typeof(cronTime) == 'object' && cronTime instanceof Array){
  161. if(value < cronTime[0] || value > cronTime[cronTime.length -1])
  162. return false;
  163. for(var i = 0; i < cronTime.length; i++)
  164. if(value == cronTime[i])
  165. return true;
  166. return false;
  167. }
  168. return null;
  169. }
  170. /**
  171. * Decude the cronTrigger string to arrays
  172. * @param cronTimeStr The cronTimeStr need to decode, like "0 12 * * * 3"
  173. * @return The array to represent the cronTimer
  174. */
  175. pro.decodeTrigger = function(cronTimeStr){
  176. var cronTimes = cronTimeStr.split(/\s+/);
  177. if(cronTimes.length != 6){
  178. console.log('error');
  179. return null;
  180. }
  181. for(var i = 0; i < cronTimes.length; i++){
  182. cronTimes[i] = (this.decodeTimeStr(cronTimes[i], i));
  183. if(!checkNum(cronTimes[i], Limit[i][0], Limit[i][1])){
  184. logger.error('Decode crontime error, value exceed limit!' +
  185. JSON.stringify({cronTime: cronTimes[i], limit:Limit[i]}));
  186. return null;
  187. }
  188. }
  189. return cronTimes;
  190. }
  191. /**
  192. * Decode the cron Time string
  193. * @param timeStr The cron time string, like: 1,2 or 1-3
  194. * @return A sorted array, like [1,2,3]
  195. */
  196. pro.decodeTimeStr = function(timeStr, type){
  197. var result = {};
  198. var arr = [];
  199. if(timeStr=='*'){
  200. return -1;
  201. }else if(timeStr.search(',')>0){
  202. var timeArr = timeStr.split(',');
  203. for(var i = 0; i < timeArr.length; i++){
  204. var time = timeArr[i];
  205. if(time.match(/^\d+-\d+$/)){
  206. decodeRangeTime(result, time);
  207. }else if(time.match(/^\d+\/\d+/)){
  208. decodePeriodTime(result, time, type);
  209. }else if(!isNaN(time)){
  210. var num = Number(time);
  211. result[num] = num;
  212. }else
  213. return null;
  214. }
  215. }else if(timeStr.match(/^\d+-\d+$/)){
  216. decodeRangeTime(result, timeStr);
  217. }else if(timeStr.match(/^\d+\/\d+/)){
  218. decodePeriodTime(result, timeStr, type);
  219. }else if(!isNaN(timeStr)){
  220. var num = Number(timeStr);
  221. result[num] = num;
  222. }else{
  223. return null;
  224. }
  225. for(var key in result){
  226. arr.push(result[key]);
  227. }
  228. arr.sort(function(a, b){
  229. return a - b;
  230. });
  231. return arr;
  232. }
  233. /**
  234. * Decode time range
  235. * @param map The decode map
  236. * @param timeStr The range string, like 2-5
  237. */
  238. function decodeRangeTime(map, timeStr){
  239. var times = timeStr.split('-');
  240. times[0] = Number(times[0]);
  241. times[1] = Number(times[1]);
  242. if(times[0] > times[1]){
  243. console.log("Error time range");
  244. return null;
  245. }
  246. for(var i = times[0]; i <= times[1]; i++){
  247. map[i] = i;
  248. }
  249. }
  250. /**
  251. * Compute the period timer
  252. */
  253. function decodePeriodTime(map, timeStr, type){
  254. var times = timeStr.split('/');
  255. var min = Limit[type][0];
  256. var max = Limit[type][1];
  257. var remind = Number(times[0]);
  258. var period = Number(times[1]);
  259. if(period==0)
  260. return;
  261. for(var i = min; i <= max; i++){
  262. if(i%period == remind)
  263. map[i] = i;
  264. }
  265. }
  266. /**
  267. * Check if the numbers are valid
  268. * @param nums The numbers array need to check
  269. * @param min Minimus value
  270. * @param max Maximam value
  271. * @return If all the numbers are in the data range
  272. */
  273. function checkNum(nums, min, max){
  274. if(nums == null)
  275. return false;
  276. if(nums == -1)
  277. return true;
  278. for(var i = 0; i < nums.length; i++){
  279. if(nums[i]<min || nums[i]>max)
  280. return false;
  281. }
  282. return true;
  283. }
  284. /**
  285. * Get the date limit of given month
  286. * @param The given year
  287. * @month The given month
  288. * @return The date count of given month
  289. */
  290. function getDomLimit(year, month){
  291. var date = new Date(year, month+1, 0);
  292. return date.getDate();
  293. }
  294. /**
  295. * Create cronTrigger
  296. * @param trigger The Cron Trigger string
  297. * @return The Cron trigger
  298. */
  299. function createTrigger(trigger, job){
  300. return new CronTrigger(trigger, job);
  301. }
  302. module.exports.createTrigger = createTrigger;