carlossosa88/cron-expression
Fork of dragonmantank/cron-expression adding nonstandard seconds support for Fcron-style scheduling. Parse cron strings and macros, check if a schedule is due, and compute next/previous run dates with optional second-level precision control.
Installation:
composer require carlossosa88/cron-expression
Add to composer.json under require if not using Composer globally.
First Use Case: Validate if a predefined schedule is due and fetch the next run time:
use Cron\CronExpression;
$cron = CronExpression::factory('@daily');
if ($cron->isDue()) {
echo "Task is due now!";
}
echo $cron->getNextRunDate()->format('Y-m-d H:i:s'); // Next run time
Key Classes/Methods:
CronExpression::factory(): Parse a cron string (e.g., @hourly, 0 * * * * *).isDue(): Check if the current time matches the cron schedule.getNextRunDate(): Get the next occurrence (supports DateTime or DateTimeImmutable).getPreviousRunDate(): Get the last occurrence before a given time.Where to Look First:
Task Scheduling in Laravel:
Use with Laravel’s schedule:run command or custom cron jobs:
$schedule = CronExpression::factory('*/5 * * * * *'); // Every 5 seconds
if ($schedule->isDue()) {
// Execute task (e.g., sync data, send notifications).
}
Dynamic Cron Validation: Validate user-input cron expressions (e.g., in a settings panel):
try {
$cron = CronExpression::factory($userInput);
if ($cron->isValid()) {
// Store or use the expression.
}
} catch (\InvalidArgumentException $e) {
// Handle invalid syntax.
}
Timezone-Aware Scheduling:
Pass a DateTimeZone to getNextRunDate():
$tz = new DateTimeZone('America/New_York');
$nextRun = $cron->getNextRunDate(null, null, false, $tz);
Iterative Run Dates: Fetch future runs (e.g., for a calendar or preview):
$nextRun = $cron->getNextRunDate(null, 3); // 3rd occurrence from now.
Seconds Support:
Use non-standard cron fields (e.g., for fcron):
$cron = CronExpression::factory('* * * * * */2'); // Every 2 seconds.
$cron->isDue(new DateTime(), null, false); // Drop seconds = false.
Laravel Task Scheduling:
Combine with Laravel’s Artisan::schedule() for declarative cron jobs:
$schedule->command('backup:run')->cron('0 2 * * *'); // Daily at 2 AM.
Use carlossosa88/cron-expression to validate custom expressions.
Queue Workers: Check cron due status in queue jobs to trigger delayed tasks:
if ($cron->isDue()) {
dispatch(new ProcessPaymentJob());
}
Testing:
Mock DateTime for predictable tests:
$fixedTime = new DateTime('2023-01-01 00:00:00');
$this->assertTrue($cron->isDue($fixedTime));
Edge Cases: Handle invalid expressions gracefully:
try {
$cron = CronExpression::factory('invalid * * * *');
} catch (\InvalidArgumentException $e) {
report($e); // Log or notify admin.
}
Seconds Field Quirks:
seconds field (6th position) is non-standard. Ensure dropSeconds = false in isDue():
$cron->isDue(null, null, false); // Critical for seconds-based cron.
Timezone Handling:
DateTimeZone for consistency:
$nextRun = $cron->getNextRunDate(null, null, false, new DateTimeZone('UTC'));
2023-03-12 01:30:00 in ambiguous timezones).Leading Zeros:
05 * * * * (5 AM) are valid, but 5 * * * * may fail if not parsed correctly. Test with both formats.Step Validation:
*/99 in minutes) are allowed but may yield unexpected results. Validate steps explicitly:
if ($cron->getStep() > 60) {
throw new \RuntimeException('Invalid step size.');
}
Day-of-Month vs. Day-of-Week Conflicts:
15 * * * 1 (15th of the month and Monday) may not work as expected. Use ? or L (last day) for clarity:
// Last Monday of the month:
$cron = CronExpression::factory('0 0 L * 1');
Immutable DateTime:
DateTimeImmutable for thread safety in concurrent environments:
$nextRun = $cron->getNextRunDate(DateTimeImmutable::createFromFormat('Y-m-d', '2023-01-01'));
Error Messages:
Logging Run Dates:
getNextRunDate() outputs to verify schedules:
\Log::debug('Next run:', ['date' => $nextRun->format('Y-m-d H:i:s')]);
Timezone Debugging:
$cron->getNextRunDate(null, null, false, new DateTimeZone('UTC'));
Unit Testing:
31 12 * * * in April).2023-10-29 01:30:00 in America/New_York).Custom Validators:
Extend the parser to enforce project-specific rules (e.g., disallow * in hours):
if (strpos($expression, '* * * * *') !== false) {
throw new \InvalidArgumentException('Wildcard hours not allowed.');
}
Alternative Time Fields:
Override CronExpression to support custom fields (e.g., quarters):
class QuarterCronExpression extends CronExpression {
public static function factory($expression) {
// Add quarter logic (e.g., 'Q1' -> '1-3').
}
}
Performance Optimization: Cache parsed expressions for frequent checks:
static $cache = [];
if (!isset($cache[$expression])) {
$cache[$expression] = CronExpression::factory($expression);
}
Localization: Translate error messages for non-English environments:
$translator = app('translator');
$error = $translator->get('cron.invalid_expression', ['position' => 3]);
Default Timezone: The library uses the system default if no timezone is provided. Set it explicitly in your app:
date_default_timezone_set('UTC');
Max Iterations:
getNextRunDate() has no hardcoded limit, but very large steps (e.g., */10000) may cause performance issues. Add a safeguard:
if ($step > 1000) {
throw new \
How can I help you explore Laravel packages today?