PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception

Invalid timezone

Error message

Invalid timezone

What it means

Date::validateTimeZone() accepts a DateTimeZone, null, or a string that must be one of DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC); anything else throws 'Invalid timezone'. It is reached publicly via Date::excelToDateTimeObject($ts, $timeZone) (src/PhpSpreadsheet/Shared/Date.php:200), which does not catch it; note Date::setDefaultTimezone() swallows the same exception and returns false instead.

Source

Thrown at src/PhpSpreadsheet/Shared/Date.php:155

    }

    /**
     * Validate a timezone.
     *
     * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
     *
     * @return ?DateTimeZone The timezone as a timezone object
     */
    private static function validateTimeZone($timeZone): ?DateTimeZone
    {
        if ($timeZone instanceof DateTimeZone || $timeZone === null) {
            return $timeZone;
        }
        if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
            return new DateTimeZone($timeZone);
        }

        throw new PhpSpreadsheetException('Invalid timezone');
    }

    /**
     * @param mixed $value Converts a date/time in ISO-8601 standard format date string to an Excel
     *                         serialized timestamp.
     *                     See https://en.wikipedia.org/wiki/ISO_8601 for details of the ISO-8601 standard format.
     */
    public static function convertIsoDate(mixed $value, ?int $calendar = null): float|int
    {
        if (!is_string($value)) {
            throw new Exception('Non-string value supplied for Iso Date conversion');
        }

        $date = new DateTime($value);
        $dateErrors = DateTime::getLastErrors();

        if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
            throw new Exception("Invalid string $value supplied for datatype Date");

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Pass an IANA identifier: 'Europe/Berlin', 'America/New_York', or 'UTC'
  2. For offsets, construct the object yourself and pass the instance: new DateTimeZone('+02:00') — instances are returned as-is by validateTimeZone
  3. Check the return value of Date::setDefaultTimeZone(); false means the same validation failed
  4. Validate strings against DateTimeZone::listIdentifiers() before calling

Example fix

// before
$dt = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject(44562, 'UTC+2'); // throws

// after
$tz = new \DateTimeZone('+02:00'); // numeric offsets are valid on the object
$dt = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject(44562, $tz);
Defensive patterns

Strategy: validation

Validate before calling

use PhpOffice\PhpSpreadsheet\Shared\Date;

function normalizeTz(string|\DateTimeZone|null $tz): ?\DateTimeZone
{
    if ($tz instanceof \DateTimeZone || $tz === null) {
        return $tz;
    }
    if (in_array($tz, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), true)) {
        return new \DateTimeZone($tz);
    }

    return new \DateTimeZone('UTC'); // or reject
}

$dt = Date::excelToDateTimeObject($serial, normalizeTz($userTz));

Type guard

function isValidTimeZoneName(string $tz): bool
{
    return in_array($tz, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), true);
}

Prevention

When it happens

Trigger: Date::excelToDateTimeObject(44562, 'UTC+2') or 'GMT+2', '+02:00', 'CT', 'EST' — offsets and abbreviations are not IANA identifiers and are not in the list; a typo'd identifier like 'Europe/Berline'.

Common situations: Copying timezone strings from JavaScript (moment/Intl offsets), DB timezone offsets, or user-profile '+HH:MM' values straight into the API; mixing setDefaultTimezone (returns bool) with per-call timezone arguments.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/351fa477bc202ae1. Report an issue: GitHub.