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))); | ||
|  |     } | ||
|  | } |