validate(); } /** * Create from timezone string */ public static function fromString(string $timezone): self { return new self($timezone); } /** * Create UTC timezone */ public static function utc(): self { return new self('UTC'); } /** * Create timezone for Europe/Berlin */ public static function berlin(): self { return new self('Europe/Berlin'); } /** * Create timezone for America/New_York */ public static function newYork(): self { return new self('America/New_York'); } /** * Create timezone for Asia/Tokyo */ public static function tokyo(): self { return new self('Asia/Tokyo'); } /** * Get DateTimeZone object */ public function toDateTimeZone(): DateTimeZone { return new DateTimeZone($this->value); } /** * Get timezone offset in seconds from UTC */ public function getOffsetFromUtc(): int { $timezone = $this->toDateTimeZone(); $now = new \DateTime('now', $timezone); return $timezone->getOffset($now); } /** * Get timezone offset as string (+/-HH:MM) */ public function getOffsetString(): string { $offset = $this->getOffsetFromUtc(); $hours = intval($offset / 3600); $minutes = abs(intval(($offset % 3600) / 60)); return sprintf('%+03d:%02d', $hours, $minutes); } /** * Check if timezone is UTC */ public function isUtc(): bool { return $this->value === 'UTC'; } /** * Check if timezone uses daylight saving time */ public function usesDaylightSaving(): bool { $timezone = $this->toDateTimeZone(); $transitions = $timezone->getTransitions(); // If there are transitions, it likely uses DST return count($transitions) > 1; } /** * Get timezone name */ public function getName(): string { return $this->value; } /** * Get timezone abbreviation */ public function getAbbreviation(): string { $timezone = $this->toDateTimeZone(); $now = new \DateTime('now', $timezone); return $now->format('T'); } /** * Get continent */ public function getContinent(): ?string { $parts = explode('/', $this->value); return $parts[0] ?? null; } /** * Get city/region */ public function getCity(): ?string { $parts = explode('/', $this->value); return $parts[1] ?? null; } /** * Check if this is a valid timezone for geographic region */ public function isValidForCountry(CountryCode $countryCode): bool { $countryTimezones = [ 'DE' => ['Europe/Berlin'], 'US' => [ 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Anchorage', 'Pacific/Honolulu', ], 'GB' => ['Europe/London'], 'FR' => ['Europe/Paris'], 'JP' => ['Asia/Tokyo'], 'CN' => ['Asia/Shanghai'], 'IN' => ['Asia/Kolkata'], 'RU' => [ 'Europe/Moscow', 'Asia/Yekaterinburg', 'Asia/Novosibirsk', 'Asia/Krasnoyarsk', 'Asia/Irkutsk', 'Asia/Yakutsk', 'Asia/Vladivostok', 'Asia/Magadan', 'Asia/Kamchatka', ], 'AU' => [ 'Australia/Sydney', 'Australia/Melbourne', 'Australia/Brisbane', 'Australia/Perth', 'Australia/Adelaide', 'Australia/Darwin', ], 'BR' => [ 'America/Sao_Paulo', 'America/Manaus', 'America/Fortaleza', 'America/Recife', 'America/Bahia', ], 'CA' => [ 'America/Toronto', 'America/Vancouver', 'America/Montreal', 'America/Calgary', 'America/Edmonton', 'America/Winnipeg', ], ]; $validTimezones = $countryTimezones[$countryCode->value] ?? []; return in_array($this->value, $validTimezones, true); } /** * Convert to array */ public function toArray(): array { return [ 'timezone' => $this->value, 'offset' => $this->getOffsetString(), 'offset_seconds' => $this->getOffsetFromUtc(), 'abbreviation' => $this->getAbbreviation(), 'continent' => $this->getContinent(), 'city' => $this->getCity(), 'is_utc' => $this->isUtc(), 'uses_daylight_saving' => $this->usesDaylightSaving(), ]; } /** * String representation */ public function toString(): string { return $this->value; } /** * Validate timezone */ private function validate(): void { try { new DateTimeZone($this->value); } catch (\Exception $e) { throw new InvalidArgumentException("Invalid timezone: {$this->value}"); } } public function __toString(): string { return $this->value; } }