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
- Pass an IANA identifier: 'Europe/Berlin', 'America/New_York', or 'UTC'
- For offsets, construct the object yourself and pass the instance: new DateTimeZone('+02:00') — instances are returned as-is by validateTimeZone
- Check the return value of Date::setDefaultTimeZone(); false means the same validation failed
- 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
- Pass IANA identifiers or DateTimeZone instances, never offsets like 'UTC+2'
- Check Date::setDefaultTimeZone()'s bool return — it does not throw
- Centralize timezone normalization for user-provided settings
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.