radfusion/app/Services/TimezoneList.php
2025-01-29 10:45:41 +06:00

399 lines
12 KiB
PHP

<?php
namespace App\Services;
use DateTime;
use DateTimeZone;
final class TimezoneList
{
/**
* HTML entities.
*/
private const MINUS = '&#8722;';
private const PLUS = '&#43;';
private const WHITESPACE = '&#160;';
/**
* General timezones.
*
* @var array<string>
*/
protected array $generalTimezones = [
'GMT',
'UTC',
];
/**
* All continents of the world.
*
* @var array<string, string>
*/
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 = '<select name="' . (string) $name . '"' . $attrs . '>';
if ($this->includeGeneral()) {
$output .= '<optgroup label="General">';
foreach ($this->generalTimezones as $timezone) {
$output .= $this->makeOptionTag($this->formatTimezone($timezone, null, $htmlEncode), $timezone, ($selected == $timezone));
}
$output .= '</optgroup>';
}
foreach ($this->loadContinents() as $continent => $mask) {
$timezones = DateTimeZone::listIdentifiers($mask);
$output .= '<optgroup label="' . $continent . '">';
foreach ($timezones as $timezone) {
$output .= $this->makeOptionTag($this->formatTimezone($timezone, $continent, $htmlEncode), $timezone, ($selected == $timezone));
}
$output .= '</optgroup>';
}
$output .= '</select>';
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 = '<select name="' . (string) $name . '"' . $attrs . '>';
if ($this->includeGeneral()) {
foreach ($this->generalTimezones as $timezone) {
$output .= $this->makeOptionTag($this->formatTimezone($timezone, null, $htmlEncode), $timezone, ($selected == $timezone));
}
}
foreach ($this->loadContinents() as $continent => $mask) {
$timezones = DateTimeZone::listIdentifiers($mask);
foreach ($timezones as $timezone) {
$output .= $this->makeOptionTag($this->formatTimezone($timezone, null, $htmlEncode), $timezone, ($selected == $timezone));
}
}
$output .= '</select>';
return $output;
}
/**
* Generate the option HTML tag.
*/
protected function makeOptionTag(string $display, string $value, bool $selected = false): string
{
$attrs = (bool) $selected ? ' selected="selected"' : '';
return '<option value="' . $value . '"' . $attrs . '>' . $display . '</option>';
}
/**
* 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}";
}
}