briannesbitt/Carbon · error · RuntimeException
Expected positive number of seconds, '.$seconds.' given
Error message
Expected positive number of seconds, '.$seconds.' given
What it means
WrapperClock::sleep() advances (or really sleeps, depending on the wrapped clock) by the given seconds; zero is a permitted no-op but any negative duration is meaningless for sleeping, so it throws RuntimeException. Because WrapperClock can wrap a real Symfony/PSR clock or a frozen DateTime/test factory, this guard protects both real sleep() and simulated time from moving backwards.
Source
Thrown at src/Carbon/WrapperClock.php:122
? ($timezone === null ? $now : $now->setTimezone($timezone))
: $this->dateAsCarbon($now, $timezone);
}
private function dateAsCarbon(DateTimeInterface $date, DateTimeZone|string|int|null $timezone): CarbonInterface
{
return $date instanceof DateTimeImmutable
? new CarbonImmutable($date, $timezone)
: new Carbon($date, $timezone);
}
public function sleep(float|int $seconds): void
{
if ($seconds === 0 || $seconds === 0.0) {
return;
}
if ($seconds < 0) {
throw new RuntimeException('Expected positive number of seconds, '.$seconds.' given');
}
if ($this->currentClock instanceof DateTimeInterface) {
$this->currentClock = $this->addSeconds($this->currentClock, $seconds);
return;
}
if ($this->currentClock instanceof ClockInterface) {
$this->currentClock->sleep($seconds);
return;
}
$this->currentClock = $this->addSeconds($this->currentClock->now(), $seconds);
}
public function withTimeZone(DateTimeZone|string $timezone): staticView on GitHub (pinned to b13f05955d)
Solutions
- Clamp the delay before sleeping: $clock->sleep(max(0, $delay))
- If the deadline already passed, branch explicitly (skip sleeping, log, or fail) instead of relying on sleep to signal it
- Compute delays as float seconds from a single now() reading taken as late as possible to shrink the race window
- Catch RuntimeException around third-party sleep calls and treat it as 'deadline passed' if clamping is not acceptable
Example fix
// before
$clock->sleep($resumeAt - $clock->now()->getTimestamp());
// after
$delay = max(0, $resumeAt - $clock->now()->getTimestamp());
if ($delay === 0 && $resumeAt < $clock->now()->getTimestamp()) {
// deadline already passed - decide explicitly
}
$clock->sleep($delay); Defensive patterns
Strategy: validation
Validate before calling
$delay = $resumeAt - $clock->now()->getTimestamp();
if ($delay < 0) {
// deadline already passed - skip sleeping and branch explicitly
return $onAlreadyDue();
}
$clock->sleep($delay); Type guard
function isNonNegativeDelay(float|int $seconds): bool
{
return $seconds >= 0;
} Try / catch
try {
$clock->sleep($delay);
} catch (\RuntimeException $e) {
// negative delay means deadline passed mid-computation
if ($delay < 0) {
return; // proceed immediately
}
throw $e;
} Prevention
- Always clamp: sleep(max(0, $delay)) - zero is an accepted no-op
- Take the now() reading as late as possible and from the same clock you sleep on
- Treat a negative computed delay as a signal the deadline expired, not as an error to silence
When it happens
Trigger: $clock->sleep(-1) directly; $clock->sleep($releaseAt - Carbon::now()->getTimestamp()) when the release moment already passed (negative difference); retry/backoff helpers computing $deadline - microtime(true) after a slow attempt; passing -0.5 floats from delta calculations; fuzz tests feeding boundary values expecting 0 to be the floor.
Common situations: Rate-limiter or lock-wait code where the target timestamp expired between computing and sleeping; concurrency races that make a computed delay negative; tests switching from a real clock to MockClock/WrapperClock exposing pre-existing negative-delay paths; timeouts configured as 'already elapsed'.
Related errors
- Unknown unit '$unit'.
- Invalid serialized value: $value
- Invalid unit for real timestamp add/sub: '$unit'
- Unable to add unit '.var_export($originalArgs, true)
- Unsupported unit '$unit'
AI-assisted analysis of briannesbitt/Carbon@b13f05955d (2026-08-17).
Data as JSON: /api/errors/b6e2f3f7da03a7f5.
Report an issue: GitHub.