tc_cron.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #include "util/tc_cron.h"
  2. #include "util/tc_platform.h"
  3. namespace tars
  4. {
  5. const std::vector<std::string> TC_Cron::DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
  6. const std::vector<std::string> TC_Cron::MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
  7. TC_Cron TC_Cron::makecron(const string& expr)
  8. {
  9. TC_Cron cron;
  10. if (expr.empty())
  11. {
  12. throw TC_CronException("[TC_Cron makecron] invalid empty cron expression");
  13. }
  14. vector<string> fields = TC_Common::sepstr<string>(expr, " ");
  15. if (fields.size() != 6)
  16. throw TC_CronException("[TC_Cron makecron] cron expression must have six fields");
  17. setCronField(fields[0], cron.seconds, CRON_MIN_SECONDS, CRON_MAX_SECONDS);
  18. setCronField(fields[1], cron.minutes, CRON_MIN_MINUTES, CRON_MAX_MINUTES);
  19. setCronField(fields[2], cron.hours, CRON_MIN_HOURS, CRON_MAX_HOURS);
  20. setCronDaysOfWeek(fields[5], cron.days_of_week);
  21. setCronDaysOfMonth(fields[3], cron.days_of_month);
  22. setCronMonth(fields[4], cron.months);
  23. cron.isset = true;
  24. return cron;
  25. }
  26. std::time_t TC_Cron::nextcron(const TC_Cron& cron, std::time_t date)
  27. {
  28. std::tm val;
  29. std::tm* dt = time_to_tm(&date, &val);
  30. if (dt == nullptr) return INVALID_TIME;
  31. time_t original = tm_to_time(*dt);
  32. if (INVALID_TIME == original) return INVALID_TIME;
  33. if (!findNext(cron, *dt, dt->tm_year))
  34. return INVALID_TIME;
  35. time_t calculated = tm_to_time(*dt);
  36. if (INVALID_TIME == calculated) return calculated;
  37. if (calculated == original)
  38. {
  39. addToField(*dt, CronField::second, 1);
  40. if (!findNext(cron, *dt, dt->tm_year))
  41. return INVALID_TIME;
  42. }
  43. return tm_to_time(*dt);
  44. }
  45. std::time_t TC_Cron::nextcron(const TC_Cron& cron)
  46. {
  47. std::time_t now = std::time(0);
  48. return nextcron(cron, now);
  49. }
  50. std::tm* TC_Cron::time_to_tm(std::time_t const* date, std::tm* const out)
  51. {
  52. #if TARGET_PLATFORM_WINDOWS
  53. errno_t err = localtime_s(out, date);
  54. return 0 == err ? out : nullptr;
  55. #else
  56. return localtime_r(date, out);
  57. #endif
  58. }
  59. std::time_t TC_Cron::tm_to_time(std::tm& tmt)
  60. {
  61. return std::mktime(&tmt);
  62. }
  63. bool TC_Cron::contains(const std::string& text, char ch)
  64. {
  65. return std::string::npos != text.find_first_of(ch);
  66. }
  67. cron_int TC_Cron::to_cron_int(const std::string& text)
  68. {
  69. try
  70. {
  71. return static_cast<cron_int>(std::stoul(text.data()));
  72. }
  73. catch (std::exception const& ex)
  74. {
  75. throw TC_CronException(string("[TC_Cron to_cron_int] ") + ex.what());
  76. }
  77. }
  78. std::string TC_Cron::replaceOrdinals(std::string text, const std::vector<std::string> & replacement)
  79. {
  80. for (size_t i = 0; i < replacement.size(); ++i)
  81. {
  82. auto pos = text.find(replacement[i]);
  83. if (std::string::npos != pos)
  84. {
  85. text.replace(pos, 3, std::to_string(i));
  86. }
  87. }
  88. return text;
  89. }
  90. std::pair<cron_int, cron_int> TC_Cron::makeRange(std::string field, cron_int minval, cron_int maxval)
  91. {
  92. cron_int first = 0;
  93. cron_int last = 0;
  94. if (field.size() == 1 && field[0] == '*')
  95. {
  96. first = minval;
  97. last = maxval;
  98. }
  99. else if (!contains(field, '-'))
  100. {
  101. first = to_cron_int(field);
  102. last = first;
  103. }
  104. else
  105. {
  106. auto parts = TC_Common::sepstr<string>(field, "-");
  107. if (parts.size() != 2)
  108. throw TC_CronException("TC_Cron::makeRange] specified range requires two fields");
  109. first = to_cron_int(parts[0]);
  110. last = to_cron_int(parts[1]);
  111. }
  112. if (first > maxval || last > maxval)
  113. {
  114. throw TC_CronException("TC_Cron::makeRange] specified range exceeds maximum");
  115. }
  116. if (first < minval || last < minval)
  117. {
  118. throw TC_CronException("TC_Cron::makeRange] specified range is less than minimum");
  119. }
  120. if (first > last)
  121. {
  122. throw TC_CronException("TC_Cron::makeRange] specified range start exceeds range end");
  123. }
  124. return { first, last };
  125. }
  126. void TC_Cron::setCronDaysOfWeek(std::string value, std::bitset<7>& target)
  127. {
  128. auto days = TC_Common::upper(value);
  129. auto days_replaced = replaceOrdinals(days, DAYS);
  130. if (days_replaced.size() == 1 && days_replaced[0] == '?')
  131. days_replaced[0] = '*';
  132. setCronField(days_replaced,target,CRON_MIN_DAYS_OF_WEEK,CRON_MAX_DAYS_OF_WEEK);
  133. }
  134. void TC_Cron::setCronDaysOfMonth(std::string value, std::bitset<31>& target)
  135. {
  136. if (value.size() == 1 && value[0] == '?')
  137. value[0] = '*';
  138. setCronField(value, target, CRON_MIN_DAYS_OF_MONTH, CRON_MAX_DAYS_OF_MONTH);
  139. }
  140. void TC_Cron::setCronMonth(std::string value, std::bitset<12>& target)
  141. {
  142. auto month = TC_Common::upper(value);
  143. auto month_replaced = replaceOrdinals(month, MONTHS);
  144. setCronField(month_replaced, target, CRON_MIN_MONTHS, CRON_MAX_MONTHS);
  145. }
  146. void TC_Cron::addToField(std::tm& tmt, CronField field, int const val)
  147. {
  148. switch (field)
  149. {
  150. case CronField::second:
  151. tmt.tm_sec += val;
  152. break;
  153. case CronField::minute:
  154. tmt.tm_min += val;
  155. break;
  156. case CronField::hour_of_day:
  157. tmt.tm_hour += val;
  158. break;
  159. case CronField::day_of_week:
  160. case CronField::day_of_month:
  161. tmt.tm_mday += val;
  162. break;
  163. case CronField::month:
  164. tmt.tm_mon += val;
  165. break;
  166. case CronField::year:
  167. tmt.tm_year += val;
  168. break;
  169. }
  170. if (INVALID_TIME == tm_to_time(tmt))
  171. throw TC_CronException("TC_Cron::addToField] invalid time expression");
  172. }
  173. void TC_Cron::setField(std::tm& tmt, CronField field, int const val)
  174. {
  175. switch (field)
  176. {
  177. case CronField::second:
  178. tmt.tm_sec = val;
  179. break;
  180. case CronField::minute:
  181. tmt.tm_min = val;
  182. break;
  183. case CronField::hour_of_day:
  184. tmt.tm_hour = val;
  185. break;
  186. case CronField::day_of_week:
  187. tmt.tm_wday = val;
  188. break;
  189. case CronField::day_of_month:
  190. tmt.tm_mday = val;
  191. break;
  192. case CronField::month:
  193. tmt.tm_mon = val;
  194. break;
  195. case CronField::year:
  196. tmt.tm_year = val;
  197. break;
  198. }
  199. if (INVALID_TIME == tm_to_time(tmt))
  200. throw TC_CronException("TC_Cron::setField] invalid time expression");
  201. }
  202. void TC_Cron::resetField(std::tm& tmt, CronField field)
  203. {
  204. switch (field)
  205. {
  206. case CronField::second:
  207. tmt.tm_sec = 0;
  208. break;
  209. case CronField::minute:
  210. tmt.tm_min = 0;
  211. break;
  212. case CronField::hour_of_day:
  213. tmt.tm_hour = 0;
  214. break;
  215. case CronField::day_of_week:
  216. tmt.tm_wday = 0;
  217. break;
  218. case CronField::day_of_month:
  219. tmt.tm_mday = 1;
  220. break;
  221. case CronField::month:
  222. tmt.tm_mon = 0;
  223. break;
  224. case CronField::year:
  225. tmt.tm_year = 0;
  226. break;
  227. }
  228. if (INVALID_TIME == tm_to_time(tmt))
  229. throw TC_CronException("TC_Cron::resetField] invalid time expression");
  230. }
  231. void TC_Cron::resetAllFields(std::tm& tmt, const std::bitset<7> & marked_fields)
  232. {
  233. for (size_t i = 0; i < marked_fields.size(); ++i)
  234. {
  235. if (marked_fields.test(i))
  236. resetField(tmt, static_cast<CronField>(i));
  237. }
  238. }
  239. void TC_Cron::markField(std::bitset<7>& orders, CronField field)
  240. {
  241. if (!orders.test(static_cast<size_t>(field)))
  242. orders.set(static_cast<size_t>(field));
  243. }
  244. size_t TC_Cron::findNextDay(
  245. std::tm& tmt,
  246. std::bitset<31> const& days_of_month,
  247. size_t day_of_month,
  248. const std::bitset<7> & days_of_week,
  249. size_t day_of_week,
  250. const std::bitset<7> & marked_fields)
  251. {
  252. unsigned int count = 0;
  253. unsigned int maximum = 366;
  254. while (
  255. (!days_of_month.test(day_of_month - CRON_MIN_DAYS_OF_MONTH) ||
  256. !days_of_week.test(day_of_week - CRON_MIN_DAYS_OF_WEEK))
  257. && count++ < maximum)
  258. {
  259. addToField(tmt, CronField::day_of_month, 1);
  260. day_of_month = tmt.tm_mday;
  261. day_of_week = tmt.tm_wday;
  262. resetAllFields(tmt, marked_fields);
  263. }
  264. return day_of_month;
  265. }
  266. bool TC_Cron::findNext(const TC_Cron & cron, std::tm& tmt, size_t const dot)
  267. {
  268. bool res = true;
  269. std::bitset<7> marked_fields{ 0 };
  270. std::bitset<7> empty_list{ 0 };
  271. unsigned int second = tmt.tm_sec;
  272. auto updated_second = findNext(cron.seconds,tmt,CRON_MIN_SECONDS,CRON_MAX_SECONDS,second,CronField::second,CronField::minute,empty_list);
  273. if (second == updated_second)
  274. {
  275. markField(marked_fields, CronField::second);
  276. }
  277. unsigned int minute = tmt.tm_min;
  278. auto update_minute = findNext(cron.minutes,tmt,CRON_MIN_MINUTES,CRON_MAX_MINUTES,minute,CronField::minute,CronField::hour_of_day,marked_fields);
  279. if (minute == update_minute)
  280. {
  281. markField(marked_fields, CronField::minute);
  282. }
  283. else
  284. {
  285. res = findNext(cron, tmt, dot);
  286. if (!res) return res;
  287. }
  288. unsigned int hour = tmt.tm_hour;
  289. auto updated_hour = findNext(cron.hours,tmt,CRON_MIN_HOURS,CRON_MAX_HOURS,hour,CronField::hour_of_day,CronField::day_of_week,marked_fields);
  290. if (hour == updated_hour)
  291. {
  292. markField(marked_fields, CronField::hour_of_day);
  293. }
  294. else
  295. {
  296. res = findNext(cron, tmt, dot);
  297. if (!res) return res;
  298. }
  299. unsigned int day_of_week = tmt.tm_wday;
  300. unsigned int day_of_month = tmt.tm_mday;
  301. auto updated_day_of_month = findNextDay(tmt,cron.days_of_month,day_of_month,cron.days_of_week,day_of_week,marked_fields);
  302. if (day_of_month == updated_day_of_month)
  303. {
  304. markField(marked_fields, CronField::day_of_month);
  305. }
  306. else
  307. {
  308. res = findNext(cron, tmt, dot);
  309. if (!res) return res;
  310. }
  311. unsigned int month = tmt.tm_mon;
  312. auto updated_month = findNext(cron.months,tmt,CRON_MIN_MONTHS,CRON_MAX_MONTHS,month,
  313. CronField::month,CronField::year,marked_fields);
  314. if (month != updated_month)
  315. {
  316. if (tmt.tm_year - dot > CRON_MAX_YEARS_DIFF)
  317. return false;
  318. res = findNext(cron, tmt, dot);
  319. if (!res) return res;
  320. }
  321. return res;
  322. }
  323. }