config = array_merge($this->getDefaultConfig(), $config); $this->loader = new TimezoneLoader($this->config); $this->cache = new TimezoneCache($this->config); $this->index = new TimezoneIndex($this->config); $this->initializeDatabase(); } /** * Get timezone information. */ public function getTimezoneInfo(string $timezone): array { if (!isset($this->timezoneData[$timezone])) { $this->loadTimezoneData($timezone); } $data = $this->timezoneData[$timezone] ?? []; if (empty($data)) { throw new \InvalidArgumentException("Timezone not found: {$timezone}"); } return $data; } /** * Get timezone location information. */ public function getLocation(string $timezone): array { if (!isset($this->locationData[$timezone])) { $this->loadLocationData($timezone); } return $this->locationData[$timezone] ?? []; } /** * Get timezone metadata. */ public function getMetadata(string $timezone): array { if (!isset($this->metadata[$timezone])) { $this->loadMetadata($timezone); } return $this->metadata[$timezone] ?? []; } /** * Search timezones by criteria. */ public function search(array $criteria): array { return $this->index->search($criteria, $this->timezoneData); } /** * Get timezones by country. */ public function getTimezonesByCountry(string $countryCode): array { return $this->search(['country' => $countryCode]); } /** * Get timezones by region. */ public function getTimezonesByRegion(string $region): array { return $this->search(['region' => $region]); } /** * Get timezones by city. */ public function getTimezonesByCity(string $city): array { return $this->search(['city' => $city]); } /** * Get timezones by coordinates. */ public function getTimezonesByCoordinates(float $latitude, float $longitude, float $radius = 50): array { return $this->index->searchByCoordinates($latitude, $longitude, $radius, $this->timezoneData); } /** * Get timezone by IP address. */ public function getTimezoneByIP(string $ipAddress): ?string { $location = $this->getLocationByIP($ipAddress); if ($location) { $timezones = $this->getTimezonesByCoordinates( $location['latitude'], $location['longitude'], 100 ); return !empty($timezones) ? $timezones[0]['timezone'] : null; } return null; } /** * Get location by IP address. */ public function getLocationByIP(string $ipAddress): ?array { // This would integrate with a GeoIP service // For now, return a basic implementation return $this->loader->loadLocationByIP($ipAddress); } /** * Get popular timezones. */ public function getPopularTimezones(int $limit = 20): array { $popularTimezones = $this->config['popular_timezones'] ?? []; $result = []; foreach ($popularTimezones as $timezone) { if (isset($this->timezoneData[$timezone])) { $result[] = $this->timezoneData[$timezone]; } } return array_slice($result, 0, $limit); } /** * Get timezone offsets for all timezones at specific datetime. */ public function getAllOffsets(\DateTimeInterface $datetime = null): array { $datetime = $datetime ?? new \DateTime(); $offsets = []; foreach ($this->timezoneData as $timezone => $data) { try { $tz = new \DateTimeZone($timezone); $dt = new \DateTime($datetime->format('Y-m-d H:i:s'), $tz); $offsets[$timezone] = [ 'offset' => $tz->getOffset($dt), 'offset_hours' => $tz->getOffset($dt) / 3600, 'abbreviation' => $dt->format('T'), 'is_dst' => $dt->format('I') === '1' ]; } catch (\Exception $e) { // Skip invalid timezones continue; } } return $offsets; } /** * Get timezone conversion matrix. */ public function getConversionMatrix(array $timezones = null): array { $timezones = $timezones ?? array_keys($this->timezoneData); $matrix = []; $now = new \DateTime(); foreach ($timezones as $fromTz) { $matrix[$fromTz] = []; foreach ($timezones as $toTz) { if ($fromTz === $toTz) { $matrix[$fromTz][$toTz] = [ 'offset_difference' => 0, 'offset_hours' => 0, 'time_difference' => '00:00:00' ]; } else { $fromZone = new \DateTimeZone($fromTz); $toZone = new \DateTimeZone($toTz); $fromOffset = $fromZone->getOffset($now); $toOffset = $toZone->getOffset($now); $difference = $toOffset - $fromOffset; $hours = floor(abs($difference) / 3600); $minutes = floor((abs($difference) % 3600) / 60); $sign = $difference >= 0 ? '+' : '-'; $matrix[$fromTz][$toTz] = [ 'offset_difference' => $difference, 'offset_hours' => $difference / 3600, 'time_difference' => sprintf('%s%02d:%02d:00', $sign, $hours, $minutes) ]; } } } return $matrix; } /** * Get timezone groups. */ public function getTimezoneGroups(): array { return [ 'americas' => $this->getTimezonesByRegion('America'), 'europe' => $this->getTimezonesByRegion('Europe'), 'africa' => $this->getTimezonesByRegion('Africa'), 'asia' => $this->getTimezonesByRegion('Asia'), 'australia' => $this->getTimezonesByRegion('Australia'), 'pacific' => $this->getTimezonesByRegion('Pacific'), 'antarctica' => $this->getTimezonesByRegion('Antarctica'), 'arctic' => $this->getTimezonesByRegion('Arctic'), 'indian' => $this->getTimezonesByRegion('Indian'), 'atlantic' => $this->getTimezonesByRegion('Atlantic') ]; } /** * Get timezone by abbreviation. */ public function getTimezoneByAbbreviation(string $abbreviation, \DateTimeInterface $datetime = null): array { $datetime = $datetime ?? new \DateTime(); $matches = []; foreach ($this->timezoneData as $timezone => $data) { try { $tz = new \DateTimeZone($timezone); $dt = new \DateTime($datetime->format('Y-m-d H:i:s'), $tz); if ($dt->format('T') === $abbreviation) { $matches[] = [ 'timezone' => $timezone, 'offset' => $tz->getOffset($dt), 'is_dst' => $dt->format('I') === '1', 'data' => $data ]; } } catch (\Exception $e) { continue; } } return $matches; } /** * Get timezone statistics. */ public function getStatistics(): array { $stats = [ 'total_timezones' => count($this->timezoneData), 'total_countries' => count($this->getCountries()), 'total_regions' => count($this->getRegions()), 'total_cities' => count($this->getCities()), 'dst_observing_timezones' => 0, 'non_dst_observing_timezones' => 0, 'offset_distribution' => [], 'region_distribution' => [] ]; $now = new \DateTime(); foreach ($this->timezoneData as $timezone => $data) { try { $tz = new \DateTimeZone($timezone); $dt = new \DateTime($now->format('Y-m-d H:i:s'), $tz); if ($dt->format('I') === '1') { $stats['dst_observing_timezones']++; } else { $stats['non_dst_observing_timezones']++; } $offset = $tz->getOffset($dt) / 3600; $offsetKey = (string) $offset; $stats['offset_distribution'][$offsetKey] = ($stats['offset_distribution'][$offsetKey] ?? 0) + 1; $region = explode('/', $timezone)[0]; $stats['region_distribution'][$region] = ($stats['region_distribution'][$region] ?? 0) + 1; } catch (\Exception $e) { continue; } } ksort($stats['offset_distribution']); ksort($stats['region_distribution']); return $stats; } /** * Validate timezone data. */ public function validateTimezone(string $timezone): array { $errors = []; $warnings = []; try { $tz = new \DateTimeZone($timezone); $now = new \DateTime('now', $tz); // Basic validation if (!in_array($timezone, \DateTimeZone::listIdentifiers())) { $errors[] = "Timezone not in PHP's timezone list"; } // Check for data completeness if (!isset($this->timezoneData[$timezone])) { $warnings[] = "No extended data available for timezone"; } // Check for location data if (!isset($this->locationData[$timezone])) { $warnings[] = "No location data available for timezone"; } // Validate offset $offset = $tz->getOffset($now); if ($offset % 900 !== 0) { // Not aligned to 15-minute intervals $warnings[] = "Timezone offset not aligned to 15-minute intervals"; } } catch (\Exception $e) { $errors[] = "Invalid timezone: " . $e->getMessage(); } return [ 'valid' => empty($errors), 'errors' => $errors, 'warnings' => $warnings ]; } /** * Update timezone data. */ public function updateTimezoneData(string $timezone, array $data): void { $this->timezoneData[$timezone] = array_merge( $this->timezoneData[$timezone] ?? [], $data ); $this->index->updateIndex($timezone, $data); if ($this->config['cache_enabled']) { $this->cache->set($timezone, $this->timezoneData[$timezone]); } } /** * Add custom timezone. */ public function addCustomTimezone(string $timezone, array $data): void { if (isset($this->timezoneData[$timezone])) { throw new \InvalidArgumentException("Timezone already exists: {$timezone}"); } $this->updateTimezoneData($timezone, $data); } /** * Remove timezone. */ public function removeTimezone(string $timezone): void { unset($this->timezoneData[$timezone]); unset($this->locationData[$timezone]); unset($this->metadata[$timezone]); $this->index->removeFromIndex($timezone); if ($this->config['cache_enabled']) { $this->cache->delete($timezone); } } /** * Export timezone data. */ public function exportData(string $format = 'json'): string { $data = [ 'timezone_data' => $this->timezoneData, 'location_data' => $this->locationData, 'metadata' => $this->metadata, 'statistics' => $this->getStatistics(), 'exported_at' => date('Y-m-d H:i:s'), 'version' => $this->config['data_version'] ?? '1.0' ]; switch ($format) { case 'json': return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); case 'php': return 'exportToCSV($data); default: throw new \InvalidArgumentException("Unsupported export format: {$format}"); } } /** * Import timezone data. */ public function importData(string $data, string $format = 'json'): void { switch ($format) { case 'json': $imported = json_decode($data, true); break; case 'php': $imported = include 'data://text/plain;base64,' . base64_encode($data); break; default: throw new \InvalidArgumentException("Unsupported import format: {$format}"); } if (!$imported) { throw new \InvalidArgumentException("Invalid data format"); } if (isset($imported['timezone_data'])) { $this->timezoneData = array_merge($this->timezoneData, $imported['timezone_data']); } if (isset($imported['location_data'])) { $this->locationData = array_merge($this->locationData, $imported['location_data']); } if (isset($imported['metadata'])) { $this->metadata = array_merge($this->metadata, $imported['metadata']); } // Rebuild index $this->index->rebuildIndex($this->timezoneData); } /** * Get database version. */ public function getVersion(): string { return $this->config['data_version'] ?? '1.0'; } /** * Update database. */ public function updateDatabase(): void { $this->loader->updateDatabase(); $this->initializeDatabase(); } /** * Initialize database. */ protected function initializeDatabase(): void { // Load basic timezone data $this->loadBasicTimezoneData(); // Build search index $this->index->buildIndex($this->timezoneData); // Warm up cache if ($this->config['cache_enabled'] && $this->config['warmup_cache']) { $this->warmUpCache(); } } /** * Load basic timezone data. */ protected function loadBasicTimezoneData(): void { $identifiers = \DateTimeZone::listIdentifiers(); foreach ($identifiers as $timezone) { $this->timezoneData[$timezone] = [ 'timezone' => $timezone, 'identifier' => $timezone, 'region' => explode('/', $timezone)[0], 'city' => $this->extractCity($timezone), 'country' => $this->extractCountry($timezone) ]; } } /** * Load timezone data. */ protected function loadTimezoneData(string $timezone): void { if ($this->config['cache_enabled']) { $cached = $this->cache->get($timezone); if ($cached) { $this->timezoneData[$timezone] = $cached; return; } } $data = $this->loader->loadTimezoneData($timezone); if ($data) { $this->timezoneData[$timezone] = $data; if ($this->config['cache_enabled']) { $this->cache->set($timezone, $data); } } } /** * Load location data. */ protected function loadLocationData(string $timezone): void { $data = $this->loader->loadLocationData($timezone); if ($data) { $this->locationData[$timezone] = $data; } } /** * Load metadata. */ protected function loadMetadata(string $timezone): void { $data = $this->loader->loadMetadata($timezone); if ($data) { $this->metadata[$timezone] = $data; } } /** * Extract city from timezone. */ protected function extractCity(string $timezone): string { $parts = explode('/', $timezone); if (count($parts) >= 2) { return str_replace('_', ' ', end($parts)); } return $timezone; } /** * Extract country from timezone. */ protected function extractCountry(string $timezone): string { // This is a simplified country extraction // In practice, you'd use a more comprehensive mapping $countryMap = [ 'America' => 'US', 'Europe' => 'EU', 'Asia' => 'AS', 'Africa' => 'AF', 'Australia' => 'AU', 'Pacific' => 'OC' ]; $region = explode('/', $timezone)[0]; return $countryMap[$region] ?? 'Unknown'; } /** * Get countries. */ protected function getCountries(): array { $countries = []; foreach ($this->timezoneData as $data) { if (isset($data['country'])) { $countries[$data['country']] = true; } } return array_keys($countries); } /** * Get regions. */ protected function getRegions(): array { $regions = []; foreach ($this->timezoneData as $data) { if (isset($data['region'])) { $regions[$data['region']] = true; } } return array_keys($regions); } /** * Get cities. */ protected function getCities(): array { $cities = []; foreach ($this->timezoneData as $data) { if (isset($data['city'])) { $cities[$data['city']] = true; } } return array_keys($cities); } /** * Export to CSV. */ protected function exportToCSV(array $data): string { $csv = "Timezone,Region,City,Country,Latitude,Longitude\n"; foreach ($data['timezone_data'] as $timezone => $info) { $location = $data['location_data'][$timezone] ?? []; $csv .= sprintf( "%s,%s,%s,%s,%s,%s\n", $timezone, $info['region'] ?? '', $info['city'] ?? '', $info['country'] ?? '', $location['latitude'] ?? '', $location['longitude'] ?? '' ); } return $csv; } /** * Warm up cache. */ protected function warmUpCache(): void { $popularTimezones = $this->config['popular_timezones'] ?? array_slice(array_keys($this->timezoneData), 0, 50); foreach ($popularTimezones as $timezone) { if (isset($this->timezoneData[$timezone])) { $this->cache->set($timezone, $this->timezoneData[$timezone]); } } } /** * Clear cache. */ public function clearCache(): void { $this->cache->clear(); } /** * Get default configuration. */ protected function getDefaultConfig(): array { return [ 'cache_enabled' => true, 'cache_ttl' => 3600, 'warmup_cache' => true, 'data_version' => '1.0', 'popular_timezones' => [ 'UTC', 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Asia/Shanghai', 'Asia/Tokyo', 'Asia/Hong_Kong', 'Asia/Singapore', 'Australia/Sydney', 'Pacific/Auckland' ], 'data_sources' => [ 'tz_database' => true, 'geoip' => true, 'custom_data' => true ] ]; } /** * Get configuration. */ public function getConfig(): array { return $this->config; } /** * Set configuration. */ public function setConfig(array $config): void { $this->config = array_merge($this->config, $config); } /** * Create database instance. */ public static function create(array $config = []): self { return new self($config); } /** * Create for production. */ public static function forProduction(): self { return new self([ 'cache_enabled' => true, 'cache_ttl' => 7200, 'warmup_cache' => true, 'data_sources' => [ 'tz_database' => true, 'geoip' => true, 'custom_data' => false ] ]); } /** * Create for development. */ public static function forDevelopment(): self { return new self([ 'cache_enabled' => false, 'warmup_cache' => false, 'data_sources' => [ 'tz_database' => true, 'geoip' => false, 'custom_data' => true ] ]); } }