#pragma once #include #include #include #include "common/error.hpp" namespace smartbotic::common { // A five-field cron expression: minute hour day-of-month month day-of-week. // // Supports the forms people actually write - "*", "5", "1,15", "2-6", "*/15", // "2-8/2" - and Sunday as either 0 or 7. Seconds are not a field: the scheduler // ticks in minutes, and offering a resolution it cannot honour would be a lie. // // This exists because the schedule-trigger node has offered a cron expression // and a timezone since it was written, and nothing ever read them. A workflow // set to "0 2 * * 1" ran every N minutes instead, and nothing said so. class CronSchedule { public: // Parses, or explains what is wrong with the expression. The message names // the offending field, because "invalid cron expression" tells somebody // staring at five numbers nothing they did not already know. static Result parse(const std::string& expression); // The first matching time strictly after `after`, in the named timezone. // // Returns nothing when the expression can never match - "30 4 31 2 *", the // 31st of February - rather than searching for ever. A schedule that cannot // fire is a mistake worth reporting, not a workflow that quietly never runs. // // An unknown timezone falls back to UTC and is reported by the caller; DST // is the standard library's problem, not this class's. std::optional nextAfter( std::chrono::system_clock::time_point after, const std::string& timezone) const; const std::string& expression() const { return expression_; } private: // One bit per possible value, so matching is a lookup rather than a search. bool minutes_[60] = {}; bool hours_[24] = {}; bool days_of_month_[32] = {}; // 1-31, index 0 unused bool months_[13] = {}; // 1-12, index 0 unused bool days_of_week_[7] = {}; // 0-6, Sunday is 0 // Cron's day rule is an OR when both day fields are restricted, and an AND // when only one is - the historical behaviour every implementation copies. bool day_of_month_restricted_ = false; bool day_of_week_restricted_ = false; std::string expression_; }; } // namespace smartbotic::common