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

  1. Pass the offset in hours within +/-14, or a '+05:30'-style string: CarbonTimeZone::create(-5), CarbonTimeZone::create('+05:30')
  2. Pass a timezone name: 'UTC', 'Europe/Paris', 'America/New_York'
  3. 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

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


AI-assisted analysis of briannesbitt/Carbon@b13f05955d (2026-08-17). Data as JSON: /api/errors/0cc91fe87a76a472. Report an issue: GitHub.