<commit_before><?php
namespace Concrete\Core\Localization\Service;

use Concrete\Core\Config\Repository\Repository;
use Concrete\Core\Entity\Site\Site;
use Concrete\Core\Http\Request;
use Concrete\Core\Localization\Localization;
use Concrete\Core\Support\Facade\Facade;
use Concrete\Core\User\User;
use Punic\Calendar;
use Punic\Comparer;
use Punic\Misc;

class Date
{
    /**
     * The PHP date/time format string to be used when dealing with the database.
     *
     * @var string
     *
     * @see http://php.net/manual/function.date.php
     */
    const DB_FORMAT = 'Y-m-d H:i:s';

    /**
     * Convert any date/time representation to a string that can be used in DB queries.
     *
     * @param string|int|\DateTime $value It can be:<ul>
     *    <li>the special value 'now' (default) to return the current date/time</li>
     *    <li>a \DateTime instance</li>
     *    <li>a string parsable by strtotime (the $fromTimezone timezone is used)</li>
     *    <li>a timestamp</li>
     * </ul>
     * @param string $fromTimezone The timezone where $value is defined (useful only if $value is a date/time representation without a timezone, like for instance '2000-12-31 23:59:59').<br />
     * Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     * @param string Returns the date/time representation (an empty string if $value is empty)
     *
     * @return string
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function toDB($value = 'now', $fromTimezone = 'system')
    {
        $datetime = $this->toDateTime($value, 'system', $fromTimezone);

        return is_object($datetime) ? $datetime->format(static::DB_FORMAT) : '';
    }

    /**
     * Return the date/time representation for now, that can be overridden by a custom request when viewing pages in a moment specified by administrators (custom request date/time).
     *
     * @param bool $asTimestamp Set to true to retrieve the Unix timestamp, false to retrieve the string representation (eg '2000-12-31 23:59:59')
     *
     * @return string|int
     */
    public function getOverridableNow($asTimestamp = false)
    {
        $req = Facade::getFacadeApplication()->make(Request::class);
        if ($req->hasCustomRequestUser() && $req->getCustomRequestDateTime()) {
            $timestamp = strtotime($req->getCustomRequestDateTime());
        } else {
            $timestamp = time();
        }

        return $asTimestamp ? $timestamp : date(static::DB_FORMAT, $timestamp);
    }

    /**
     * Subsitute for the native date() function that adds localized date support.
     * Use *ONLY* if really needed: you may want to use some of the formatDate/Time methods.
     * If you're not working with timestamps you may want to use the formatCustom method.
     *
     * @param string $mask The PHP format mask
     * @param bool|int $timestamp Use false for the current date/time, otherwise a valid Unix timestamp (we assume it's in the system timezone)
     * @param string $toTimezone The destination timezone.<br />
     * Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string
     *
     * @throws \Punic\Exception
     *
     * @see http://php.net/manual/function.date.php
     */
    public function date($mask, $timestamp = false, $toTimezone = 'system')
    {
        if ($timestamp === false) {
            $timestamp = time();
        }
        $result = '';
        $datetime = $this->toDateTime($timestamp, $toTimezone);
        if (is_object($datetime)) {
            if (Localization::activeLocale() == Localization::BASE_LOCALE) {
                $result = $datetime->format($mask);
            } else {
                $result = Calendar::format(
                    $datetime,
                    Calendar::convertPhpToIsoFormat($mask)
                );
            }
        }

        return $result;
    }

    /**
     * Retrieve the display name (localized) of a time zone given its PHP identifier.
     *
     * @param string $timezoneID
     *
     * @return string
     */
    public function getTimezoneName($timezoneID)
    {
        $result = '';
        if (is_string($timezoneID) && $timezoneID !== '') {
            $names = $this->getTimezones();
            $result = isset($names[$timezoneID]) ? $names[$timezoneID] : $timezoneID;
        }

        return $result;
    }

    /**
     * Returns a keyed array of timezone identifiers (keys are the standard PHP timezone names, values are the localized timezone names).
     *
     * @return array
     *
     * @see http://www.php.net/datetimezone.listidentifiers.php
     */
    public function getTimezones()
    {
        static $cache = [];
        $locale = Localization::activeLocale();
        if (array_key_exists($locale, $cache)) {
            $result = $cache[$locale];
        } else {
            $result = [];
            $continentNames = [
                'Africa' => \Punic\Territory::getName('002'),
                'Asia' => \Punic\Territory::getName('142'),
                'America' => \Punic\Territory::getName('019'),
                'Antarctica' => \Punic\Territory::getName('AQ'),
                'Arctic' => t('Arctic'),
                'Atlantic' => t('Atlantic Ocean'),
                'Australia' => \Punic\Territory::getName('AU'),
                'Europe' => \Punic\Territory::getName('150'),
                'Indian' => t('Indian Ocean'),
                'Pacific' => t('Pacific Ocean'),
            ];
            foreach (\DateTimeZone::listIdentifiers() as $timezoneID) {
                switch ($timezoneID) {
                    case 'UTC':
                    case 'GMT':
                        $timezoneName = t(/*i18n: %s is an acronym like UTC, GMT, ... */'Greenwich Mean Time (%s)', $timezoneID);
                        break;
                    default:
                        $chunks = explode('/', $timezoneID);
                        if (array_key_exists($chunks[0], $continentNames)) {
                            $chunks[0] = $continentNames[$chunks[0]];
                        }
                        if (count($chunks) > 0) {
                            $city = Calendar::getTimezoneExemplarCity($timezoneID, false);
                            if (!strlen($city)) {
                                switch ($timezoneID) {
                                    case 'America/Fort_Nelson':
                                        $city = tc(/*i18n: Canadian territory */'Territory', 'Fort Nelson');
                                        break;
                                    case 'America/Montreal':
                                        $city = tc(/*i18n: Canadian city */'Territory', 'Montreal');
                                        break;
                                    case 'America/Shiprock':
                                        $city = tc(/*i18n: Territory in New Mexico (USA) */'Territory', 'Shiprock');
                                        break;
                                    case 'Antarctica/South_Pole':
                                        $city = tc(/*i18n: The South Pole */'Territory', 'South Pole');
                                        break;
                                    case 'Asia/Atyrau':
                                        $city = tc(/*i18n: Kazakh territory */'Territory', 'Atyrau');
                                        break;
                                    case 'Asia/Barnaul':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Barnaul');
                                        break;
                                    case 'Asia/Famagusta':
                                        $city = tc(/*i18n: City in Cyprus Island */'Territory', 'Famagusta');
                                        break;
                                    case 'Asia/Tomsk':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Tomsk');
                                        break;
                                    case 'Asia/Yangon':
                                        $city = tc(/*i18n: Burmese city */'Territory', 'Yangon');
                                        break;
                                    case 'Europe/Astrakhan':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Astrakhan');
                                        break;
                                    case 'Europe/Kirov':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Kirov');
                                        break;
                                    case 'Europe/Saratov':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Saratov');
                                        break;
                                    case 'Europe/Ulyanovsk':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Ulyanovsk');
                                        break;
                                }
                            }
                            if (strlen($city)) {
                                $chunks = [$chunks[0], $city];
                            }
                        }
                        $timezoneName = implode('/', $chunks);
                        break;
                }
                $result[$timezoneID] = $timezoneName;
            }
            $comparer = new Comparer();
            $comparer->sort($result, true);
            $cache[$locale] = $result;
        }

        return $result;
    }

    /**
     * Returns the list of timezones with translated names, grouped by region.
     *
     * @return array
     *
     * @example
     * <pre>[
     *     'Africa' => [
     *         'Africa/Abidjan' => 'Abidjan',
     *         'Africa/Addis_Ababa' => 'Addis Abeba',
     *     ],
     *     'Americas' => [
     *         'America/North_Dakota/Beulah' => 'Beulah, North Dakota',
     *     ],
     *     'Antarctica' => [
     *         'Antarctica/McMurdo' => 'McMurdo',
     *     ],
     *     'Arctic' => [
     *         ...
     *     ],
     *     'Asia' => [
     *         ....
     *     ],
     *     'Atlantic Ocean' => [
     *         ....
     *     ],
     *     'Australia' => [
     *         ....
     *     ],
     *     'Europe' => [
     *         ....
     *     ],
     *     'Indian Ocean' => [
     *         ....
     *     ],
     *     'Pacific Ocean' => [
     *         ....
     *     ],
     *     'Others' => [
     *         'UTC' => 'Greenwich Mean Time (UTC)',
     *     ],
     *  ]</pre>
     *
     * @see http://www.php.net/datetimezone.listidentifiers.php
     */
    public function getGroupedTimezones()
    {
        $groups = [];
        $generics = [];
        $genericGroupName = tc('GenericTimezonesGroupName', 'Others');
        foreach ($this->getTimezones() as $id => $fullName) {
            $chunks = explode('/', $fullName, 2);
            if (!isset($chunks[1])) {
                array_unshift($chunks, $genericGroupName);
            }
            list($groupName, $territoryName) = $chunks;
            if ($groupName === $genericGroupName) {
                $generics[$id] = $territoryName;
            } else {
                if (!isset($groups[$groupName])) {
                    $groups[$groupName] = [];
                }
                $groups[$groupName][$id] = $territoryName;
            }
        }
        if (!empty($generics)) {
            $groups[$genericGroupName] = $generics;
        }

        return $groups;
    }

    /**
     * Returns the display name of a timezone.
     *
     * @param string|\DateTimeZone|\DateTime $timezone The timezone for which you want the localized display name
     *
     * @return string
     */
    public function getTimezoneDisplayName($timezone)
    {
        $displayName = '';
        if (is_object($timezone)) {
            if ($timezone instanceof \DateTimeZone) {
                $displayName = $timezone->getName();
            } elseif ($timezone instanceof \DateTime) {
                $displayName = $timezone->getTimezone()->getName();
            }
        } elseif (is_string($timezone)) {
            $displayName = $timezone;
        }
        if (strlen($displayName) > 0) {
            $timezones = $this->getTimezones();
            if (array_key_exists($displayName, $timezones)) {
                $displayName = $timezones[$displayName];
            }
        }

        return $displayName;
    }

    /**
     * Describe the difference in time between now and a date/time in the past.
     * If the date/time is in the future or if it's more than one year old, you'll get the date representation of $posttime.
     *
     * @param int $posttime The timestamp to analyze
     * @param bool $precise = false Set to true to a more verbose and precise result, false for a more rounded result
     *
     * @return string
     *
     * @throws \Punic\Exception
     */
    public function timeSince($posttime, $precise = false)
    {
        $diff = time() - $posttime;
        if (($diff < 0) || ($diff > 365 * 24 * 60 * 60)) {
            return $this->formatDate($posttime, false);
        } else {
            return $this->describeInterval($diff, $precise);
        }
    }

    /**
     * Returns the localized representation of a time interval specified as seconds.
     *
     * @param int $diff The time difference in seconds
     * @param bool $precise = false Set to true to a more verbose and precise result, false for a more rounded result
     *
     * @return string
     */
    public function describeInterval($diff, $precise = false)
    {
        $secondsPerMinute = 60;
        $secondsPerHour = 60 * $secondsPerMinute;
        $secondsPerDay = 24 * $secondsPerHour;
        $days = floor($diff / $secondsPerDay);
        $diff = $diff - $days * $secondsPerDay;
        $hours = floor($diff / $secondsPerHour);
        $diff = $diff - $hours * $secondsPerHour;
        $minutes = floor($diff / $secondsPerMinute);
        $seconds = $diff - $minutes * $secondsPerMinute;
        $chunks = [];
        if ($days > 0) {
            $chunks[] = t2('%d day', '%d days', $days, $days);
            if ($precise) {
                $chunks[] = t2('%d hour', '%d hours', $hours, $hours);
            }
        } elseif ($hours > 0) {
            $chunks[] = t2('%d hour', '%d hours', $hours, $hours);
            if ($precise) {
                $chunks[] = t2('%d minute', '%d minutes', $minutes, $minutes);
            }
        } elseif ($minutes > 0) {
            $chunks[] = t2('%d minute', '%d minutes', $minutes, $minutes);
            if ($precise) {
                $chunks[] = t2('%d second', '%d seconds', $seconds, $seconds);
            }
        } else {
            $chunks[] = t2('%d second', '%d seconds', $seconds, $seconds);
        }

        return Misc::joinAnd($chunks);
    }

    /**
     * Returns the normalized timezone identifier.
     *
     * @param string $timezone The timezone to retrieve. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string
     */
    public function getTimezoneID($timezone)
    {
        $app = Facade::getFacadeApplication();
        /** @var Repository $config */
        $config = $app->make('config');

        switch ($timezone) {
            case 'system':
                $timezone = $config->get('app.server_timezone', @date_default_timezone_get() ?: 'UTC');
                break;
            case 'app':
                /** @var Site $site */
                $site = $app->make('site')->getSite();
                $timezone = $site->getConfigRepository()->get('timezone', @date_default_timezone_get() ?: 'UTC');
                break;
            case 'user':
                $tz = null;
                if ($config->get('concrete.misc.user_timezones')) {
                    $u = null;
                    $request = null;
                    if (!$app->isRunThroughCommandLineInterface()) {
                        $request = Request::getInstance();
                    }
                    if ($request && $request->hasCustomRequestUser()) {
                        $u = $request->getCustomRequestUser();
                    } else {
                        $u = new User();
                    }
                    if (is_object($u) && $u->isRegistered()) {
                        $tz = $u->getUserTimezone();
                    }
                }
                if ($tz) {
                    $timezone = $tz;
                } else {
                    $timezone = $this->getTimezoneID('app');
                }
                break;
        }

        return $timezone;
    }

    /**
     * @return string
     */
    public function getUserTimeZoneID()
    {
        return $this->getTimezoneID('user');
    }

    /**
     * Returns a \DateTimeZone instance for a specified timezone identifier.
     *
     * @param string $timezone The timezone to retrieve. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return \DateTimeZone|null Returns null if $timezone is invalid or the \DateTimeZone corresponding to $timezone
     */
    public function getTimezone($timezone)
    {
        $tz = null;
        $phpTimezone = $this->getTimezoneID($timezone);
        if (is_string($phpTimezone) && strlen($phpTimezone)) {
            try {
                $tz = new \DateTimeZone($phpTimezone);
            } catch (\Exception $x) {
            }
        }

        return $tz;
    }

    /**
     * Convert a date to a \DateTime instance.
     *
     * @param string|\DateTime|int $value It can be:<ul>
     *    <li>the special value 'now' (default) to return the current date/time</li>
     *    <li>a \DateTime instance</li>
     *    <li>a string parsable by strtotime (the $fromTimezone timezone is used)</li>
     *    <li>a timestamp</li>
     * </ul>
     * @param string $toTimezone The timezone to set. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     * @param string $fromTimezone The original timezone of $value (useful only if $value is a string like '2000-12-31 23:59'); it accepts the same values as $toTimezone
     *
     * @return \DateTime|null Returns the \DateTime instance (or null if $value couldn't be parsed)
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function toDateTime($value = 'now', $toTimezone = 'system', $fromTimezone = 'system')
    {
        return Calendar::toDateTime($value, $this->getTimezone($toTimezone), $this->getTimezone($fromTimezone));
    }

    /**
     * Returns the difference in days between to dates.
     *
     * @param mixed $from The start date/time representation (one of the values accepted by toDateTime)
     * @param mixed $to The end date/time representation (one of the values accepted by toDateTime)
     * @param string $timezone The timezone to set. Special values are:<ul>
     *    <li>'system' for the current system timezone</li>
     *    <li>'user' (default) for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return int|null Returns the difference in days (less than zero if $dateFrom if greater than $dateTo).
     * Returns null if one of both the dates can't be parsed
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function getDeltaDays($from, $to, $timezone = 'user')
    {
        $dtFrom = $this->toDateTime($from, $timezone);
        $dtTo = $this->toDateTime($to, $timezone);
        if (is_null($dtFrom) || is_null($dtTo)) {
            return null;
        }

        $seconds = $dtTo->getTimestamp() - $dtFrom->getTimestamp();

        return round($seconds / 86400);
    }

    /**
     * Render the date part of a date/time as a localized string.
     *
     * @param mixed $value $The date/time representation (one of the values accepted by toDateTime)
     * @param string $format The format name; it can be 'full' (eg 'EEEE, MMMM d, y' - 'Wednesday, August 20, 2014'), 'long' (eg 'MMMM d, y' - 'August 20, 2014'), 'medium' (eg 'MMM d, y' - 'August 20, 2014') or 'short' (eg 'M/d/yy' - '8/20/14'),
     *                      or a skeleton pattern prefixed by '~', e.g. '~yMd'.
     *                      You can also append a caret ('^') or an asterisk ('*') to $width. If so, special day names may be used (like 'Today', 'Yesterday', 'Tomorrow' with '^' and 'today', 'yesterday', 'tomorrow' width '*') instead of the date.
     * @param string $toTimezone The timezone to set. Special values are:<ul>
     *     <li>'system' for the current system timezone</li>
     *     <li>'user' (default) for the user's timezone</li>
     *     <li>'app' for the app's timezone</li>
     *     <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string Returns an empty string if $value couldn't be parsed, the localized string otherwise
     *
     * @throws \Punic\Exception
     */
    public function formatDate($value = 'now', $format = 'short', $toTimezone = 'user')
    {
        // legacy
        if ($format === true) {<commit_msg>Fix #7446<commit_after><?php
namespace Concrete\Core\Localization\Service;

use Concrete\Core\Config\Repository\Repository;
use Concrete\Core\Entity\Site\Site;
use Concrete\Core\Http\Request;
use Concrete\Core\Localization\Localization;
use Concrete\Core\Support\Facade\Facade;
use Concrete\Core\User\User;
use Punic\Calendar;
use Punic\Comparer;
use Punic\Misc;

class Date
{
    /**
     * The PHP date/time format string to be used when dealing with the database.
     *
     * @var string
     *
     * @see http://php.net/manual/function.date.php
     */
    const DB_FORMAT = 'Y-m-d H:i:s';

    /**
     * Convert any date/time representation to a string that can be used in DB queries.
     *
     * @param string|int|\DateTime $value It can be:<ul>
     *    <li>the special value 'now' (default) to return the current date/time</li>
     *    <li>a \DateTime instance</li>
     *    <li>a string parsable by strtotime (the $fromTimezone timezone is used)</li>
     *    <li>a timestamp</li>
     * </ul>
     * @param string $fromTimezone The timezone where $value is defined (useful only if $value is a date/time representation without a timezone, like for instance '2000-12-31 23:59:59').<br />
     * Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     * @param string Returns the date/time representation (an empty string if $value is empty)
     *
     * @return string
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function toDB($value = 'now', $fromTimezone = 'system')
    {
        $datetime = $this->toDateTime($value, 'system', $fromTimezone);

        return is_object($datetime) ? $datetime->format(static::DB_FORMAT) : '';
    }

    /**
     * Return the date/time representation for now, that can be overridden by a custom request when viewing pages in a moment specified by administrators (custom request date/time).
     *
     * @param bool $asTimestamp Set to true to retrieve the Unix timestamp, false to retrieve the string representation (eg '2000-12-31 23:59:59')
     *
     * @return string|int
     */
    public function getOverridableNow($asTimestamp = false)
    {
        $req = Facade::getFacadeApplication()->make(Request::class);
        if ($req->hasCustomRequestUser() && $req->getCustomRequestDateTime()) {
            $timestamp = strtotime($req->getCustomRequestDateTime());
        } else {
            $timestamp = time();
        }

        return $asTimestamp ? $timestamp : date(static::DB_FORMAT, $timestamp);
    }

    /**
     * Subsitute for the native date() function that adds localized date support.
     * Use *ONLY* if really needed: you may want to use some of the formatDate/Time methods.
     * If you're not working with timestamps you may want to use the formatCustom method.
     *
     * @param string $mask The PHP format mask
     * @param bool|int $timestamp Use false for the current date/time, otherwise a valid Unix timestamp (we assume it's in the system timezone)
     * @param string $toTimezone The destination timezone.<br />
     * Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string
     *
     * @throws \Punic\Exception
     *
     * @see http://php.net/manual/function.date.php
     */
    public function date($mask, $timestamp = false, $toTimezone = 'system')
    {
        if ($timestamp === false) {
            $timestamp = time();
        }
        $result = '';
        $datetime = $this->toDateTime($timestamp, $toTimezone);
        if (is_object($datetime)) {
            if (Localization::activeLocale() == Localization::BASE_LOCALE) {
                $result = $datetime->format($mask);
            } else {
                $result = Calendar::format(
                    $datetime,
                    Calendar::convertPhpToIsoFormat($mask)
                );
            }
        }

        return $result;
    }

    /**
     * Retrieve the display name (localized) of a time zone given its PHP identifier.
     *
     * @param string $timezoneID
     *
     * @return string
     */
    public function getTimezoneName($timezoneID)
    {
        $result = '';
        if (is_string($timezoneID) && $timezoneID !== '') {
            $names = $this->getTimezones();
            $result = isset($names[$timezoneID]) ? $names[$timezoneID] : $timezoneID;
        }

        return $result;
    }

    /**
     * Returns a keyed array of timezone identifiers (keys are the standard PHP timezone names, values are the localized timezone names).
     *
     * @return array
     *
     * @see http://www.php.net/datetimezone.listidentifiers.php
     */
    public function getTimezones()
    {
        static $cache = [];
        $locale = Localization::activeLocale();
        if (array_key_exists($locale, $cache)) {
            $result = $cache[$locale];
        } else {
            $result = [];
            $continentNames = [
                'Africa' => \Punic\Territory::getName('002'),
                'Asia' => \Punic\Territory::getName('142'),
                'America' => \Punic\Territory::getName('019'),
                'Antarctica' => \Punic\Territory::getName('AQ'),
                'Arctic' => t('Arctic'),
                'Atlantic' => t('Atlantic Ocean'),
                'Australia' => \Punic\Territory::getName('AU'),
                'Europe' => \Punic\Territory::getName('150'),
                'Indian' => t('Indian Ocean'),
                'Pacific' => t('Pacific Ocean'),
            ];
            foreach (\DateTimeZone::listIdentifiers() as $timezoneID) {
                switch ($timezoneID) {
                    case 'UTC':
                    case 'GMT':
                        $timezoneName = t(/*i18n: %s is an acronym like UTC, GMT, ... */'Greenwich Mean Time (%s)', $timezoneID);
                        break;
                    default:
                        $chunks = explode('/', $timezoneID);
                        if (array_key_exists($chunks[0], $continentNames)) {
                            $chunks[0] = $continentNames[$chunks[0]];
                        }
                        if (count($chunks) > 0) {
                            $city = Calendar::getTimezoneExemplarCity($timezoneID, false);
                            if (!strlen($city)) {
                                switch ($timezoneID) {
                                    case 'America/Fort_Nelson':
                                        $city = tc(/*i18n: Canadian territory */'Territory', 'Fort Nelson');
                                        break;
                                    case 'America/Montreal':
                                        $city = tc(/*i18n: Canadian city */'Territory', 'Montreal');
                                        break;
                                    case 'America/Shiprock':
                                        $city = tc(/*i18n: Territory in New Mexico (USA) */'Territory', 'Shiprock');
                                        break;
                                    case 'Antarctica/South_Pole':
                                        $city = tc(/*i18n: The South Pole */'Territory', 'South Pole');
                                        break;
                                    case 'Asia/Atyrau':
                                        $city = tc(/*i18n: Kazakh territory */'Territory', 'Atyrau');
                                        break;
                                    case 'Asia/Barnaul':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Barnaul');
                                        break;
                                    case 'Asia/Famagusta':
                                        $city = tc(/*i18n: City in Cyprus Island */'Territory', 'Famagusta');
                                        break;
                                    case 'Asia/Tomsk':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Tomsk');
                                        break;
                                    case 'Asia/Yangon':
                                        $city = tc(/*i18n: Burmese city */'Territory', 'Yangon');
                                        break;
                                    case 'Europe/Astrakhan':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Astrakhan');
                                        break;
                                    case 'Europe/Kirov':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Kirov');
                                        break;
                                    case 'Europe/Saratov':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Saratov');
                                        break;
                                    case 'Europe/Ulyanovsk':
                                        $city = tc(/*i18n: Russian city */'Territory', 'Ulyanovsk');
                                        break;
                                }
                            }
                            if (strlen($city)) {
                                $chunks = [$chunks[0], $city];
                            }
                        }
                        $timezoneName = implode('/', $chunks);
                        break;
                }
                $result[$timezoneID] = $timezoneName;
            }
            $comparer = new Comparer();
            $comparer->sort($result, true);
            $cache[$locale] = $result;
        }

        return $result;
    }

    /**
     * Returns the list of timezones with translated names, grouped by region.
     *
     * @return array
     *
     * @example
     * <pre>[
     *     'Africa' => [
     *         'Africa/Abidjan' => 'Abidjan',
     *         'Africa/Addis_Ababa' => 'Addis Abeba',
     *     ],
     *     'Americas' => [
     *         'America/North_Dakota/Beulah' => 'Beulah, North Dakota',
     *     ],
     *     'Antarctica' => [
     *         'Antarctica/McMurdo' => 'McMurdo',
     *     ],
     *     'Arctic' => [
     *         ...
     *     ],
     *     'Asia' => [
     *         ....
     *     ],
     *     'Atlantic Ocean' => [
     *         ....
     *     ],
     *     'Australia' => [
     *         ....
     *     ],
     *     'Europe' => [
     *         ....
     *     ],
     *     'Indian Ocean' => [
     *         ....
     *     ],
     *     'Pacific Ocean' => [
     *         ....
     *     ],
     *     'Others' => [
     *         'UTC' => 'Greenwich Mean Time (UTC)',
     *     ],
     *  ]</pre>
     *
     * @see http://www.php.net/datetimezone.listidentifiers.php
     */
    public function getGroupedTimezones()
    {
        $groups = [];
        $generics = [];
        $genericGroupName = tc('GenericTimezonesGroupName', 'Others');
        foreach ($this->getTimezones() as $id => $fullName) {
            $chunks = explode('/', $fullName, 2);
            if (!isset($chunks[1])) {
                array_unshift($chunks, $genericGroupName);
            }
            list($groupName, $territoryName) = $chunks;
            if ($groupName === $genericGroupName) {
                $generics[$id] = $territoryName;
            } else {
                if (!isset($groups[$groupName])) {
                    $groups[$groupName] = [];
                }
                $groups[$groupName][$id] = $territoryName;
            }
        }
        if (!empty($generics)) {
            $groups[$genericGroupName] = $generics;
        }

        return $groups;
    }

    /**
     * Returns the display name of a timezone.
     *
     * @param string|\DateTimeZone|\DateTime $timezone The timezone for which you want the localized display name
     *
     * @return string
     */
    public function getTimezoneDisplayName($timezone)
    {
        $displayName = '';
        if (is_object($timezone)) {
            if ($timezone instanceof \DateTimeZone) {
                $displayName = $timezone->getName();
            } elseif ($timezone instanceof \DateTime) {
                $displayName = $timezone->getTimezone()->getName();
            }
        } elseif (is_string($timezone)) {
            $displayName = $timezone;
        }
        if (strlen($displayName) > 0) {
            $timezones = $this->getTimezones();
            if (array_key_exists($displayName, $timezones)) {
                $displayName = $timezones[$displayName];
            }
        }

        return $displayName;
    }

    /**
     * Describe the difference in time between now and a date/time in the past.
     * If the date/time is in the future or if it's more than one year old, you'll get the date representation of $posttime.
     *
     * @param int $posttime The timestamp to analyze
     * @param bool $precise = false Set to true to a more verbose and precise result, false for a more rounded result
     *
     * @return string
     *
     * @throws \Punic\Exception
     */
    public function timeSince($posttime, $precise = false)
    {
        $diff = time() - $posttime;
        if (($diff < 0) || ($diff > 365 * 24 * 60 * 60)) {
            return $this->formatDate($posttime, false);
        } else {
            return $this->describeInterval($diff, $precise);
        }
    }

    /**
     * Returns the localized representation of a time interval specified as seconds.
     *
     * @param int $diff The time difference in seconds
     * @param bool $precise = false Set to true to a more verbose and precise result, false for a more rounded result
     *
     * @return string
     */
    public function describeInterval($diff, $precise = false)
    {
        $secondsPerMinute = 60;
        $secondsPerHour = 60 * $secondsPerMinute;
        $secondsPerDay = 24 * $secondsPerHour;
        $days = floor($diff / $secondsPerDay);
        $diff = $diff - $days * $secondsPerDay;
        $hours = floor($diff / $secondsPerHour);
        $diff = $diff - $hours * $secondsPerHour;
        $minutes = floor($diff / $secondsPerMinute);
        $seconds = $diff - $minutes * $secondsPerMinute;
        $chunks = [];
        if ($days > 0) {
            $chunks[] = t2('%d day', '%d days', $days, $days);
            if ($precise) {
                $chunks[] = t2('%d hour', '%d hours', $hours, $hours);
            }
        } elseif ($hours > 0) {
            $chunks[] = t2('%d hour', '%d hours', $hours, $hours);
            if ($precise) {
                $chunks[] = t2('%d minute', '%d minutes', $minutes, $minutes);
            }
        } elseif ($minutes > 0) {
            $chunks[] = t2('%d minute', '%d minutes', $minutes, $minutes);
            if ($precise) {
                $chunks[] = t2('%d second', '%d seconds', $seconds, $seconds);
            }
        } else {
            $chunks[] = t2('%d second', '%d seconds', $seconds, $seconds);
        }

        return Misc::joinAnd($chunks);
    }

    /**
     * Returns the normalized timezone identifier.
     *
     * @param string $timezone The timezone to retrieve. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string
     */
    public function getTimezoneID($timezone)
    {
        $app = Facade::getFacadeApplication();
        /** @var Repository $config */
        $config = $app->make('config');

        switch ($timezone) {
            case 'system':
                $timezone = $config->get('app.server_timezone', @date_default_timezone_get() ?: 'UTC');
                break;
            case 'app':
                /** @var Site $site */
                $site = $app->make('site')->getSite();
                $timezone = $site->getConfigRepository()->get('timezone', @date_default_timezone_get() ?: 'UTC');
                break;
            case 'user':
                $tz = null;
                if ($config->get('concrete.misc.user_timezones')) {
                    $u = null;
                    $request = null;
                    if (!$app->isRunThroughCommandLineInterface()) {
                        $request = Request::getInstance();
                    }
                    if ($request && $request->hasCustomRequestUser()) {
                        $u = $request->getCustomRequestUser();
                    } else {
                        $u = new User();
                    }
                    if (is_object($u) && $u->isRegistered()) {
                        $tz = $u->getUserTimezone();
                    }
                }
                if ($tz) {
                    $timezone = $tz;
                } else {
                    $timezone = $this->getTimezoneID('app');
                }
                break;
        }

        return $timezone;
    }

    /**
     * @return string
     */
    public function getUserTimeZoneID()
    {
        return $this->getTimezoneID('user');
    }

    /**
     * Returns a \DateTimeZone instance for a specified timezone identifier.
     *
     * @param string $timezone The timezone to retrieve. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return \DateTimeZone|null Returns null if $timezone is invalid or the \DateTimeZone corresponding to $timezone
     */
    public function getTimezone($timezone)
    {
        $tz = null;
        $phpTimezone = $this->getTimezoneID($timezone);
        if (is_string($phpTimezone) && strlen($phpTimezone)) {
            try {
                $tz = new \DateTimeZone($phpTimezone);
            } catch (\Exception $x) {
            }
        }

        return $tz;
    }

    /**
     * Convert a date to a \DateTime instance.
     *
     * @param string|\DateTime|int $value It can be:<ul>
     *    <li>the special value 'now' (default) to return the current date/time</li>
     *    <li>a \DateTime instance</li>
     *    <li>a string parsable by strtotime (the $fromTimezone timezone is used)</li>
     *    <li>a timestamp</li>
     * </ul>
     * @param string $toTimezone The timezone to set. Special values are:<ul>
     *    <li>'system' (default) for the current system timezone</li>
     *    <li>'user' for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     * @param string $fromTimezone The original timezone of $value (useful only if $value is a string like '2000-12-31 23:59'); it accepts the same values as $toTimezone
     *
     * @return \DateTime|null Returns the \DateTime instance (or null if $value couldn't be parsed)
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function toDateTime($value = 'now', $toTimezone = 'system', $fromTimezone = 'system')
    {
        return Calendar::toDateTime($value, $this->getTimezone($toTimezone), $this->getTimezone($fromTimezone));
    }

    /**
     * Returns the difference in days between to dates.
     *
     * @param mixed $from The start date/time representation (one of the values accepted by toDateTime)
     * @param mixed $to The end date/time representation (one of the values accepted by toDateTime)
     * @param string $timezone The timezone to set. Special values are:<ul>
     *    <li>'system' for the current system timezone</li>
     *    <li>'user' (default) for the user's timezone</li>
     *    <li>'app' for the app's timezone</li>
     *    <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return int|null Returns the difference in days (less than zero if $dateFrom if greater than $dateTo).
     * Returns null if one of both the dates can't be parsed
     *
     * @throws \Punic\Exception\BadArgumentType
     */
    public function getDeltaDays($from, $to, $timezone = 'user')
    {
        $dtFrom = $this->toDateTime($from, $timezone);
        $dtTo = $this->toDateTime($to, $timezone);
        if (is_null($dtFrom) || is_null($dtTo)) {
            return null;
        }

        // Re-create DateTime objects without time
        $dtFrom = $this->toDateTime($dtFrom->format('Y-m-d'), $timezone);
        $dtTo = $this->toDateTime($dtTo->format('Y-m-d'), $timezone);

        return (int) $dtFrom->diff($dtTo)->format('%r%a');
    }

    /**
     * Render the date part of a date/time as a localized string.
     *
     * @param mixed $value $The date/time representation (one of the values accepted by toDateTime)
     * @param string $format The format name; it can be 'full' (eg 'EEEE, MMMM d, y' - 'Wednesday, August 20, 2014'), 'long' (eg 'MMMM d, y' - 'August 20, 2014'), 'medium' (eg 'MMM d, y' - 'August 20, 2014') or 'short' (eg 'M/d/yy' - '8/20/14'),
     *                      or a skeleton pattern prefixed by '~', e.g. '~yMd'.
     *                      You can also append a caret ('^') or an asterisk ('*') to $width. If so, special day names may be used (like 'Today', 'Yesterday', 'Tomorrow' with '^' and 'today', 'yesterday', 'tomorrow' width '*') instead of the date.
     * @param string $toTimezone The timezone to set. Special values are:<ul>
     *     <li>'system' for the current system timezone</li>
     *     <li>'user' (default) for the user's timezone</li>
     *     <li>'app' for the app's timezone</li>
     *     <li>Other values: one of the PHP supported time zones (see http://us1.php.net/manual/en/timezones.php )</li>
     * </ul>
     *
     * @return string Returns an empty string if $value couldn't be parsed, the localized string otherwise
     *
     * @throws \Punic\Exception
     */
    public function formatDate($value = 'now', $format = 'short', $toTimezone = 'user')
    {
        // legacy
        if ($format === true) {
{'fraction_non_alphanumeric': 0.13317520973338604, 'fraction_numerical': 0.006522598497825801, 'mean_word_length': 1.6108021329052233, 'pattern_counts': {'":': 0, '<': 195, '<?xml version=': 0, '>': 337, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 106, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2261410', 'n_tokens_mistral': 13208, 'n_tokens_neox': 12566, 'n_words': 3383}