From ed6e462f2ce3a8cd6089f206f93d43a4d27f4e69 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Wed, 29 Jan 2025 10:45:41 +0600 Subject: [PATCH] timezones --- app/Services/TimezoneList.php | 398 ++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 app/Services/TimezoneList.php diff --git a/app/Services/TimezoneList.php b/app/Services/TimezoneList.php new file mode 100644 index 0000000..6e247cd --- /dev/null +++ b/app/Services/TimezoneList.php @@ -0,0 +1,398 @@ + + */ + protected array $generalTimezones = [ + 'GMT', + 'UTC', + ]; + + /** + * All continents of the world. + * + * @var array + */ + protected array $continents = [ + 'Africa' => DateTimeZone::AFRICA, + 'America' => DateTimeZone::AMERICA, + 'Antarctica' => DateTimeZone::ANTARCTICA, + 'Arctic' => DateTimeZone::ARCTIC, + 'Asia' => DateTimeZone::ASIA, + 'Atlantic' => DateTimeZone::ATLANTIC, + 'Australia' => DateTimeZone::AUSTRALIA, + 'Europe' => DateTimeZone::EUROPE, + 'Indian' => DateTimeZone::INDIAN, + 'Pacific' => DateTimeZone::PACIFIC, + ]; + + /** + * The filter of the groups to get. + */ + protected array $groupsFilter = []; + + /** + * Status of grouping the return list. + */ + protected bool $splitGroup = true; + + /** + * Status of showing timezone offset. + */ + protected bool $showOffset = true; + + /** + * The offset prefix in list. + */ + protected string $offsetPrefix = 'GMT/UTC'; + + /** + * Set the filter of the groups want to get. + * + * @return $this + */ + public function onlyGroups(array $groups = []): static + { + $this->groupsFilter = $groups; + + return $this; + } + + /** + * Set the filter of the groups do not want to get. + * + * @return $this + */ + public function excludeGroups(array $groups = []): static + { + if (empty($groups)) { + $this->groupsFilter = []; + + return $this; + } + + $this->groupsFilter = array_values(array_diff(array_keys($this->continents), $groups)); + + if (! in_array('General', $groups)) { + $this->groupsFilter[] = 'General'; + } + + return $this; + } + + /** + * Decide whether to split group or not. + * + * @return $this + */ + public function splitGroup(bool $status = true): static + { + $this->splitGroup = (bool) $status; + + return $this; + } + + /** + * Decide whether to show the offset or not. + * + * @return $this + */ + public function showOffset(bool $status = true): static + { + $this->showOffset = (bool) $status; + + return $this; + } + + /** + * Return new static to reset all config. + * + * @return $this + */ + public function reset(): static + { + return new self; + } + + /** + * Create an array of timezones. + */ + public function toArray(bool $htmlEncode = true): mixed + { + $list = []; + + // If do not split group + if (! $this->splitGroup) { + if ($this->includeGeneral()) { + foreach ($this->generalTimezones as $timezone) { + $list[$timezone] = $this->formatTimezone($timezone, null, $htmlEncode); + } + } + + foreach ($this->loadContinents() as $continent => $mask) { + $timezones = DateTimeZone::listIdentifiers($mask); + + foreach ($timezones as $timezone) { + $list[$timezone] = $this->formatTimezone($timezone, null, $htmlEncode); + } + } + + return $list; + } + + // If split group + if ($this->includeGeneral()) { + foreach ($this->generalTimezones as $timezone) { + $list['General'][$timezone] = $this->formatTimezone($timezone, null, $htmlEncode); + } + } + + foreach ($this->loadContinents() as $continent => $mask) { + $timezones = DateTimeZone::listIdentifiers($mask); + + foreach ($timezones as $timezone) { + $list[$continent][$timezone] = $this->formatTimezone($timezone, $continent, $htmlEncode); + } + } + + return $list; + } + + /** + * Alias of the `toSelectBox()` method. + * + * @param string $name The name of the select tag + * @param string|null $selected The selected value + * @param array|string|null $attrs The HTML attributes of select tag + * @param bool $htmlEncode Use HTML entities for values of select tag + * + *@deprecated 6.0.0 This method name no longer matches the semantics + */ + public function create(string $name, ?string $selected = null, array|string|null $attrs = null, bool $htmlEncode = true): string + { + return $this->toSelectBox($name, $selected, $attrs, $htmlEncode); + } + + /** + * Create a select box of timezones. + * + * @param string $name The name of the select tag + * @param string|null $selected The selected value + * @param array|string|null $attrs The HTML attributes of select tag + * @param bool $htmlEncode Use HTML entities for values of select tag + * @return string + */ + public function toSelectBox(string $name, ?string $selected = null, array|string|null $attrs = null, bool $htmlEncode = true) + { + // Attributes for select element + $attrString = null; + + if (! empty($attrs)) { + if (is_array($attrs)) { + foreach ($attrs as $attr_name => $attr_value) { + $attrString .= ' ' . $attr_name . '="' . $attr_value . '"'; + } + } else { + $attrString = $attrs; + } + } + + if ($this->splitGroup) { + return $this->makeSelectTagWithGroup($name, $selected, $attrString, $htmlEncode); + } + + return $this->makeSelectTagWithoutGroup($name, $selected, $attrString, $htmlEncode); + } + + /** + * Generate select element with the optgroup tag. + * + * @param string $name The name of the select tag + * @param null|string $selected The selected value + * @param null|string $attrs The HTML attributes of select tag + * @param bool $htmlEncode Use HTML entities for values of select tag + * @return string + */ + protected function makeSelectTagWithGroup(string $name, ?string $selected = null, ?string $attrs = null, bool $htmlEncode = true) + { + $attrs = ! empty($attrs) ? ' ' . trim((string) $attrs) : ''; + $output = ''; + + return $output; + } + + /** + * Generate select element without the optgroup tag. + * + * @param string $name The name of the select tag + * @param null|string $selected The selected value + * @param null|string $attrs The HTML attributes of select tag + * @param bool $htmlEncode Use HTML entities for values of select tag + * @return string + */ + protected function makeSelectTagWithoutGroup(string $name, ?string $selected = null, ?string $attrs = null, bool $htmlEncode = true) + { + $attrs = ! empty($attrs) ? ' ' . trim((string) $attrs) : ''; + $output = ''; + + return $output; + } + + /** + * Generate the option HTML tag. + */ + protected function makeOptionTag(string $display, string $value, bool $selected = false): string + { + $attrs = (bool) $selected ? ' selected="selected"' : ''; + + return ''; + } + + /** + * DetermineCheck if the general timezones is loaded in the returned result. + */ + protected function includeGeneral(): bool + { + return empty($this->groupsFilter) || in_array('General', $this->groupsFilter); + } + + /** + * Load filtered continents. + */ + protected function loadContinents(): array + { + if (empty($this->groupsFilter)) { + return $this->continents; + } + + return array_filter($this->continents, function ($key) { + return in_array($key, $this->groupsFilter); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Format to display timezones. + */ + protected function formatTimezone(string $timezone, ?string $cutOffContinent = null, bool $htmlEncode = true): string + { + $displayedTimezone = empty($cutOffContinent) ? $timezone : substr($timezone, strlen($cutOffContinent) + 1); + $normalizedTimezone = $this->normalizeTimezone($displayedTimezone, $htmlEncode); + + if (! $this->showOffset) { + return $normalizedTimezone; + } + + $offset = $this->normalizeOffset($this->getOffset($timezone), $htmlEncode); + $separator = $this->normalizeSeparator($htmlEncode); + + return '(' . $this->offsetPrefix . $offset . ')' . $separator . $normalizedTimezone; + } + + /** + * Normalize the offset. + */ + protected function normalizeOffset(string $offset, bool $htmlEncode = true): string + { + $search = ['-', '+']; + $replace = $htmlEncode ? [' ' . self::MINUS . ' ', ' ' . self::PLUS . ' '] : [' - ', ' + ']; + + return str_replace($search, $replace, $offset); + } + + /** + * Normalize the timezone. + */ + protected function normalizeTimezone(string $timezone, bool $htmlEncode = true): string + { + $search = ['St_', '/', '_']; + $replace = ['St. ', ' / ', ' ']; + + return str_replace($search, $replace, $timezone); + } + + /** + * Normalize the separator between the timezone and offset. + */ + protected function normalizeSeparator(bool $htmlEncode = true): string + { + return $htmlEncode ? str_repeat(self::WHITESPACE, 5) : ' '; + } + + /** + * Get the timezone offset. + */ + protected function getOffset(string $timezone): string + { + $time = new DateTime('', new DateTimeZone($timezone)); + + return $time->format('P'); + } + + /** + * Get the difference of timezone to Coordinated Universal Time (UTC). + */ + protected function getUTCOffset(string $timezone): string + { + $dateTimeZone = new DateTimeZone($timezone); + $utcTime = new DateTime('', new DateTimeZone('UTC')); + $offset = $dateTimeZone->getOffset($utcTime); + $format = gmdate('H:i', abs($offset)); + + return $offset >= 0 ? "+{$format}" : "-{$format}"; + } +}