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.