briannesbitt/Carbon · error · InvalidTimeZoneException
Absolute timezone offset cannot be greater than 99.
Error message
Absolute timezone offset cannot be greater than 99.
What it means
When a numeric value is passed to CarbonTimeZone (directly or via Carbon::now($tz) etc.), it is interpreted as an HOUR offset and formatted as '+H:MM' for PHP's DateTimeZone. Because DateTimeZone rejects offsets beyond roughly +/-100 hours, Carbon caps the absolute offset at MAXIMUM_TIMEZONE_OFFSET (99) and throws InvalidTimeZoneException above that (src/Carbon/CarbonTimeZone.php:41). Real-world offsets never exceed +/-14, so values above 99 are almost always a unit mistake.
Source
Thrown at src/Carbon/CarbonTimeZone.php:41
use Throwable;
class CarbonTimeZone extends DateTimeZone
{
use LocalFactory;
public const MAXIMUM_TIMEZONE_OFFSET = 99;
public function __construct(string|int|float $timezone)
{
$this->initLocalFactory();
parent::__construct(static::getDateTimeZoneNameFromMixed($timezone));
}
protected static function parseNumericTimezone(string|int|float $timezone): string
{
if (abs((float) $timezone) > static::MAXIMUM_TIMEZONE_OFFSET) {
throw new InvalidTimeZoneException(
'Absolute timezone offset cannot be greater than '.
static::MAXIMUM_TIMEZONE_OFFSET.'.',
);
}
return ($timezone >= 0 ? '+' : '').ltrim((string) $timezone, '+').':00';
}
protected static function getDateTimeZoneNameFromMixed(string|int|float $timezone): string
{
if (\is_string($timezone)) {
$timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone);
}
if (is_numeric($timezone)) {
return static::parseNumericTimezone($timezone);
}
View on GitHub (pinned to b13f05955d)
Solutions
- Pass the offset in hours within +/-14, or a '+05:30'-style string: CarbonTimeZone::create(-5), CarbonTimeZone::create('+05:30')
- Pass a timezone name: 'UTC', 'Europe/Paris', 'America/New_York'
- If the number is seconds, convert first: CarbonTimeZone::create($seconds / 3600) — or better, name the zone explicitly
Example fix
// before
$tz = new \Carbon\CarbonTimeZone(3600); // seconds mistaken for hours
// after
$tz = new \Carbon\CarbonTimeZone(1); // +01:00
// or explicit
$tz = new \Carbon\CarbonTimeZone('+01:00'); Defensive patterns
Strategy: validation
Validate before calling
if (is_numeric($tz) && \abs((float) $tz) > \Carbon\CarbonTimeZone::MAXIMUM_TIMEZONE_OFFSET) {
throw new InvalidArgumentException(
'Timezone offset must be hours within ±14 (got '.$tz.') — did you pass seconds or a timestamp?'
);
}
$timezone = new \Carbon\CarbonTimeZone($tz); Type guard
/** Narrow a timezone-ish value to a safe CarbonTimeZone input */
function toTimezoneInput(mixed $tz): string|int|float
{
if (is_numeric($tz) && \abs((float) $tz) > 14) {
throw new InvalidArgumentException('Numeric timezone must be an hour offset within ±14');
}
return $tz;
} Prevention
- Numeric timezone values are HOURS (±14 max in reality), not seconds or timestamps
- Prefer named zones ('Europe/Paris') or '+05:30' strings over raw numbers
- Never forward epoch timestamps into timezone parameters; check types at the boundary
When it happens
Trigger: new CarbonTimeZone(3600) (Unix seconds mistaken for hours); CarbonTimeZone::create(-100); Carbon::now(1690000000) (an epoch timestamp landing in the timezone parameter); numeric strings like '105'.
Common situations: Passing a UTC offset in seconds or minutes instead of hours; forwarding an epoch timestamp into a timezone argument; sign errors or duplicated minus signs ('--5'); timezone IDs (integers) confused with offsets.
Related errors
- Invalid number of recurrences.
- You cannot set {$unit} to a float value as {$name} would be
- Argument 1 passed to {$class}::{$method}() must be an instan
- Invalid ISO 8601 specification: {$iso}.
- $anchorDay parameter must not be set for $mode OverflowMode:
AI-assisted analysis of briannesbitt/Carbon@b13f05955d (2026-08-17).
Data as JSON: /api/errors/0cc91fe87a76a472.
Report an issue: GitHub.