NAME

Langertha::Raider::Cron - Internal five-field cron expressions: validation, the next fire time and Claude Code's jitter

VERSION

version 0.504

SYNOPSIS

my $cron = 'Langertha::Raider::Cron';

my $error = $cron->validate('*/5 * * * *');            # undef, or the reason
my $at    = $cron->next( '0 9 * * 1-5', time );        # epoch of the next match, local time
my $when  = $cron->jittered( '7 * * * *', $at, key => 'cron_1', recurring => 1 );

DESCRIPTION

Internal module. Its interface may change without notice.

The cron arithmetic behind cron_create (ADR 0031): the five-field expressions Claude Code's CronCreate takes, in local time, and the jitter it adds to the run times. Everything works on epoch seconds, so a caller with a clock of its own (the scheduler's now) is not tied to the real time.

An expression is minute hour day-of-month month day-of-week. Every field takes *, a value, a range (1-5), a step (*/15, 1-30/5) and comma-separated lists of those. Day-of-week is 0 or 7 for Sunday up to 6 for Saturday. The extended syntax -- L, W, ?, # and the names MON or JAN -- is not supported. When both day-of-month and day-of-week are constrained, a day matches if either field matches (vixie cron).

parse

my $parsed = Langertha::Raider::Cron->parse('0 9 * * 1-5');
my ( $parsed, $error ) = Langertha::Raider::Cron->parse('61 * * * *');

The expression as a hash reference, or nothing and the reason: for each of minute, hour, dom, month and dow a hash reference of the values that match, and dom_star and dow_star (true when that field starts with *).

validate

my $error = Langertha::Raider::Cron->validate($text);

Undef for an expression that parses and matches some date within the next years, else the reason (expected five fields ..., does not match any date).

next

my $at = Langertha::Raider::Cron->next( $text, $epoch );

The first minute after $epoch that the expression matches, as epoch seconds in local time; nothing when there is none within eight years or the expression is invalid.

matches

my $yes = Langertha::Raider::Cron->matches( $text, $epoch );

True when the minute of $epoch matches the expression.

jitter

my $seconds = Langertha::Raider::Cron->jitter(
  $text, $scheduled, key => 'cron_1', recurring => 1,
);

The seconds added to the scheduled time $scheduled (epoch) of a job, as Claude Code shifts runs so that many sessions do not call the API at the same moment. The shift is a fixed fraction of the span allowed, taken from key (the job's ID), so the same job always runs the same number of seconds off:

  • A recurring job runs late by up to half the gap to its next run, at most 30 minutes (7,37 * * * * by 0 to 15 minutes, an hourly or daily job by 0 to 30).

  • Except a job that runs every five minutes (a gap of exactly 300 seconds): it runs 15 seconds sooner, so that a run does not start after the five-minute prompt cache has expired.

  • A one-shot job on the hour or the half hour runs up to 90 seconds early; any other minute is not shifted.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests at https://github.com/Getty/langertha-raider/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

https://raudssus.de/