930 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			930 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
| #pragma once
 | |
| 
 | |
| #include <vector>
 | |
| #include <string>
 | |
| #include <sstream>
 | |
| #include <bitset>
 | |
| #include <cctype>
 | |
| #include <ctime>
 | |
| #include <iomanip>
 | |
| #include <algorithm>
 | |
| #include <chrono>
 | |
| 
 | |
| #if __cplusplus > 201402L
 | |
| #include <string_view>
 | |
| #define CRONCPP_IS_CPP17
 | |
| #endif
 | |
| 
 | |
| namespace cron
 | |
| {
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
| #define CRONCPP_STRING_VIEW std::string_view
 | |
| #define CRONCPP_STRING_VIEW_NPOS std::string_view::npos
 | |
| #define CRONCPP_CONSTEXPTR constexpr
 | |
| #else
 | |
| #define CRONCPP_STRING_VIEW std::string const &
 | |
| #define CRONCPP_STRING_VIEW_NPOS std::string::npos
 | |
| #define CRONCPP_CONSTEXPTR
 | |
| #endif
 | |
| 
 | |
|     using cron_int = uint8_t;
 | |
| 
 | |
|     constexpr std::time_t INVALID_TIME = static_cast<std::time_t>(-1);
 | |
| 
 | |
|     constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
 | |
| 
 | |
|     class cronexpr;
 | |
| 
 | |
|     namespace detail
 | |
|     {
 | |
|         enum class cron_field
 | |
|         {
 | |
|             second,
 | |
|             minute,
 | |
|             hour_of_day,
 | |
|             day_of_week,
 | |
|             day_of_month,
 | |
|             month,
 | |
|             year
 | |
|         };
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static bool find_next(cronexpr const &cex,
 | |
|                               std::tm &date,
 | |
|                               size_t const dot);
 | |
|     }
 | |
| 
 | |
|     struct bad_cronexpr : public std::runtime_error
 | |
|     {
 | |
|     public:
 | |
|         explicit bad_cronexpr(CRONCPP_STRING_VIEW message) : std::runtime_error(message.data())
 | |
|         {
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     struct cron_standard_traits
 | |
|     {
 | |
|         static const cron_int CRON_MIN_SECONDS = 0;
 | |
|         static const cron_int CRON_MAX_SECONDS = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MINUTES = 0;
 | |
|         static const cron_int CRON_MAX_MINUTES = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_HOURS = 0;
 | |
|         static const cron_int CRON_MAX_HOURS = 23;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_WEEK = 0;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_WEEK = 6;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MONTHS = 1;
 | |
|         static const cron_int CRON_MAX_MONTHS = 12;
 | |
| 
 | |
|         static const cron_int CRON_MAX_YEARS_DIFF = 4;
 | |
| 
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|         static const inline std::vector<std::string> DAYS = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|         static const inline std::vector<std::string> MONTHS = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
| #else
 | |
|         static std::vector<std::string> &DAYS()
 | |
|         {
 | |
|             static std::vector<std::string> days = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|             return days;
 | |
|         }
 | |
| 
 | |
|         static std::vector<std::string> &MONTHS()
 | |
|         {
 | |
|             static std::vector<std::string> months = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
|             return months;
 | |
|         }
 | |
| #endif
 | |
|     };
 | |
| 
 | |
|     struct cron_oracle_traits
 | |
|     {
 | |
|         static const cron_int CRON_MIN_SECONDS = 0;
 | |
|         static const cron_int CRON_MAX_SECONDS = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MINUTES = 0;
 | |
|         static const cron_int CRON_MAX_MINUTES = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_HOURS = 0;
 | |
|         static const cron_int CRON_MAX_HOURS = 23;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MONTHS = 0;
 | |
|         static const cron_int CRON_MAX_MONTHS = 11;
 | |
| 
 | |
|         static const cron_int CRON_MAX_YEARS_DIFF = 4;
 | |
| 
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|         static const inline std::vector<std::string> DAYS = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|         static const inline std::vector<std::string> MONTHS = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
| #else
 | |
| 
 | |
|         static std::vector<std::string> &DAYS()
 | |
|         {
 | |
|             static std::vector<std::string> days = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|             return days;
 | |
|         }
 | |
| 
 | |
|         static std::vector<std::string> &MONTHS()
 | |
|         {
 | |
|             static std::vector<std::string> months = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
|             return months;
 | |
|         }
 | |
| #endif
 | |
|     };
 | |
| 
 | |
|     struct cron_quartz_traits
 | |
|     {
 | |
|         static const cron_int CRON_MIN_SECONDS = 0;
 | |
|         static const cron_int CRON_MAX_SECONDS = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MINUTES = 0;
 | |
|         static const cron_int CRON_MAX_MINUTES = 59;
 | |
| 
 | |
|         static const cron_int CRON_MIN_HOURS = 0;
 | |
|         static const cron_int CRON_MAX_HOURS = 23;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
 | |
| 
 | |
|         static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
 | |
|         static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
 | |
| 
 | |
|         static const cron_int CRON_MIN_MONTHS = 1;
 | |
|         static const cron_int CRON_MAX_MONTHS = 12;
 | |
| 
 | |
|         static const cron_int CRON_MAX_YEARS_DIFF = 4;
 | |
| 
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|         static const inline std::vector<std::string> DAYS = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|         static const inline std::vector<std::string> MONTHS = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
| #else
 | |
|         static std::vector<std::string> &DAYS()
 | |
|         {
 | |
|             static std::vector<std::string> days = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
 | |
|             return days;
 | |
|         }
 | |
| 
 | |
|         static std::vector<std::string> &MONTHS()
 | |
|         {
 | |
|             static std::vector<std::string> months = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
 | |
|             return months;
 | |
|         }
 | |
| #endif
 | |
|     };
 | |
| 
 | |
|     class cronexpr;
 | |
| 
 | |
|     template <typename Traits = cron_standard_traits>
 | |
|     static cronexpr make_cron(CRONCPP_STRING_VIEW expr);
 | |
| 
 | |
|     class cronexpr
 | |
|     {
 | |
|         std::bitset<60> seconds;
 | |
|         std::bitset<60> minutes;
 | |
|         std::bitset<24> hours;
 | |
|         std::bitset<7> days_of_week;
 | |
|         std::bitset<31> days_of_month;
 | |
|         std::bitset<12> months;
 | |
|         std::string expr;
 | |
| 
 | |
|         friend bool operator==(cronexpr const &e1, cronexpr const &e2);
 | |
|         friend bool operator!=(cronexpr const &e1, cronexpr const &e2);
 | |
| 
 | |
|         template <typename Traits>
 | |
|         friend bool detail::find_next(cronexpr const &cex,
 | |
|                                       std::tm &date,
 | |
|                                       size_t const dot);
 | |
| 
 | |
|         friend std::string to_cronstr(cronexpr const &cex);
 | |
|         friend std::string to_string(cronexpr const &cex);
 | |
| 
 | |
|         template <typename Traits>
 | |
|         friend cronexpr make_cron(CRONCPP_STRING_VIEW expr);
 | |
|     };
 | |
| 
 | |
|     inline bool operator==(cronexpr const &e1, cronexpr const &e2)
 | |
|     {
 | |
|         return e1.seconds == e2.seconds &&
 | |
|                e1.minutes == e2.minutes &&
 | |
|                e1.hours == e2.hours &&
 | |
|                e1.days_of_week == e2.days_of_week &&
 | |
|                e1.days_of_month == e2.days_of_month &&
 | |
|                e1.months == e2.months;
 | |
|     }
 | |
| 
 | |
|     inline bool operator!=(cronexpr const &e1, cronexpr const &e2)
 | |
|     {
 | |
|         return !(e1 == e2);
 | |
|     }
 | |
| 
 | |
|     inline std::string to_string(cronexpr const &cex)
 | |
|     {
 | |
|         return cex.seconds.to_string() + " " +
 | |
|                cex.minutes.to_string() + " " +
 | |
|                cex.hours.to_string() + " " +
 | |
|                cex.days_of_month.to_string() + " " +
 | |
|                cex.months.to_string() + " " +
 | |
|                cex.days_of_week.to_string();
 | |
|     }
 | |
| 
 | |
|     inline std::string to_cronstr(cronexpr const &cex)
 | |
|     {
 | |
|         return cex.expr;
 | |
|     }
 | |
| 
 | |
|     namespace utils
 | |
|     {
 | |
|         inline std::time_t tm_to_time(std::tm &date)
 | |
|         {
 | |
|             return std::mktime(&date);
 | |
|         }
 | |
| 
 | |
|         inline std::tm *time_to_tm(std::time_t const *date, std::tm *const out)
 | |
|         {
 | |
| #ifdef _WIN32
 | |
|             errno_t err = localtime_s(out, date);
 | |
|             return 0 == err ? out : nullptr;
 | |
| #else
 | |
|             return localtime_r(date, out);
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         inline std::tm to_tm(CRONCPP_STRING_VIEW time)
 | |
|         {
 | |
|             std::tm result;
 | |
| #if __cplusplus > 201103L
 | |
|             std::istringstream str(time.data());
 | |
|             str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
 | |
| 
 | |
|             str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S");
 | |
|             if (str.fail())
 | |
|                 throw std::runtime_error("Parsing date failed!");
 | |
| #else
 | |
|             int year = 1900;
 | |
|             int month = 1;
 | |
|             int day = 1;
 | |
|             int hour = 0;
 | |
|             int minute = 0;
 | |
|             int second = 0;
 | |
|             sscanf(time.data(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
 | |
|             result.tm_year = year - 1900;
 | |
|             result.tm_mon = month - 1;
 | |
|             result.tm_mday = day;
 | |
|             result.tm_hour = hour;
 | |
|             result.tm_min = minute;
 | |
|             result.tm_sec = second;
 | |
| #endif
 | |
|             result.tm_isdst = -1; // DST info not available
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         inline std::string to_string(std::tm const &tm)
 | |
|         {
 | |
| #if __cplusplus > 201103L
 | |
|             std::ostringstream str;
 | |
|             str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
 | |
|             str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
 | |
|             if (str.fail())
 | |
|                 throw std::runtime_error("Writing date failed!");
 | |
| 
 | |
|             return str.str();
 | |
| #else
 | |
|             char buff[70] = {0};
 | |
|             strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm);
 | |
|             return std::string(buff);
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         inline std::string to_upper(std::string text)
 | |
|         {
 | |
|             std::transform(std::begin(text), std::end(text),
 | |
|                            std::begin(text), [](char const c)
 | |
|                            { return static_cast<char>(std::toupper(c)); });
 | |
| 
 | |
|             return text;
 | |
|         }
 | |
| 
 | |
|         static std::vector<std::string> split(CRONCPP_STRING_VIEW text, char const delimiter)
 | |
|         {
 | |
|             std::vector<std::string> tokens;
 | |
|             std::string token;
 | |
|             std::istringstream tokenStream(text.data());
 | |
|             while (std::getline(tokenStream, token, delimiter))
 | |
|             {
 | |
|                 tokens.push_back(token);
 | |
|             }
 | |
|             return tokens;
 | |
|         }
 | |
| 
 | |
|         CRONCPP_CONSTEXPTR inline bool contains(CRONCPP_STRING_VIEW text, char const ch) noexcept
 | |
|         {
 | |
|             return CRONCPP_STRING_VIEW_NPOS != text.find_first_of(ch);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     namespace detail
 | |
|     {
 | |
| 
 | |
|         inline cron_int to_cron_int(CRONCPP_STRING_VIEW text)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 return static_cast<cron_int>(std::stoul(text.data()));
 | |
|             }
 | |
|             catch (std::exception const &ex)
 | |
|             {
 | |
|                 throw bad_cronexpr(ex.what());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         static std::string replace_ordinals(
 | |
|             std::string text,
 | |
|             std::vector<std::string> const &replacement)
 | |
|         {
 | |
|             for (size_t i = 0; i < replacement.size(); ++i)
 | |
|             {
 | |
|                 auto pos = text.find(replacement[i]);
 | |
|                 if (std::string::npos != pos)
 | |
|                     text.replace(pos, 3, std::to_string(i));
 | |
|             }
 | |
| 
 | |
|             return text;
 | |
|         }
 | |
| 
 | |
|         static std::pair<cron_int, cron_int> make_range(
 | |
|             CRONCPP_STRING_VIEW field,
 | |
|             cron_int const minval,
 | |
|             cron_int const maxval)
 | |
|         {
 | |
|             cron_int first = 0;
 | |
|             cron_int last = 0;
 | |
|             if (field.size() == 1 && field[0] == '*')
 | |
|             {
 | |
|                 first = minval;
 | |
|                 last = maxval;
 | |
|             }
 | |
|             else if (!utils::contains(field, '-'))
 | |
|             {
 | |
|                 first = to_cron_int(field);
 | |
|                 last = first;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 auto parts = utils::split(field, '-');
 | |
|                 if (parts.size() != 2)
 | |
|                     throw bad_cronexpr("Specified range requires two fields");
 | |
| 
 | |
|                 first = to_cron_int(parts[0]);
 | |
|                 last = to_cron_int(parts[1]);
 | |
|             }
 | |
| 
 | |
|             if (first > maxval || last > maxval)
 | |
|             {
 | |
|                 throw bad_cronexpr("Specified range exceeds maximum");
 | |
|             }
 | |
|             if (first < minval || last < minval)
 | |
|             {
 | |
|                 throw bad_cronexpr("Specified range is less than minimum");
 | |
|             }
 | |
|             if (first > last)
 | |
|             {
 | |
|                 throw bad_cronexpr("Specified range start exceeds range end");
 | |
|             }
 | |
| 
 | |
|             return {first, last};
 | |
|         }
 | |
| 
 | |
|         template <size_t N>
 | |
|         static void set_cron_field(
 | |
|             CRONCPP_STRING_VIEW value,
 | |
|             std::bitset<N> &target,
 | |
|             cron_int const minval,
 | |
|             cron_int const maxval)
 | |
|         {
 | |
|             if (value.length() > 0 && value[value.length() - 1] == ',')
 | |
|                 throw bad_cronexpr("Value cannot end with comma");
 | |
| 
 | |
|             auto fields = utils::split(value, ',');
 | |
|             if (fields.empty())
 | |
|                 throw bad_cronexpr("Expression parsing error");
 | |
| 
 | |
|             for (auto const &field : fields)
 | |
|             {
 | |
|                 if (!utils::contains(field, '/'))
 | |
|                 {
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|                     auto [first, last] = detail::make_range(field, minval, maxval);
 | |
| #else
 | |
|                     auto range = detail::make_range(field, minval, maxval);
 | |
|                     auto first = range.first;
 | |
|                     auto last = range.second;
 | |
| #endif
 | |
|                     for (cron_int i = first - minval; i <= last - minval; ++i)
 | |
|                     {
 | |
|                         target.set(i);
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     auto parts = utils::split(field, '/');
 | |
|                     if (parts.size() != 2)
 | |
|                         throw bad_cronexpr("Incrementer must have two fields");
 | |
| 
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|                     auto [first, last] = detail::make_range(parts[0], minval, maxval);
 | |
| #else
 | |
|                     auto range = detail::make_range(parts[0], minval, maxval);
 | |
|                     auto first = range.first;
 | |
|                     auto last = range.second;
 | |
| #endif
 | |
| 
 | |
|                     if (!utils::contains(parts[0], '-'))
 | |
|                     {
 | |
|                         last = maxval;
 | |
|                     }
 | |
| 
 | |
|                     auto delta = detail::to_cron_int(parts[1]);
 | |
|                     if (delta <= 0)
 | |
|                         throw bad_cronexpr("Incrementer must be a positive value");
 | |
| 
 | |
|                     for (cron_int i = first - minval; i <= last - minval; i += delta)
 | |
|                     {
 | |
|                         target.set(i);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static void set_cron_days_of_week(
 | |
|             std::string value,
 | |
|             std::bitset<7> &target)
 | |
|         {
 | |
|             auto days = utils::to_upper(value);
 | |
|             auto days_replaced = detail::replace_ordinals(
 | |
|                 days,
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|                 Traits::DAYS
 | |
| #else
 | |
|                 Traits::DAYS()
 | |
| #endif
 | |
|             );
 | |
| 
 | |
|             if (days_replaced.size() == 1 && days_replaced[0] == '?')
 | |
|                 days_replaced[0] = '*';
 | |
| 
 | |
|             set_cron_field(
 | |
|                 days_replaced,
 | |
|                 target,
 | |
|                 Traits::CRON_MIN_DAYS_OF_WEEK,
 | |
|                 Traits::CRON_MAX_DAYS_OF_WEEK);
 | |
|         }
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static void set_cron_days_of_month(
 | |
|             std::string value,
 | |
|             std::bitset<31> &target)
 | |
|         {
 | |
|             if (value.size() == 1 && value[0] == '?')
 | |
|                 value[0] = '*';
 | |
| 
 | |
|             set_cron_field(
 | |
|                 value,
 | |
|                 target,
 | |
|                 Traits::CRON_MIN_DAYS_OF_MONTH,
 | |
|                 Traits::CRON_MAX_DAYS_OF_MONTH);
 | |
|         }
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static void set_cron_month(
 | |
|             std::string value,
 | |
|             std::bitset<12> &target)
 | |
|         {
 | |
|             auto month = utils::to_upper(value);
 | |
|             auto month_replaced = replace_ordinals(
 | |
|                 month,
 | |
| #ifdef CRONCPP_IS_CPP17
 | |
|                 Traits::MONTHS
 | |
| #else
 | |
|                 Traits::MONTHS()
 | |
| #endif
 | |
|             );
 | |
| 
 | |
|             set_cron_field(
 | |
|                 month_replaced,
 | |
|                 target,
 | |
|                 Traits::CRON_MIN_MONTHS,
 | |
|                 Traits::CRON_MAX_MONTHS);
 | |
|         }
 | |
| 
 | |
|         template <size_t N>
 | |
|         inline size_t next_set_bit(
 | |
|             std::bitset<N> const &target,
 | |
|             size_t /*minimum*/,
 | |
|             size_t /*maximum*/,
 | |
|             size_t offset)
 | |
|         {
 | |
|             for (auto i = offset; i < N; ++i)
 | |
|             {
 | |
|                 if (target.test(i))
 | |
|                     return i;
 | |
|             }
 | |
| 
 | |
|             return INVALID_INDEX;
 | |
|         }
 | |
| 
 | |
|         inline void add_to_field(
 | |
|             std::tm &date,
 | |
|             cron_field const field,
 | |
|             int const val)
 | |
|         {
 | |
|             switch (field)
 | |
|             {
 | |
|             case cron_field::second:
 | |
|                 date.tm_sec += val;
 | |
|                 break;
 | |
|             case cron_field::minute:
 | |
|                 date.tm_min += val;
 | |
|                 break;
 | |
|             case cron_field::hour_of_day:
 | |
|                 date.tm_hour += val;
 | |
|                 break;
 | |
|             case cron_field::day_of_week:
 | |
|             case cron_field::day_of_month:
 | |
|                 date.tm_mday += val;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::month:
 | |
|                 date.tm_mon += val;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::year:
 | |
|                 date.tm_year += val;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (INVALID_TIME == utils::tm_to_time(date))
 | |
|                 throw bad_cronexpr("Invalid time expression");
 | |
|         }
 | |
| 
 | |
|         inline void set_field(
 | |
|             std::tm &date,
 | |
|             cron_field const field,
 | |
|             int const val)
 | |
|         {
 | |
|             switch (field)
 | |
|             {
 | |
|             case cron_field::second:
 | |
|                 date.tm_sec = val;
 | |
|                 break;
 | |
|             case cron_field::minute:
 | |
|                 date.tm_min = val;
 | |
|                 break;
 | |
|             case cron_field::hour_of_day:
 | |
|                 date.tm_hour = val;
 | |
|                 break;
 | |
|             case cron_field::day_of_week:
 | |
|                 date.tm_wday = val;
 | |
|                 break;
 | |
|             case cron_field::day_of_month:
 | |
|                 date.tm_mday = val;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::month:
 | |
|                 date.tm_mon = val;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::year:
 | |
|                 date.tm_year = val;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (INVALID_TIME == utils::tm_to_time(date))
 | |
|                 throw bad_cronexpr("Invalid time expression");
 | |
|         }
 | |
| 
 | |
|         inline void reset_field(
 | |
|             std::tm &date,
 | |
|             cron_field const field)
 | |
|         {
 | |
|             switch (field)
 | |
|             {
 | |
|             case cron_field::second:
 | |
|                 date.tm_sec = 0;
 | |
|                 break;
 | |
|             case cron_field::minute:
 | |
|                 date.tm_min = 0;
 | |
|                 break;
 | |
|             case cron_field::hour_of_day:
 | |
|                 date.tm_hour = 0;
 | |
|                 break;
 | |
|             case cron_field::day_of_week:
 | |
|                 date.tm_wday = 0;
 | |
|                 break;
 | |
|             case cron_field::day_of_month:
 | |
|                 date.tm_mday = 1;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::month:
 | |
|                 date.tm_mon = 0;
 | |
|                 date.tm_isdst = -1;
 | |
|                 break;
 | |
|             case cron_field::year:
 | |
|                 date.tm_year = 0;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (INVALID_TIME == utils::tm_to_time(date))
 | |
|                 throw bad_cronexpr("Invalid time expression");
 | |
|         }
 | |
| 
 | |
|         inline void reset_all_fields(
 | |
|             std::tm &date,
 | |
|             std::bitset<7> const &marked_fields)
 | |
|         {
 | |
|             for (size_t i = 0; i < marked_fields.size(); ++i)
 | |
|             {
 | |
|                 if (marked_fields.test(i))
 | |
|                     reset_field(date, static_cast<cron_field>(i));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         inline void mark_field(
 | |
|             std::bitset<7> &orders,
 | |
|             cron_field const field)
 | |
|         {
 | |
|             if (!orders.test(static_cast<size_t>(field)))
 | |
|                 orders.set(static_cast<size_t>(field));
 | |
|         }
 | |
| 
 | |
|         template <size_t N>
 | |
|         static size_t find_next(
 | |
|             std::bitset<N> const &target,
 | |
|             std::tm &date,
 | |
|             unsigned int const minimum,
 | |
|             unsigned int const maximum,
 | |
|             unsigned int const value,
 | |
|             cron_field const field,
 | |
|             cron_field const next_field,
 | |
|             std::bitset<7> const &marked_fields)
 | |
|         {
 | |
|             auto next_value = next_set_bit(target, minimum, maximum, value);
 | |
|             if (INVALID_INDEX == next_value)
 | |
|             {
 | |
|                 add_to_field(date, next_field, 1);
 | |
|                 reset_field(date, field);
 | |
|                 next_value = next_set_bit(target, minimum, maximum, 0);
 | |
|             }
 | |
| 
 | |
|             if (INVALID_INDEX == next_value || next_value != value)
 | |
|             {
 | |
|                 set_field(date, field, static_cast<int>(next_value));
 | |
|                 reset_all_fields(date, marked_fields);
 | |
|             }
 | |
| 
 | |
|             return next_value;
 | |
|         }
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static size_t find_next_day(
 | |
|             std::tm &date,
 | |
|             std::bitset<31> const &days_of_month,
 | |
|             size_t day_of_month,
 | |
|             std::bitset<7> const &days_of_week,
 | |
|             size_t day_of_week,
 | |
|             std::bitset<7> const &marked_fields)
 | |
|         {
 | |
|             unsigned int count = 0;
 | |
|             unsigned int maximum = 366;
 | |
|             while (
 | |
|                 (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) ||
 | |
|                  !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) &&
 | |
|                 count++ < maximum)
 | |
|             {
 | |
|                 add_to_field(date, cron_field::day_of_month, 1);
 | |
| 
 | |
|                 day_of_month = date.tm_mday;
 | |
|                 day_of_week = date.tm_wday;
 | |
| 
 | |
|                 reset_all_fields(date, marked_fields);
 | |
|             }
 | |
| 
 | |
|             return day_of_month;
 | |
|         }
 | |
| 
 | |
|         template <typename Traits>
 | |
|         static bool find_next(cronexpr const &cex,
 | |
|                               std::tm &date,
 | |
|                               size_t const dot)
 | |
|         {
 | |
|             bool res = true;
 | |
| 
 | |
|             std::bitset<7> marked_fields{0};
 | |
|             std::bitset<7> empty_list{0};
 | |
| 
 | |
|             unsigned int second = date.tm_sec;
 | |
|             auto updated_second = find_next(
 | |
|                 cex.seconds,
 | |
|                 date,
 | |
|                 Traits::CRON_MIN_SECONDS,
 | |
|                 Traits::CRON_MAX_SECONDS,
 | |
|                 second,
 | |
|                 cron_field::second,
 | |
|                 cron_field::minute,
 | |
|                 empty_list);
 | |
| 
 | |
|             if (second == updated_second)
 | |
|             {
 | |
|                 mark_field(marked_fields, cron_field::second);
 | |
|             }
 | |
| 
 | |
|             unsigned int minute = date.tm_min;
 | |
|             auto update_minute = find_next(
 | |
|                 cex.minutes,
 | |
|                 date,
 | |
|                 Traits::CRON_MIN_MINUTES,
 | |
|                 Traits::CRON_MAX_MINUTES,
 | |
|                 minute,
 | |
|                 cron_field::minute,
 | |
|                 cron_field::hour_of_day,
 | |
|                 marked_fields);
 | |
|             if (minute == update_minute)
 | |
|             {
 | |
|                 mark_field(marked_fields, cron_field::minute);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 res = find_next<Traits>(cex, date, dot);
 | |
|                 if (!res)
 | |
|                     return res;
 | |
|             }
 | |
| 
 | |
|             unsigned int hour = date.tm_hour;
 | |
|             auto updated_hour = find_next(
 | |
|                 cex.hours,
 | |
|                 date,
 | |
|                 Traits::CRON_MIN_HOURS,
 | |
|                 Traits::CRON_MAX_HOURS,
 | |
|                 hour,
 | |
|                 cron_field::hour_of_day,
 | |
|                 cron_field::day_of_week,
 | |
|                 marked_fields);
 | |
|             if (hour == updated_hour)
 | |
|             {
 | |
|                 mark_field(marked_fields, cron_field::hour_of_day);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 res = find_next<Traits>(cex, date, dot);
 | |
|                 if (!res)
 | |
|                     return res;
 | |
|             }
 | |
| 
 | |
|             unsigned int day_of_week = date.tm_wday;
 | |
|             unsigned int day_of_month = date.tm_mday;
 | |
|             auto updated_day_of_month = find_next_day<Traits>(
 | |
|                 date,
 | |
|                 cex.days_of_month,
 | |
|                 day_of_month,
 | |
|                 cex.days_of_week,
 | |
|                 day_of_week,
 | |
|                 marked_fields);
 | |
|             if (day_of_month == updated_day_of_month)
 | |
|             {
 | |
|                 mark_field(marked_fields, cron_field::day_of_month);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 res = find_next<Traits>(cex, date, dot);
 | |
|                 if (!res)
 | |
|                     return res;
 | |
|             }
 | |
| 
 | |
|             unsigned int month = date.tm_mon;
 | |
|             auto updated_month = find_next(
 | |
|                 cex.months,
 | |
|                 date,
 | |
|                 Traits::CRON_MIN_MONTHS,
 | |
|                 Traits::CRON_MAX_MONTHS,
 | |
|                 month,
 | |
|                 cron_field::month,
 | |
|                 cron_field::year,
 | |
|                 marked_fields);
 | |
|             if (month != updated_month)
 | |
|             {
 | |
|                 if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF)
 | |
|                     return false;
 | |
| 
 | |
|                 res = find_next<Traits>(cex, date, dot);
 | |
|                 if (!res)
 | |
|                     return res;
 | |
|             }
 | |
| 
 | |
|             return res;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     template <typename Traits>
 | |
|     static cronexpr make_cron(CRONCPP_STRING_VIEW expr)
 | |
|     {
 | |
|         cronexpr cex;
 | |
| 
 | |
|         if (expr.empty())
 | |
|             throw bad_cronexpr("Invalid empty cron expression");
 | |
| 
 | |
|         auto fields = utils::split(expr, ' ');
 | |
|         fields.erase(
 | |
|             std::remove_if(std::begin(fields), std::end(fields),
 | |
|                            [](CRONCPP_STRING_VIEW s)
 | |
|                            { return s.empty(); }),
 | |
|             std::end(fields));
 | |
|         if (fields.size() != 6)
 | |
|             throw bad_cronexpr("cron expression must have six fields");
 | |
| 
 | |
|         detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS);
 | |
|         detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES);
 | |
|         detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS);
 | |
| 
 | |
|         detail::set_cron_days_of_week<Traits>(fields[5], cex.days_of_week);
 | |
| 
 | |
|         detail::set_cron_days_of_month<Traits>(fields[3], cex.days_of_month);
 | |
| 
 | |
|         detail::set_cron_month<Traits>(fields[4], cex.months);
 | |
| 
 | |
|         cex.expr = expr;
 | |
| 
 | |
|         return cex;
 | |
|     }
 | |
| 
 | |
|     template <typename Traits = cron_standard_traits>
 | |
|     static std::tm cron_next(cronexpr const &cex, std::tm date)
 | |
|     {
 | |
|         time_t original = utils::tm_to_time(date);
 | |
|         if (INVALID_TIME == original)
 | |
|             return {};
 | |
| 
 | |
|         if (!detail::find_next<Traits>(cex, date, date.tm_year))
 | |
|             return {};
 | |
| 
 | |
|         time_t calculated = utils::tm_to_time(date);
 | |
|         if (INVALID_TIME == calculated)
 | |
|             return {};
 | |
| 
 | |
|         if (calculated == original)
 | |
|         {
 | |
|             add_to_field(date, detail::cron_field::second, 1);
 | |
|             if (!detail::find_next<Traits>(cex, date, date.tm_year))
 | |
|                 return {};
 | |
|         }
 | |
| 
 | |
|         return date;
 | |
|     }
 | |
| 
 | |
|     template <typename Traits = cron_standard_traits>
 | |
|     static std::time_t cron_next(cronexpr const &cex, std::time_t const &date)
 | |
|     {
 | |
|         std::tm val;
 | |
|         std::tm *dt = utils::time_to_tm(&date, &val);
 | |
|         if (dt == nullptr)
 | |
|             return INVALID_TIME;
 | |
| 
 | |
|         time_t original = utils::tm_to_time(*dt);
 | |
|         if (INVALID_TIME == original)
 | |
|             return INVALID_TIME;
 | |
| 
 | |
|         if (!detail::find_next<Traits>(cex, *dt, dt->tm_year))
 | |
|             return INVALID_TIME;
 | |
| 
 | |
|         time_t calculated = utils::tm_to_time(*dt);
 | |
|         if (INVALID_TIME == calculated)
 | |
|             return calculated;
 | |
| 
 | |
|         if (calculated == original)
 | |
|         {
 | |
|             add_to_field(*dt, detail::cron_field::second, 1);
 | |
|             if (!detail::find_next<Traits>(cex, *dt, dt->tm_year))
 | |
|                 return INVALID_TIME;
 | |
|         }
 | |
| 
 | |
|         return utils::tm_to_time(*dt);
 | |
|     }
 | |
| 
 | |
|     template <typename Traits = cron_standard_traits>
 | |
|     static std::chrono::system_clock::time_point cron_next(cronexpr const &cex, std::chrono::system_clock::time_point const &time_point)
 | |
|     {
 | |
|         return std::chrono::system_clock::from_time_t(cron_next<Traits>(cex, std::chrono::system_clock::to_time_t(time_point)));
 | |
|     }
 | |
| }
 |