Path: blob/master/cpp/linux/scheduler/Cron.h
644 views
#include <chrono>1#include <string>2#include <sstream>3#include <vector>4#include <iterator>56namespace Bosma {7using Clock = std::chrono::system_clock;89inline void add(std::tm &tm, Clock::duration time) {10auto tp = Clock::from_time_t(std::mktime(&tm));11auto tp_adjusted = tp + time;12auto tm_adjusted = Clock::to_time_t(tp_adjusted);13tm = *std::localtime(&tm_adjusted);14}1516class BadCronExpression : public std::exception {17public:18explicit BadCronExpression(std::string msg) : msg_(std::move(msg)) {}1920const char *what() const noexcept override { return (msg_.c_str()); }2122private:23std::string msg_;24};2526inline void27verify_and_set(const std::string &token, const std::string &expression, int &field, const int lower_bound,28const int upper_bound, const bool adjust = false) {29if (token == "*")30field = -1;31else {32try {33field = std::stoi(token);34} catch (const std::invalid_argument &) {35throw BadCronExpression("malformed cron string (`" + token + "` not an integer or *): " + expression);36} catch (const std::out_of_range &) {37throw BadCronExpression("malformed cron string (`" + token + "` not convertable to int): " + expression);38}39if (field < lower_bound || field > upper_bound) {40std::ostringstream oss;41oss << "malformed cron string ('" << token << "' must be <= " << upper_bound << " and >= " << lower_bound42<< "): " << expression;43throw BadCronExpression(oss.str());44}45if (adjust)46field--;47}48}4950class Cron {51public:52explicit Cron(const std::string &expression) {53std::istringstream iss(expression);54std::vector<std::string> tokens{std::istream_iterator<std::string>{iss},55std::istream_iterator<std::string>{}};5657if (tokens.size() != 5) throw BadCronExpression("malformed cron string (must be 5 fields): " + expression);5859verify_and_set(tokens[0], expression, minute, 0, 59);60verify_and_set(tokens[1], expression, hour, 0, 23);61verify_and_set(tokens[2], expression, day, 1, 31);62verify_and_set(tokens[3], expression, month, 1, 12, true);63verify_and_set(tokens[4], expression, day_of_week, 0, 6);64}6566// http://stackoverflow.com/a/322058/128455067Clock::time_point cron_to_next(const Clock::time_point from = Clock::now()) const {68// get current time as a tm object69auto now = Clock::to_time_t(from);70std::tm next(*std::localtime(&now));71// it will always at least run the next minute72next.tm_sec = 0;73add(next, std::chrono::minutes(1));74while (true) {75if (month != -1 && next.tm_mon != month) {76// add a month77// if this will bring us over a year, increment the year instead and reset the month78if (next.tm_mon + 1 > 11) {79next.tm_mon = 0;80next.tm_year++;81} else82next.tm_mon++;8384next.tm_mday = 1;85next.tm_hour = 0;86next.tm_min = 0;87continue;88}89if (day != -1 && next.tm_mday != day) {90add(next, std::chrono::hours(24));91next.tm_hour = 0;92next.tm_min = 0;93continue;94}95if (day_of_week != -1 && next.tm_wday != day_of_week) {96add(next, std::chrono::hours(24));97next.tm_hour = 0;98next.tm_min = 0;99continue;100}101if (hour != -1 && next.tm_hour != hour) {102add(next, std::chrono::hours(1));103next.tm_min = 0;104continue;105}106if (minute != -1 && next.tm_min != minute) {107add(next, std::chrono::minutes(1));108continue;109}110break;111}112113// telling mktime to figure out dst114next.tm_isdst = -1;115return Clock::from_time_t(std::mktime(&next));116}117118int minute, hour, day, month, day_of_week;119};120}121122