Your IP : 216.73.216.240


Current Path : /home/juvelize/saulnois/libraries/ic_library/Vendor/Icalcreator/src/Util/
Upload File :
Current File : /home/juvelize/saulnois/libraries/ic_library/Vendor/Icalcreator/src/Util/DateTimeZoneFactory.php

<?php
/**
 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
 *
 * This file is a part of iCalcreator.
 *
 * @author    Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @copyright 2007-2023 Kjell-Inge Gustafsson, kigkonsult AB, All rights reserved
 * @link      https://kigkonsult.se
 * @license   Subject matter of licence is the software iCalcreator.
 *            The above copyright, link, package and version notices,
 *            this licence notice and the invariant [rfc5545] PRODID result use
 *            as implemented and invoked in iCalcreator shall be included in
 *            all copies or substantial portions of the iCalcreator.
 *
 *            iCalcreator is free software: you can redistribute it and/or modify
 *            it under the terms of the GNU Lesser General Public License as
 *            published by the Free Software Foundation, either version 3 of
 *            the License, or (at your option) any later version.
 *
 *            iCalcreator is distributed in the hope that it will be useful,
 *            but WITHOUT ANY WARRANTY; without even the implied warranty of
 *            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *            GNU Lesser General Public License for more details.
 *
 *            You should have received a copy of the GNU Lesser General Public License
 *            along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
 */
declare( strict_types = 1 );
namespace Kigkonsult\Icalcreator\Util;

use DateTime;
use DateTimeZone;
use Exception;
use IntlTimeZone;
use InvalidArgumentException;
use Kigkonsult\Icalcreator\IcalInterface;
use RuntimeException;

use function ctype_digit;
use function floor;
use function in_array;
use function sprintf;
use function str_replace;
use function strlen;
use function substr;
use function strtolower;
use function timezone_name_from_abbr;
use function trim;

/**
 * iCalcreator DateTimeZone support class
 *
 * @since  2.27.8 - 2019-01-12
 */
class DateTimeZoneFactory
{

    /**
     * UTC variants, 'Z' first !!
     *
     * @var string[]
     */
    public static array $UTCARR = [
        IcalInterface::Z,
        'Etc/GMT',
        'Etc/GMT+0',
        'Etc/GMT-0',
        'Etc/GMT0',
        'Etc/Greenwich',
        'Etc/UCT',
        'Etc/Universal',
        'Etc/Zulu',
        IcalInterface::GMT,
        IcalInterface::UTC
    ];

    /**
     * @param string $tzString
     * @return bool
     */
    public static function isUtcTz( string $tzString ) : bool
    {
        return in_array( $tzString, self::$UTCARR, true );
    }

    /**
     * Return new DateTimeZone object instance
     *
     * @param string      $tzString
     * @param null|string $ymdHisString
     * @return DateTimeZone
     * @throws InvalidArgumentException|Exception
     * @since  2.41.70 - 2022-10-19
     */
    public static function factory( string $tzString, ? string $ymdHisString = null ) : DateTimeZone
    {
        return self::assertDateTimeZone( $tzString, $ymdHisString );
    }

    /**
     * Assert DateTimeZoneString, return DateTimeZone
     *
     * @param string      $tzString
     * @param null|string $ymdHisString
     * @return DateTimeZone
     * @throws InvalidArgumentException|Exception
     * @since  2.41.89 - 2024-01-16
     */
    public static function assertDateTimeZone( string $tzString, ? string $ymdHisString = null ) : DateTimeZone
    {
        static $ERR    = 'Invalid DateTimeZone \'%s\'';
        static $ERR2   = 'Invalid DateTime \'%s %s\' (%s)';
        if( empty( $tzString ) && ( 0 !== (int) $tzString)) {
            throw new InvalidArgumentException( sprintf( $ERR, $tzString ));
        }
        if( self::hasOffset( $tzString )) {
            $tzString = self::getTimeZoneNameFromOffset( $tzString );
        }
        elseif( self::isUtcTz( $tzString )) {
            $tzString = IcalInterface::UTC;
        }
        try {
            $timeZone = new DateTimeZone( $tzString );
            if( strtolower( $tzString ) !== strtolower( $timeZone->getName())) {
                throw new InvalidArgumentException( sprintf( $ERR, $tzString )); // some ms timezone may still be accepted!!
            }
        }
        catch( Exception $e ) {
            if( false === ( $tzString2 = IntlTimeZone::getIDForWindowsID( $tzString ))) {
                throw new InvalidArgumentException( sprintf( $ERR, $tzString ), $e->getCode(), $e );
            }
            if( self::isUtcTz( $tzString2 )) {
                $tzString2 = IcalInterface::UTC;
            }
            $timeZone = new DateTimeZone( $tzString2 );
            try {
                if( ! empty( $ymdHisString ) && // force UTC on empty offset
                    empty( $timeZone->getOffset( new DateTime( $ymdHisString, $timeZone )))) {
                    $timeZone = new DateTimeZone( IcalInterface::UTC );
                }
            }
            catch( Exception $e ) {
                throw new InvalidArgumentException(
                    sprintf( $ERR2, $ymdHisString, $timeZone->getName(), $tzString ), $e->getCode(), $e );
            }
        }
        return $timeZone;
    }

    /**
     * Return (array) all transitions from timezone
     *
     * @param DateTimeZone|string $dateTimeZone
     * @param null|int $from
     * @param null|int $to
     * @return array
     * @throws InvalidArgumentException|Exception
     * @since  2.27.8 - 2019-01-22
     */
    public static function getDateTimeZoneTransitions(
        DateTimeZone | string $dateTimeZone,
        ? int $from = null,
        ? int $to = null
    ) : array
    {
        if( ! $dateTimeZone instanceof DateTimeZone ) {
            $dateTimeZone = self::factory( $dateTimeZone );
        }
        $res = $dateTimeZone->getTransitions( $from, $to );
        return ( empty( $res )) ? [] : $res;
    }

    /**
     * Return (first found) timezone from offset
     *
     * @param string $offset
     * @return string
     * @throws InvalidArgumentException
     * @since  2.27.14 - 2019-02-26
     */
    public static function getTimeZoneNameFromOffset( string $offset ) : string
    {
        static $UTCOFFSET = '+00:00';
        static $ERR       = 'Offset \'%s\' (%+d seconds) don\'t match any timezone';
        if( $UTCOFFSET === $offset ) {
            return IcalInterface::UTC;
        }
        $seconds = self::offsetToSeconds( $offset );
        $res     = timezone_name_from_abbr( StringFactory::$SP0, $seconds );
        if( false === $res ) {
            $res = timezone_name_from_abbr( StringFactory::$SP0, $seconds, 0 );
        }
        if( false === $res ) {
            $res = timezone_name_from_abbr( StringFactory::$SP0, $seconds, 1 );
        }
        if( false === $res ) {
            throw new InvalidArgumentException( sprintf( $ERR, $offset, $seconds ));
        }
        return $res;
    }

    /**
     * Return offset part from dateString
     *
     * An offset is one of [+/-]NNNN, [+/-]NN:NN, [+/-]NNNNNN, [+/-]NN:NN:NN
     *
     * @param string $dateString
     * @return string
     */
    public static function getOffset( string $dateString ) : string
    {
        $dateString = trim( $dateString );
        $ix         = strlen( $dateString ) - 1;
        $offset     = StringFactory::$SP0;
        while( true ) {
            $dateX1 = $dateString[$ix];
            switch( true ) {
                case ( ctype_digit( $dateX1 ) || ( StringFactory::$COLON === $dateX1 )) :
                    $offset = $dateX1 . $offset;
                    break;
                case DateIntervalFactory::hasPlusMinusPrefix( $dateX1 ) :
                    $offset = $dateX1 . $offset;
                    break 2;
                default :
                    $offset = StringFactory::$SP0;
                    break 2;
            } // end switch
            if( 1 > $ix ) {
                break;
            }
            --$ix;
        } // end while
        return $offset;
    }

    /**
     * Return bool true if input string contains (trailing) UTC/iCal offset
     *
     * An offset is one of [+/-]NNNN, [+/-]NN:NN, [+/-]NNNNNN, [+/-]NN:NN:NN
     *
     * @param string $string
     * @return bool
     * @since  2.27.14 - 2019-02-18
     */
    public static function hasOffset( string $string ) : bool
    {
        $string = trim( $string );
        if( empty( $string )) {
            return false;
        }
        if( IcalInterface::Z === substr( $string, -1 )) {
            return false;
        }
        if( str_contains( $string, StringFactory::$COLON )) {
            $string = str_replace( StringFactory::$COLON, StringFactory::$SP0, $string );
        }
        if( DateIntervalFactory::hasPlusMinusPrefix( substr( $string, -5 )) &&
            ctype_digit( substr( $string, -4 ))) {
            return true;
        }
        if( DateIntervalFactory::hasPlusMinusPrefix( substr( $string, -7 )) &&
            ctype_digit( substr( $string, -6 ))) {
            return true;
        }
        return false;
    }

    /**
     * Return bool true if UTC timezone
     *
     * @param null|string $timeZoneString
     * @param null|string $dateTimeString    A date/time string
     * @return bool
     * @throws RuntimeException|Exception
     * @since  2.41.73 - 2023-03-15
     * @todo SPl Exception??
     */
    public static function isUTCtimeZone(
        ? string $timeZoneString = null,
        ? string $dateTimeString = null
    ) : bool
    {
        if( empty( $timeZoneString )) {
            return false;
        }
        if( self::hasOffset( $timeZoneString )) {
            if( str_contains( $timeZoneString, StringFactory::$COLON )) {
                $timeZoneString = str_replace(
                    StringFactory::$COLON,
                    StringFactory::$SP0,
                    $timeZoneString
                );
            }
            return ( empty((int) $timeZoneString ));
        }
        $timeZoneString = trim( $timeZoneString );
        if( self::isUtcTz( $timeZoneString )) {
            return true;
        }
        try {
            $tz = new DateTimeZone( $timeZoneString );
            if( 0 !== strcasecmp( $timeZoneString, $tz->getName() )) {
                throw new RuntimeException(); // ms timezone still accepted!!
            }
        }
        catch( Exception ) {
            return false;
        }
        return empty( $tz->getOffset( DateTimeFactory::factory( $dateTimeString, $timeZoneString )));
    }

    /**
     * Return seconds based on an offset, [+/-]HHmm[ss], used when altering UTC to localtime or v.v.
     *
     * @param string $offset
     * @return int
     * @since  2.26.7 - 2018-11-23
     */
    public static function offsetToSeconds( string $offset ) : int
    {
        $offset  = trim( $offset );
        $seconds = 0;
        if( str_contains( $offset, StringFactory::$COLON )) {
            $offset = str_replace( StringFactory::$COLON, StringFactory::$SP0, $offset );
        }
        $strLen = strlen( $offset );
        if( ( 5 > $strLen ) || ( 7 < $strLen )) {
            return $seconds;
        }
        if( ! DateIntervalFactory::hasPlusMinusPrefix( $offset )) {
            return $seconds;
        }
        $isMinus = ( StringFactory::$MINUS === $offset[0]);
        if( ! ctype_digit( substr( $offset, 1 ))) {
            return $seconds;
        }
        $seconds += ((int) substr( $offset, 1, 2 )) * 3600;
        $seconds += ((int) substr( $offset, 3, 2 )) * 60;
        if( 7 === $strLen ) {
            $seconds += (int) substr( $offset, 5, 2 );
        }
        return ( $isMinus ) ? $seconds * -1 : $seconds;
    }

    /**
     * Return iCal offset [-/+]hhmm[ss] (string) from UTC offset seconds
     *
     * @param int $offset
     * @return string
     * @since  2.26 - 2018-11-10
     */
    public static function secondsToOffset( int $offset ) : string
    {
        static $FMT = '%02d';
        $offset2    = (string) $offset;
        switch( $offset2[0] ) {
            case StringFactory::$MINUS :
                $output = StringFactory::$MINUS;
                $offset = (int) substr( $offset2, 1 );
                break;
            case StringFactory::$PLUS :
                $output = StringFactory::$PLUS;
                $offset = (int) substr( $offset2, 1 );
                break;
            default :
                $output = StringFactory::$PLUS;
                break;
        } // end switch
        $output .= sprintf( $FMT, ((int) floor( $offset / 3600 ))); // hour
        $seconds = $offset % 3600;
        $output .= sprintf( $FMT, ((int) floor( $seconds / 60 )));   // min
        $seconds %= 60;
        if( 0 < $seconds ) {
            $output .= sprintf( $FMT, $seconds ); // sec
        }
        return $output;
    }
}