Git Product home page Git Product logo

croncpp's Introduction

croncpp

croncpp is a C++11/14/17 header-only cross-platform library for handling CRON expressions. It implements two basic operations: parsing an expression and computing the next occurence of the scheduled time.

Build Status Tests status

CRON expressions

A CRON expression is a string composed of six fields (in some implementation seven) separated by a whites space representing a time schedule. The general form is the following (with the years being optional):

<seconds> <minutes> <hours> <days of month> <months> <days of week> <years>

The following values are allowed for these fields:

Field Required Allowed value * Allowed value (alternative 1) ** Allowed value (alternative 2) *** Allowed special characters
seconds yes 0-59 0-59 0-59 * , -
minutes yes 0-59 0-59 0-59 * , -
hours yes 0-23 0-23 0-23 * , -
days of month 1-31 1-31 1-31 1-31 * , - ? L W
months yes 1-12 0-11 1-12 * , -
days of week yes 0-6 1-7 1-7 * , - ? L #
years no 1970-2099 1970-2099 1970-2099 * , -

* - As described on Wikipedia Cron

** - As described on Oracle Role Manager Integration Guide - A Cron Expressions

*** - As described for the Quartz scheduler CronTrigger Tutorial

The special characters have the following meaning:

Special character Meaning Description
* all values selects all values within a field
? no specific value specify one field and leave the other unspecified
- range specify ranges
, comma specify additional values
/ slash speficy increments
L last last day of the month or last day of the week
W weekday the weekday nearest to the given day
# nth specify the Nth day of the month

Examples:

CRON Description
* * * * * * Every second
*/5 * * * * ? Every 5 seconds
0 */5 */2 * * ? Every 5 minutes, every 2 hours
0 */2 */2 ? */2 */2 Every 2 minutes, every 2 hours, every 2 days of the week, every 2 months
0 15 10 * * ? * 10:15 AM every day
0 0/5 14 * * ? Every 5 minutes starting at 2 PM and ending at 2:55 PM, every day
0 10,44 14 ? 3 WED 2:10 PM and at 2:44 PM every Wednesday of March
0 15 10 ? * MON-FRI 10:15 AM every Monday, Tuesday, Wednesday, Thursday and Friday
0 15 10 L * ? 10:15 AM on the last day of every month
0 0 12 1/5 * ? 12 PM every 5 days every month, starting on the first day of the month
0 11 11 11 11 ? Every November 11th at 11:11 AM

croncpp library

To parse a CRON expression use make_cron() as follows:

try
{
   auto cron = cron::make_cron("* 0/5 * * * ?");
}
catch (cron::bad_cronexpr const & ex)
{
   std::cerr << ex.what() << '\n';
}

make_cron() returns an object of the type cronexpr. The actual content of this object is not of real interest and, in fact, all its details are private. You can consider this as an implementation detail object that contains the necessary information for a CRON expression, in order to compute the next occurence of the time schedule, which is the actual important operation we are interested in.

To get the next occurence of the time schedule use the cron_next() function as follows:

try
{
   auto cron = cron::make_cron("* 0/5 * * * ?");
   
   std::time_t now = std::time(0);
   std::time_t next = cron::cron_next(cron, now);   
}
catch (cron::bad_cronexpr const & ex)
{
   std::cerr << ex.what() << '\n';
}

Alternatively, you can use std::tm instead of std::time_t:

try
{
   auto cron = cron::make_cron("* 0/5 * * * ?");
   
   std::tm time = cron::utils::to_tm("2018-08-08 20:30:45");
   std::tm next = cron::cron_next(cron, time);
}
catch (cron::bad_cronexpr const & ex)
{
   std::cerr << ex.what() << '\n';
}

When you use these functions as shown above you implicitly use the standard supported values for the fields, as described in the first section. However, you can use any other settings. The ones provided with the library are called cron_standard_traits, cron_oracle_traits and cron_quartz_traits (coresponding to the aforementioned settings).

try
{
   auto cron = cron::make_cron<cron_quartz_traits>("* 0/5 * * * ?");
   
   std::time_t now = std::time(0);
   std::time_t next = cron::cron_next<cron_quartz_traits>(cron, now);   
}
catch (cron::bad_cronexpr const & ex)
{
   std::cerr << ex.what() << '\n';
}

There are two functions that convert the cronexpr object to a string:

  • to_cronstr() returns the original cron expression text from with the object was created.
  • to_string() returns a string format of the representation of the cron expression.
auto cex = make_cron("* * * * * *");

assert(to_cronstr(cex) == "* * * * * *");
assert(to_string(cex) == "111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111111111 111111111111111111111111 1111111111111111111111111111111 111111111111 1111111");

Benchmarks

The following results are the average (in microseconds) for running the benchmark program ten times on Windows and Mac with different compilers (all with release settings).

VC++ 32-bit VC++ 64-bit GCC 32-bit GCC 64-bit Clang 64-bit
11.52 8.30 8.95 7.03 4.48

VC++ 15.7.4 running on

  • Windows 10 Enterprise build 17134
  • Intel Core i7, 2.67 GHz, 1 CPU / 4 cores / 8 logical, 6 RAM

GCC 8.1.0 / Clang LLVM 9.1.0 running on

  • macOS 10.13.5
  • Intel Core i7, 1.7 GHz, 1 CPU / 2 cores, 8 GB RAM

CRON parsin

Credits

This library implementation is based on ccronexpr ANSI C library, which in turn is based on the implementation of CronSequenceGenerator from Spring Framework.

croncpp's People

Contributors

eliasnaslund avatar greenbrettmichael avatar harlowja avatar laoshanxi avatar mariusbancila avatar martinzink avatar thomasgt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

croncpp's Issues

Time zones / daylight savings time?

Does croncpp operate relative to UTC, the system's local time zone, or something else? I'm not seeing anything about this in the documentation, and it's concerning because it absolutely matters when you're talking about scheduling against calendar-related attributes. I'm also seeing a lot of open issues regarding things like daylight savings time transitions (#8, #24, #30, #32) and/or time zones (#23).

I know a second-order dependency is probably undesirable, but I'm wondering if it might be worth using the Howard Hinnant date library (https://github.com/HowardHinnant/date) since it's meant to provide modern C++ tools that can help with these kinds of issues.

The C# Cronos library also has some good thoughts in the "Daylight Savings Time" section of its documentation: https://github.com/HangfireIO/Cronos/

Behavior on "imaginary dates"

The cron string "0 0 5 31 2 ?" (February 31st) returns large negative numbers as wait times, but should throw an exception. In general, imaginary dates should cause an error- there is no next occurrence of the cron string. However, this is easily handled on the user side.

g++ 4.8.5 build failed with no std::put_time() & std::get_time()

I am trying to use this lib in app-mesh, faced below compile error:

20:55:08 In file included from /opt/jenkins_data/workspace/appmesh/src/daemon/application/ApplicationCron.cpp:5:0:
20:55:08 /opt/jenkins_data/workspace/appmesh/src/daemon/application/../../common/croncpp.h: In function 'tm cron::utils::to_tm(const string&)':
20:55:08 /opt/jenkins_data/workspace/appmesh/src/daemon/application/../../common/croncpp.h:265:17: error: 'get_time' is not a member of 'std'
20:55:08           str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S");
20:55:08                  ^
20:55:08 /opt/jenkins_data/workspace/appmesh/src/daemon/application/../../common/croncpp.h: In function 'std::string cron::utils::to_string(const tm&)':
20:55:08 /opt/jenkins_data/workspace/appmesh/src/daemon/application/../../common/croncpp.h:277:17: error: 'put_time' is not a member of 'std'
20:55:08           str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
20:55:08                  ^
20:55:09 make[2]: *** [src/daemon/application/CMakeFiles/application.dir/ApplicationCron.cpp.o] Error 1
20:55:09 make[1]: *** [src/daemon/application/CMakeFiles/application.dir/all] Error 2

complie error occurs when using c++1z

Hi, i've encountered some complie error when i passed c++1z option to a complier.

My main.cpp is simple as below.

#include <iostream>
#include <ctime>

#include <croncpp.h>

using namespace std;

int main(void)
{
    std::cout<<"End this program"<<std::endl;

    std::string sExpression = "0 0/10 * * * *";

    auto sCron = cron::make_cron(sExpression);

    std::time_t sNow = std::time(nullptr);
    std::time_t sNextTime = cron::cron_next(sCron, sNow);

    return 0;
}

But when I tried to compile this like below, some error occured.

$g++ -o main main.cpp -isystem ../externals/croncpp/include --std=c++1z
In file included from main.cpp:4:0:
../externals/croncpp/include/croncpp.h: In function ‘constexpr bool cron::utils::contains(std::string_view, char)’:
../externals/croncpp/include/croncpp.h:333:63: error: call to non-constexpr function ‘std::basic_string_view<_CharT, _Traits>::size_type std::basic_string_view<_CharT, _Traits>::find_first_of(_CharT, std::basic_string_view<_CharT, _Traits>::size_type) const [with _CharT = char; _Traits = std::char_traits<char>; std::basic_string_view<_CharT, _Traits>::size_type = long unsigned int]’
          return CRONCPP_STRING_VIEW_NPOS != text.find_first_of(ch);
                                             ~~~~~~~~~~~~~~~~~~^~~~

Without the --std=c++1z option, it complied without any complaints.

My complier version is gcc version 7.2.1 20170829 (Red Hat 7.2.1-1) (GCC)

get last execution time

It would be nice to be able to get the last time a cron expression was executed. The java lib http://cron-parser.com/ does this, and it's very useful. I'll try implementing it myself but would like to know if it's something you'd be interested in working on.

cron_next with time_t parameter produces next time in the past

Hi, I'm trying to use croncpp and it looks very good. The only issue I see is with time_t input and timezone.
For example if I call setenv to use TZ as Europe/Rome, then I call the time_t function with current timestamp, a cron string like "*/5 * * * * *" gives a next_time in the past.
The problem is that the function takes a UTC time_t as input and then converts it as if it was a local time.

Bug in handling cronspecs with 31-day months

Looks like there's a bug with handing cronspecs with some 31-day months. The tests don't fail for every 31-day month but where they do the failure is different depending on the input. I'm fairly sure I've the correct input format.

Passing test:

check_next("0 25 23 31 10 ?", "2011-09-22 14:20:00", "2011-10-31 23:25:00");

Failing test 1:

check_next("0 25 23 31 7 ?", "2011-09-22 14:20:00", "2012-07-31 23:25:00");

image

Failing test 2:

check_next("0 25 23 31 8 ?", "2011-09-22 14:20:00", "2012-08-31 23:25:00");

image

I'll try and go through the code to see if I can identify the cause but thought I'd post this in case some wiser heads can assist.

Daylight saving issue

unix epoch time: 1585398600
Saturday, March 28, 2020 2:30:00 PM GMT+02:00

rule: 0 30 14 * * *

using that time, next time returns the same time, it's probably related with the daylight time saving of next day change.

last month day not works

Hello! Thanks for this work first of all. Respectful!
When i try to parse "0 0 12 L * ?", it's catches 'stoul' exception.

Compilation error: redeclaration of friend may not have default template args

I am having the following compilation error:

In file included from /mnt/data/compile/croncpp_orig/benchmark/main.cpp:7:0:
/mnt/data/compile/croncpp_orig/include/croncpp.h:713:20: error: redeclaration of friend ‘template<class Traits> cron::cronexpr cron::make_cron(std::string_view)’ may not have default template arguments
    static cronexpr make_cron(std::string_view expr)
                    ^~~~~~~~~
In file included from /mnt/data/compile/croncpp_orig/benchmark/main.cpp:7:0:
/mnt/data/compile/croncpp_orig/include/croncpp.h:24:20: note: ‘template<class Traits> cron::cronexpr cron::make_cron(std::string_view)’ previously declared here
    static cronexpr make_cron(std::string_view expr);
                    ^~~~~~~~~
/mnt/data/compile/croncpp_orig/benchmark/main.cpp: In function ‘void test_cron(const std::vector<std::__cxx11::basic_string<char> >&)’:
/mnt/data/compile/croncpp_orig/benchmark/main.cpp:32:39: error: no matching function for call to ‘make_cron(const std::__cxx11::basic_string<char>&)’
          auto cron = cron::make_cron(e);
                                       ^
In file included from /mnt/data/compile/croncpp_orig/benchmark/main.cpp:7:0:
/mnt/data/compile/croncpp_orig/include/croncpp.h:24:20: note: candidate: template<class Traits> cron::cronexpr cron::make_cron(std::string_view)
    static cronexpr make_cron(std::string_view expr);
                    ^~~~~~~~~
/mnt/data/compile/croncpp_orig/include/croncpp.h:24:20: note:   template argument deduction/substitution failed:
/mnt/data/compile/croncpp_orig/benchmark/main.cpp:32:39: note:   couldn't deduce template parameter ‘Traits’
          auto cron = cron::make_cron(e);

I'm using Ubuntu 18.04 and gcc 7.3.0, is this normal? Issue with my gcc?

Specify the Nth day of the month not working

I've been trying to use croncpp for scheduling, however when I try to do something monthly (ie: Only run on the 2nd friday) croncpp seems to only do weekly.

I've looked in croncpp.h and couldn't see it parsing the '#' character nor handling anything else regarding days_of_week besides ranges.

I tried adding to the test_standard.cpp:
check_next("0 30 23 ? * 5#2", "2011-04-30 23:30:00", "2011-05-13 23:30:00");

image

Also noticed that this seems to be handling 5 as thursday instead of friday.

Daylight savings issue

There is definitely a problem with this code having to do with daylight savings transition. Simple example

time_t now = 1647084600;    // Mar 12, 2022 4:30am
string e = "0 30 4 * * *";
cron::cronexpr exp = cron::make_cron(e);
time_t next= cron::cron_next(exp, now);

"now" and "next" come out identical. Expectation is next would be a day ahead. Works the rest of the year

croncpp returning time in the past near the DST

Hi, I've found a strange case which fails:

#include "include/croncpp.h"
#include <iostream>

int main() {

   try {

     setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
     tzset();

     auto cron_string = std::string{ "30 55 5,11,17 * * *" };
     auto cron = cron::make_cron(cron_string);

     std::time_t now = 1679777644;
     std::time_t next = cron::cron_next(cron, now);

     std::cout << "Now:  " << now << std::endl;
     std::cout << "Next: " << next << std::endl;

  }
  catch (cron::bad_cronexpr const & ex)
  {
    std::cerr << ex.what() << '\n';
  }

}

prints:

Now:  1679777644
Next: 1679720130

where next < now.
Am I doing something wrong with the invocation here?
Thanks!

No cron::cron_previous() ?...

Hi,
Great project ! But here is a proposal for improvement ... ;-)

I am currently working on a new iot project, a connected petdoor with the possibility to program the blocking of the input or output : https://github.com/peychart/Smart-petdoor (using a Json C++ library with a small memory footprint : https://github.com/peychart/Untyped-cpp... ;- )

But, on ESP8266 start (or reboot), it would be necessary to know in what state it should put the door, and thus to find the previous cron programming in the planning...

Would it be easy to do that : cron::cron_previous()?
Regards.

store and return original cron expr

cron object is created by cron::make_cron(original_expr), but cron::to_string(cron::cronexpr) just return the extracted string, there is no function get original_expr.

It is better to add a get function to get the input cron expr.

How do we use this?

I am trying to set up a timer. However "cron_next" doesn't return correct values for the timer.

        auto expr{ "* 0/5 * * * ?" };
        auto cron = cron::make_cron(expr);

        std::time_t now = std::time(0);
        std::time_t next = cron::cron_next(cron, now);
        std::cout << "set timer for: " << next - now << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(next - now));

        cron = cron::make_cron(expr); // construct again?
        now = std::time(0); // new time or old time?
        next = cron::cron_next(cron, now);
        std::cout << "set timer for: " << next - now << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(next - now));

        cron = cron::make_cron(expr); // construct again?
        now = std::time(0); // new time or old time?
        next = cron::cron_next(cron, now);
        std::cout << "set timer for: " << next - now << std::endl;

Wrong timestamp when set day of month and day of week

If I set the cronjob 0 0 0 1 1 1 (https://crontab.guru/#0_0_1_1_1) and want to have the next timestamp, I expect Friday, January 1. 2021.
But my program crashes with a stack overflow, because it is looking for the 1st of January which is also on a Monday.

By making this change I could fix the problem locally:

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;

   const auto is_all_days_of_month = days_of_month.all();
   const auto is_all_days_of_week = days_of_week.all();

   while (
      count++ < maximum && (
         (is_all_days_of_month && is_all_days_of_week && (!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)))
            ||
         (!is_all_days_of_month && is_all_days_of_week && (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH)))
            ||
         (is_all_days_of_month && !is_all_days_of_week && (!days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)))
            ||
         (!is_all_days_of_month && !is_all_days_of_week && (!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)))
      )`
   )
   {
      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;
}

Example does appear to work/parse?

I was trying some of the examples and saw the following happen:

Trying: 0 15 10 L * ?
terminate called after throwing an instance of 'cron::bad_cronexpr'
  what():  stoul
Aborted (core dumped)

Program code (nothing crazy):

#include "croncpp.h"

#include <chrono>
#include <iostream>
#include <string>

int main(int argc, char** argv) {
    std::string c = argv[1];
    std::cout << "Trying: "<< c << "\n";
    auto cexpr = cron::make_cron(c);
    auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    auto next = cron::cron_next(cexpr, now);
    std::cout << next << "\n";
    return 0;
}

I also tried with the various oracle and quartz traits, but didn't seem to work.

Perhaps remove from the README.md if this format doesn't work?

When generated time added to an asio timer, several executions happen at the same time

In the example below I am trying to set an asio timer with the time calculated by cron_next. When the timer expires I am trying to set it again with the same cron entry (every 5 seconds). As you can see from the printed log, the timer is executed several times per second before making the next execution 5 seconds later.

Looks like cron_next can generate time which has already passed

To be executed at 1545474550000000000
Executed 1545474550000000000 at 1545474550000052948
Executed 1545474550000000000 at 1545474550000311756
Executed 1545474550000000000 at 1545474550000532974
Executed 1545474555000000000 at 1545474555000051491
Executed 1545474555000000000 at 1545474555000311948
Executed 1545474555000000000 at 1545474555000529890
Executed 1545474560000000000 at 1545474560000054536
Executed 1545474560000000000 at 1545474560000316441
Executed 1545474560000000000 at 1545474560000541654

void doSetTimer(asio::system_timer & timer, std::chrono::system_clock::time_point timePoint)
{
    timer.expires_at(timePoint);

    timer.async_wait([&timer, timePoint](std::error_code ec) {
	if (!ec) //Timed out
	{
	    std::cout << "Executed " << timePoint.time_since_epoch().count() << " at " << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
	    auto cron = cron::make_cron("*/5 * * * * ?");
	    auto timePointNew = std::chrono::system_clock::from_time_t(cron::cron_next(cron, std::time(nullptr)));
	    doSetTimer(timer, timePointNew);
	}
    });
}


int main()
{
    auto cron = cron::make_cron("*/5 * * * * ?");
    auto timePoint = std::chrono::system_clock::from_time_t(cron::cron_next(cron, std::time(nullptr)));

    std::cout << "To be executed at " << timePoint.time_since_epoch().count() << std::endl;

    asio::io_context ioContext;
    asio::system_timer timer(ioContext);

    doSetTimer(timer, timePoint);

    ioContext.run_for(std::chrono::seconds(14));
}

"invalid bitset position" thrown when calling cron::cron_next<cron::cron_quartz_traits>(...)

Hi,
It is noticed that for a date and time entry that correspond to Sundays the cron::cron_next<cron::cron_quartz_traits>(...) function throws an invalid bitset position exception. This case is deterministic and could be produced with following code snippet for example.

        std::tm currentRunTime{};
	currentRunTime.tm_sec = 0;
	currentRunTime.tm_min = 0;
	currentRunTime.tm_hour = 0;
	currentRunTime.tm_mday = 3;
	currentRunTime.tm_mon = 8;
	currentRunTime.tm_year = 123;
	currentRunTime.tm_wday = 0;
	currentRunTime.tm_yday = 245;
	currentRunTime.tm_isdst = 1;

	auto quartzExpression{ "0 0 */12 * * 1-7" };
	auto cronExpr = cron::make_cron<cron::cron_quartz_traits>(quartzExpression);
	auto nextRunTime = cron::cron_next<cron::cron_quartz_traits>(cronExpr, currentRunTime);

We'll be glad to have your insight on this subject.
Kind regards,
Hossein Afshari
image

Support chrono

As a C++17 library, this should support modern C++ chrono facilities instead of (or at least in addition to) C-style std::time_t.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.