libamxp  1.4.0
Patterns C Implementation
Cron expression parser

Data Structures

struct  _cron_expr
 Structure containing parsed cron expression. More...
 

Typedefs

typedef struct _cron_expr amxp_cron_expr_t
 Structure containing parsed cron expression. More...
 

Functions

int amxp_cron_new (amxp_cron_expr_t **cron_expr)
 Allocates an amxp_cron_expr_t structures and initializes it to every second. More...
 
void amxp_cron_delete (amxp_cron_expr_t **cron_expr)
 Frees the previously allocated amxp_cron_expr_t structure. More...
 
int amxp_cron_init (amxp_cron_expr_t *cron_expr)
 Initializes an amxp_cron_expr_t structures to every second. More...
 
void amxp_cron_clean (amxp_cron_expr_t *cron_expr)
 Resets the amxp_cron_expr_t structure to the initialized state. More...
 
int amxp_cron_parse_expr (amxp_cron_expr_t *target, const char *expression, const char **error)
 Allocates and initializes an amxp_cron_expr_t structures and parses the given cron expression. More...
 
int amxp_cron_build_weekly (amxp_cron_expr_t *target, const char *time, const char *days_of_week)
 Builds a weekly cron expression that is triggered at a certain time on certain days of the week. More...
 
int amxp_cron_prev (const amxp_cron_expr_t *expr, const amxc_ts_t *ref, amxc_ts_t *next)
 Calculates the previous trigger time for a parsed cron expression. More...
 
int amxp_cron_next (const amxp_cron_expr_t *expr, const amxc_ts_t *ref, amxc_ts_t *next)
 Calculates the next trigger time for a parsed cron expression. More...
 
int64_t amxp_cron_time_until_next (const amxp_cron_expr_t *expr, bool local)
 Calculates the time in seconds until next trigger of a parsed cron expression occurs. More...
 

Detailed Description

Ambiorix cron expression parser.

┌───────────── second (0 - 59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12)
│ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
│ │ │ │ │ │ 7 is also Sunday on some systems)
│ │ │ │ │ │
│ │ │ │ │ │
* * * * *

Typedef Documentation

◆ amxp_cron_expr_t

typedef struct _cron_expr amxp_cron_expr_t

Structure containing parsed cron expression.

This structure contains a member for each cron expression field. Each member will have bits set. Each set bit indicates an active position in time for which the cron expression can be trigger.

Function Documentation

◆ amxp_cron_build_weekly()

int amxp_cron_build_weekly ( amxp_cron_expr_t target,
const char *  time,
const char *  days_of_week 
)

Builds a weekly cron expression that is triggered at a certain time on certain days of the week.

Passing the time "15:10" and list of week days "saterday, sunday" will build the cron expression "0 10 15 * * SAT,SUN" and initializes the provided amxp_cron_expr_t structure with it.

The time must be provided in "HH:MM:SS" format, The seconds are optional, when not provided the seconds will be set to "00".

The days of week argument must be a comma separated list of week day names:

  • sunday
  • monday
  • tuesday
  • wednesday
  • thursday
  • friday
  • saterday

The name of the days is case insensitive. It is allowed to provide a range like "monday-friday".

Parameters
targetpointer to amxp_cron_expr_t.
timea string containing the time in "HH:MM:SS" format, seconds are optional.
days_of_weekcomma separated list of week days or a range of week days.
Returns
0 when successful, otherwise an error code

Definition at line 531 of file amxp_cron.c.

533  {
534  int rv = -1;
535  amxc_string_t expr;
536  amxc_string_t strtime;
537  amxc_string_t* time_part = NULL;
538  amxc_llist_t parts;
539  int len = 0;
540  amxc_llist_it_t* it = NULL;
541 
542  amxc_string_init(&strtime, 0);
543  amxc_llist_init(&parts);
544  amxc_string_init(&expr, 0);
545 
546  when_null(target, exit);
547  when_null(time, exit);
548  when_str_empty(days_of_week, exit);
549  amxp_cron_remove_spaces(&strtime, days_of_week);
550 
551  amxc_string_setf(&expr, "* * %s", amxc_string_get(&strtime, 0));
552  amxc_string_set(&strtime, time);
553  if(amxc_string_is_empty(&strtime)) {
554  amxc_string_prependf(&expr, "0 0 0 ");
555  } else {
556  amxc_string_split_to_llist(&strtime, &parts, ':');
557  len = amxc_llist_size(&parts);
558  when_true(len < 2 || len > 3, exit);
559 
560  it = amxc_llist_take_first(&parts);
561  time_part = amxc_string_from_llist_it(it);
562  amxc_string_prependf(&expr, "%s ", amxc_string_get(time_part, 0));
563  amxc_string_delete(&time_part);
564 
565  it = amxc_llist_take_first(&parts);
566  time_part = amxc_string_from_llist_it(it);
567  amxc_string_prependf(&expr, "%s ", amxc_string_get(time_part, 0));
568  amxc_string_delete(&time_part);
569 
570  it = amxc_llist_take_first(&parts);
571  if(it == NULL) {
572  amxc_string_prependf(&expr, "0 ");
573  } else {
574  time_part = amxc_string_from_llist_it(it);
575  amxc_string_prependf(&expr, "%s ", amxc_string_get(time_part, 0));
576  amxc_string_delete(&time_part);
577  }
578  }
579  rv = amxp_cron_parse_expr(target, amxc_string_get(&expr, 0), NULL);
580 
581 exit:
582  amxc_llist_clean(&parts, amxc_string_list_it_free);
583  amxc_string_clean(&strtime);
584  amxc_string_clean(&expr);
585  return rv;
586 }
static void amxp_cron_remove_spaces(amxc_string_t *result, const char *days_of_week)
Definition: amxp_cron.c:424
int amxp_cron_parse_expr(amxp_cron_expr_t *target, const char *expression, const char **error)
Allocates and initializes an amxp_cron_expr_t structures and parses the given cron expression.
Definition: amxp_cron.c:486

◆ amxp_cron_clean()

void amxp_cron_clean ( amxp_cron_expr_t cron_expr)

Resets the amxp_cron_expr_t structure to the initialized state.

Resets the amxp_cron_expr_t structure to the "* * * * * *" cron expression.

Parameters
cron_exprpointer to an amxp_cron_expr_t structure

Definition at line 477 of file amxp_cron.c.

477  {
478  when_null(cron_expr, exit);
479 
480  amxp_cron_parse_expr(cron_expr, "* * * * * *", NULL);
481 
482 exit:
483  return;
484 }

◆ amxp_cron_delete()

void amxp_cron_delete ( amxp_cron_expr_t **  cron_expr)

Frees the previously allocated amxp_cron_expr_t structure.

Frees the allocated memory and sets the pointer to NULL.

Note
Only call this function for amxp_cron_expr_t structures that are allocated on the heap using amxp_cron_new.
Parameters
cron_exprpointer to a pointer that points to the allocated amxp_cron_expr_t structure

Definition at line 453 of file amxp_cron.c.

453  {
454  when_null(cron_expr, exit);
455  when_null(*cron_expr, exit);
456 
457  amxp_cron_clean(*cron_expr);
458 
459  free(*cron_expr);
460  *cron_expr = NULL;
461 
462 exit:
463  return;
464 }
void amxp_cron_clean(amxp_cron_expr_t *cron_expr)
Resets the amxp_cron_expr_t structure to the initialized state.
Definition: amxp_cron.c:477

◆ amxp_cron_init()

int amxp_cron_init ( amxp_cron_expr_t cron_expr)

Initializes an amxp_cron_expr_t structures to every second.

This function initializes a amxp_cron_expr_t structure using following cron expression "* * * * * *". This cron expression triggers every second.

Call amxp_cron_parse_expr to set another schedule for the cron expression.

Parameters
cron_exprpointer to a amxp_cron_expr_t structure that must be initialized.
Returns
When allocation is successfull, this functions returns 0.

Definition at line 466 of file amxp_cron.c.

466  {
467  int retval = -1;
468  when_null(cron_expr, exit);
469 
470  amxp_cron_parse_expr(cron_expr, "* * * * * *", NULL);
471  retval = 0;
472 
473 exit:
474  return retval;
475 }

◆ amxp_cron_new()

int amxp_cron_new ( amxp_cron_expr_t **  cron_expr)

Allocates an amxp_cron_expr_t structures and initializes it to every second.

This function allocates memory on the heap for an amxp_cron_expr_t structure and initializes it using following cron expression "* * * * * *". This cron expression triggers every second.

Call amxp_cron_parse_expr to set another schedule for the cron expression.

Note
The allocated memory must be freed when not used anymore, use amxp_cron_delete to free the memory.
Parameters
cron_exprpointer to a pointer that points to the new allocated amxp_cron_expr_t structure
Returns
When allocation is successfull, this functions returns 0.

Definition at line 440 of file amxp_cron.c.

440  {
441  int retval = -1;
442  when_null(cron_expr, exit);
443 
444  *cron_expr = (amxp_cron_expr_t*) calloc(1, sizeof(amxp_cron_expr_t));
445  when_null(*cron_expr, exit);
446 
447  retval = amxp_cron_init(*cron_expr);
448 
449 exit:
450  return retval;
451 }
int amxp_cron_init(amxp_cron_expr_t *cron_expr)
Initializes an amxp_cron_expr_t structures to every second.
Definition: amxp_cron.c:466
Structure containing parsed cron expression.
Definition: amxp_cron.h:103

◆ amxp_cron_next()

int amxp_cron_next ( const amxp_cron_expr_t expr,
const amxc_ts_t *  ref,
amxc_ts_t *  next 
)

Calculates the next trigger time for a parsed cron expression.

Given a parsed cron expression and a reference time, calculates the next trigger time for the cron expression.

Typically the reference time will be the current time.

Example:

void calculate_timing(void) {
amxp_cron_expr_t* cron_expr = NULL;
amxc_ts_t start = { 0, 0, 0 };
amxc_ts_t next = { 0, 0, 0 };
char str_ts[40];
amxp_cron_parse_expr(&cron_expr, "0 0 0 1-7 JAN-DEC MON", NULL);
amxc_ts_parse(&start, "2023-05-18T10:07:24Z", 20);
amxc_ts_format(&start, str_ts, 40);
printf("Start = %s\n", str_ts);
amxp_cron_next(cron_expr, &start, &next);
amxc_ts_format(&next, str_ts, 40);
printf("Next = %s\n", str_ts);
start = next;
amxp_cron_next(cron_expr, &start, &next);
amxc_ts_format(&next, str_ts, 40);
printf("Next = %s\n", str_ts);
}
int amxp_cron_next(const amxp_cron_expr_t *expr, const amxc_ts_t *ref, amxc_ts_t *next)
Calculates the next trigger time for a parsed cron expression.
Definition: amxp_cron.c:631

Calling the above function will provide this output:

Start = 2023-05-18T10:07:24Z
Next = 2023-06-05T00:00:00Z
Next = 2023-07-03T00:00:00Z
Note
The time offset is always taken into consideration.
If the reference time is local time, the cron expression will work on local time. The next occurence will then be expressed in local time as well.
Consider this local time: "2023-05-19T14:25:00+02:00" and following cron expression "0 15 10,12,14 * * *" then the next occurence will be at "2023-05-20T10:15:00+02:00".
If the local reference time was first converted to UTC it would be:
  • reference time: "2023-05-19T14:25:00+02:00" = "2023-05-19T12:25:00Z" (in UTC)
  • next time: "2023-05-19T14:25:00Z" (in UTC) = "2023-05-19T16:15:00+02:00".
Parameters
exprA parsed cron expression.
refThe reference time.
nextThe calculated next trigger time.
Returns
0 when successful, otherwise an error code

Definition at line 631 of file amxp_cron.c.

631  {
632  int rv = -1;
633  struct tm calval;
634  struct tm* calendar = NULL;
635  time_t date = 0;
636  time_t original = 0;
637  time_t calculated = 0;
638  int res = 0;
639 
640  memset(&calval, 0, sizeof(struct tm));
641 
642  when_null(expr, exit);
643  when_null(next, exit);
644  when_null(ref, exit);
645 
646  date = ref->sec + (ref->offset * 60);
647  calendar = gmtime_r(&date, &calval);
648  when_null(calendar, exit);
649  original = timegm(calendar);
650  when_true(original == -1, exit);
651 
652  res = amxp_cron_calc_next(expr, calendar, calendar->tm_year, true);
653  when_failed(res, exit);
654  calculated = timegm(calendar);
655  when_true(calculated == -1, exit);
656 
657  if(calculated == original) {
658  /* We arrived at the original timestamp - round up to the next whole second and try again... */
659  res = amxp_cron_set_field(calendar, CRON_CF_SECOND, 1, true);
660  when_failed(res, exit);
661  res = amxp_cron_calc_next(expr, calendar, calendar->tm_year, true);
662  when_failed(res, exit)
663  }
664 
665  next->sec = timegm(calendar) - (ref->offset * 60);
666  next->offset = ref->offset;
667  rv = 0;
668 
669 exit:
670  return rv;
671 }
#define CRON_CF_SECOND
Definition: amxp_cron.c:79
static int amxp_cron_calc_next(const amxp_cron_expr_t *expr, struct tm *calendar, unsigned int dot, bool forwards)
Definition: amxp_cron.c:333
static int amxp_cron_set_field(struct tm *calendar, int field, int val, bool add)
Definition: amxp_cron.c:224

◆ amxp_cron_parse_expr()

int amxp_cron_parse_expr ( amxp_cron_expr_t target,
const char *  expression,
const char **  error 
)

Allocates and initializes an amxp_cron_expr_t structures and parses the given cron expression.

This function allocates memory for an amxp_cron_expr_t structure. This memory must be freed when not needed anymore.

When an invalid expression is provided, no memory will be allocated and an error string is put in error.

A valid cron expression contains 6 fields:

┌───────────── second (0 - 59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12)
│ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday);
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
* * * * *

Each field can contain a single value, a list of values, a range, and an incrementor.

  • single value: "<number>"
  • list of values: "<number>,<number>,<number>"
  • range: "<number>-<number>"
  • incrementor: "/<number>"
Note
  • When using a range, make sure that the smallest value is put first.
  • For day of week Sunday can be indicated as 0 or 7.

To indicate all possible value for that field the * symbol can be used.

Months can be specified using ordinals: "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"

Days of week can be specified using ordinals: "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"

When using "SUN" to indicate sunday, index 0 for the day of week will be used.

Examples:

  • Every 2 hours on Sunday: "0 0 0/2 * * 0"
  • Every Saterday at 23:45:00 (11:45 PM): "0 45 23 * * SAT"
  • All weekdays at midnight: "0 0 0 * * MON-FRI"
  • Every first Monday of the month at 12:00:00: "0 0 0 1-7 JAN-DEC MON"
  • Every first Monday of the months June, August, December at 12:00:00: "0 0 0 1-7 JUN,8,DEC MON"
  • Every 15 minutes every day: "0 0/15 * * * *"
Parameters
targetpointer to amxp_cron_expr_t.
expressionthe cron expression that needs to be parsed.
error(optional) if provided a human readable error string will be placed here.
Returns
0 when successful, otherwise an error code

Definition at line 486 of file amxp_cron.c.

488  {
489  const char* err_local = NULL;
490  size_t len = 0;
491  int rv = -1;
492  amxc_var_t fields;
493  static amxp_cron_hits_t hits[] = {
494  { amxp_cron_set_hits, offsetof(amxp_cron_expr_t, seconds), 0, CRON_MAX_SECONDS },
495  { amxp_cron_set_hits, offsetof(amxp_cron_expr_t, minutes), 0, CRON_MAX_MINUTES },
496  { amxp_cron_set_hits, offsetof(amxp_cron_expr_t, hours), 0, CRON_MAX_HOURS },
497  { amxp_cron_set_days_of_month, offsetof(amxp_cron_expr_t, days_of_month), 1, CRON_MAX_DAYS_OF_MONTH },
498  { amxp_cron_set_months, offsetof(amxp_cron_expr_t, months), 1, CRON_MAX_MONTHS },
499  { amxp_cron_set_days_of_week, offsetof(amxp_cron_expr_t, days_of_week), 0, CRON_MAX_DAYS_OF_WEEK },
500  };
501 
502  if(error == NULL) {
503  error = &err_local;
504  }
505  *error = NULL;
506 
507  amxc_var_init(&fields);
508 
509  when_null_status(expression, exit, *error = "Invalid NULL expression");
510  when_null_status(target, exit, *error = "Invalid NULL target");
511  amxp_cron_reset(target);
512 
513  amxc_var_set(ssv_string_t, &fields, expression);
514  amxc_var_cast(&fields, AMXC_VAR_ID_LIST);
515  len = amxc_llist_size(amxc_var_constcast(amxc_llist_t, &fields));
516  when_false_status(len == 6, exit, *error = "Invalid number of fields, expression must consist of 6 fields");
517 
518  len = 0;
519  amxc_var_for_each(field, &fields) {
520  uint8_t* target_field = (uint8_t*) (((char*) (target)) + hits[len].offset);
521  rv = hits[len].fn(field, target_field, hits[len].min, hits[len].max, error);
522  when_failed(rv, exit);
523  len++;
524  }
525 
526 exit:
527  amxc_var_clean(&fields);
528  return rv;
529 }
#define CRON_MAX_DAYS_OF_WEEK
Definition: amxp_cron.c:75
#define CRON_MAX_MONTHS
Definition: amxp_cron.c:77
#define CRON_MAX_DAYS_OF_MONTH
Definition: amxp_cron.c:76
#define CRON_MAX_HOURS
Definition: amxp_cron.c:74
#define CRON_MAX_MINUTES
Definition: amxp_cron.c:73
#define CRON_MAX_SECONDS
Definition: amxp_cron.c:72
PRIVATE int amxp_cron_set_months(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
PRIVATE int amxp_cron_set_hits(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
PRIVATE int amxp_cron_set_days_of_month(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
PRIVATE int amxp_cron_set_days_of_week(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
PRIVATE int amxp_cron_reset(amxp_cron_expr_t *cron_expr)
amxp_cron_set_hits_fn_t fn

◆ amxp_cron_prev()

int amxp_cron_prev ( const amxp_cron_expr_t expr,
const amxc_ts_t *  ref,
amxc_ts_t *  next 
)

Calculates the previous trigger time for a parsed cron expression.

Given a parsed cron expression and a reference time, calculates the previous trigger time for the cron expression.

Typically the reference time will be the current time.

Example:

void calculate_timing(void) {
amxp_cron_expr_t* cron_expr = NULL;
amxc_ts_t start = { 0, 0, 0 };
amxc_ts_t next = { 0, 0, 0 };
char str_ts[40];
amxp_cron_parse_expr(&cron_expr, "0 0 0 1-7 JAN-DEC MON", NULL);
amxc_ts_parse(&start, "2023-05-18T10:07:24Z", 20);
amxc_ts_format(&start, str_ts, 40);
printf("Start = %s\n", str_ts);
amxp_cron_prev(cron_expr, &start, &next);
amxc_ts_format(&next, str_ts, 40);
printf("Previous = %s\n", str_ts);
start = next;
amxp_cron_prev(cron_expr, &start, &next);
amxc_ts_format(&next, str_ts, 40);
printf("Previous = %s\n", str_ts);
}
int amxp_cron_prev(const amxp_cron_expr_t *expr, const amxc_ts_t *ref, amxc_ts_t *next)
Calculates the previous trigger time for a parsed cron expression.
Definition: amxp_cron.c:588

Calling the above function will provide this output:

Start = 2023-05-18T10:07:24Z
Next = 2023-05-01T00:00:00Z
Next = 2023-04-03T00:00:00Z
Note
The time offset is always taken into consideration.
If the reference time is local time, the cron expression will work on local time. The next occurence will then be expressed in local time as well.
Consider this local time: "2023-05-19T12:10:00+02:00" and following cron expression "0 15 10,12,14 * * *" then the previous occurence was at "2023-05-19T10:15:00+02:00".
If the local reference time was first converted to UTC it would be:
  • reference time: "2023-05-19T12:10:00+02:00" = "2023-05-19T10:10:00Z" (in UTC)
  • previous time: "2023-05-18T14:15:00Z" (in UTC) = "2023-05-18T16:15:00+02:00".
Parameters
exprA parsed cron expression.
refThe reference time.
nextThe calculated next trigger time.
Returns
0 when successful, otherwise an error code

Definition at line 588 of file amxp_cron.c.

588  {
589  int rv = -1;
590  struct tm calval;
591  struct tm* calendar = NULL;
592  time_t date = 0;
593  time_t original = 0;
594  time_t calculated = 0;
595  int res = 0;
596 
597  memset(&calval, 0, sizeof(struct tm));
598 
599  when_null(expr, exit);
600  when_null(next, exit);
601  when_null(ref, exit);
602 
603  date = ref->sec + (ref->offset * 60);
604  calendar = gmtime_r(&date, &calval);
605  when_null(calendar, exit);
606  original = timegm(calendar);
607  when_true(original == -1, exit);
608 
609  /* calculate the previous occurrence */
610  res = amxp_cron_calc_next(expr, calendar, calendar->tm_year, false);
611  when_failed(res, exit);
612  calculated = timegm(calendar);
613  when_true(calculated == -1, exit);
614 
615  if(calculated == original) {
616  /* We arrived at the original timestamp - round up to the next whole second and try again... */
617  res = amxp_cron_set_field(calendar, CRON_CF_SECOND, -1, true);
618  when_failed(res, exit);
619  res = amxp_cron_calc_next(expr, calendar, calendar->tm_year, false);
620  when_failed(res, exit)
621  }
622 
623  next->sec = timegm(calendar) - (ref->offset * 60);
624  next->offset = ref->offset;
625  rv = 0;
626 
627 exit:
628  return rv;
629 }

◆ amxp_cron_time_until_next()

int64_t amxp_cron_time_until_next ( const amxp_cron_expr_t expr,
bool  local 
)

Calculates the time in seconds until next trigger of a parsed cron expression occurs.

Calculates the time in seconds, from the current time until the next trigger of the parsed cron expression occurs.

When local is set to true, the local time is used, otherwise the UTC time is used.

Parameters
exprA parsed cron expression.
localSet to true to use local time.
Returns
The time in seconds until next trigger, or negative number when failed to calculate.

Definition at line 673 of file amxp_cron.c.

673  {
674  int64_t seconds = -1;
675  amxc_ts_t now;
676  amxc_ts_t next = { 0, 0, 0 };
677 
678  when_null(expr, exit);
679  amxc_ts_now(&now);
680  if(local) {
681  amxc_ts_to_local(&now);
682  }
683 
684  when_failed(amxp_cron_next(expr, &now, &next), exit);
685  seconds = next.sec - now.sec;
686 
687 exit:
688  return seconds;
689 }