2024-05-07 12:17:25 +02:00
< ? php
2024-08-13 13:44:16 +00:00
declare ( strict_types = 1 );
2024-05-07 12:17:25 +02:00
namespace Cron ;
use DateTime ;
use DateTimeImmutable ;
use DateTimeInterface ;
use DateTimeZone ;
use Exception ;
use InvalidArgumentException ;
2024-08-13 13:44:16 +00:00
use LogicException ;
2024-05-07 12:17:25 +02:00
use RuntimeException ;
2024-08-13 13:44:16 +00:00
use Webmozart\Assert\Assert ;
2024-05-07 12:17:25 +02:00
/**
* CRON expression parser that can determine whether or not a CRON expression is
* due to run , the next run date and previous run date of a CRON expression .
* The determinations made by this class are accurate if checked run once per
* minute ( seconds are dropped from date time comparisons ) .
*
* Schedule parts must map to :
* minute [ 0 - 59 ], hour [ 0 - 23 ], day of month , month [ 1 - 12 | JAN - DEC ], day of week
* [ 1 - 7 | MON - SUN ], and an optional year .
*
2024-08-13 13:44:16 +00:00
* @ see http :// en . wikipedia . org / wiki / Cron
2024-05-07 12:17:25 +02:00
*/
class CronExpression
{
2024-08-13 13:44:16 +00:00
public const MINUTE = 0 ;
public const HOUR = 1 ;
public const DAY = 2 ;
public const MONTH = 3 ;
public const WEEKDAY = 4 ;
/** @deprecated */
public const YEAR = 5 ;
public const MAPPINGS = [
'@yearly' => '0 0 1 1 *' ,
'@annually' => '0 0 1 1 *' ,
'@monthly' => '0 0 1 * *' ,
'@weekly' => '0 0 * * 0' ,
'@daily' => '0 0 * * *' ,
'@midnight' => '0 0 * * *' ,
'@hourly' => '0 * * * *' ,
];
2024-05-07 12:17:25 +02:00
/**
* @ var array CRON expression parts
*/
2024-08-13 13:44:16 +00:00
protected $cronParts ;
2024-05-07 12:17:25 +02:00
/**
2024-08-13 13:44:16 +00:00
* @ var FieldFactoryInterface CRON field factory
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
protected $fieldFactory ;
2024-05-07 12:17:25 +02:00
/**
* @ var int Max iteration count when searching for next run date
*/
2024-08-13 13:44:16 +00:00
protected $maxIterationCount = 1000 ;
2024-05-07 12:17:25 +02:00
/**
* @ var array Order in which to test of cron parts
*/
2024-08-13 13:44:16 +00:00
protected static $order = [
self :: YEAR ,
self :: MONTH ,
self :: DAY ,
self :: WEEKDAY ,
self :: HOUR ,
self :: MINUTE ,
];
2024-05-07 12:17:25 +02:00
/**
2024-08-13 13:44:16 +00:00
* @ var array < string , string >
*/
private static $registeredAliases = self :: MAPPINGS ;
/**
* Registered a user defined CRON Expression Alias .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ throws LogicException If the expression or the alias name are invalid
* or if the alias is already registered .
*/
public static function registerAlias ( string $alias , string $expression ) : void
{
try {
new self ( $expression );
} catch ( InvalidArgumentException $exception ) {
throw new LogicException ( " The expression ` $expression ` is invalid " , 0 , $exception );
}
$shortcut = strtolower ( $alias );
if ( 1 !== preg_match ( '/^@\w+$/' , $shortcut )) {
throw new LogicException ( " The alias ` $alias ` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_). " );
}
if ( isset ( self :: $registeredAliases [ $shortcut ])) {
throw new LogicException ( " The alias ` $alias ` is already registered. " );
}
self :: $registeredAliases [ $shortcut ] = $expression ;
}
/**
* Unregistered a user defined CRON Expression Alias .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ throws LogicException If the user tries to unregister a built - in alias
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
public static function unregisterAlias ( string $alias ) : bool
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
$shortcut = strtolower ( $alias );
if ( isset ( self :: MAPPINGS [ $shortcut ])) {
throw new LogicException ( " The alias ` $alias ` is a built-in alias; it can not be unregistered. " );
}
if ( ! isset ( self :: $registeredAliases [ $shortcut ])) {
return false ;
2024-05-07 12:17:25 +02:00
}
2024-08-13 13:44:16 +00:00
unset ( self :: $registeredAliases [ $shortcut ]);
return true ;
}
/**
* Tells whether a CRON Expression alias is registered .
*/
public static function supportsAlias ( string $alias ) : bool
{
return isset ( self :: $registeredAliases [ strtolower ( $alias )]);
}
/**
* Returns all registered aliases as an associated array where the aliases are the key
* and their associated expressions are the values .
*
* @ return array < string , string >
*/
public static function getAliases () : array
{
return self :: $registeredAliases ;
}
/**
* @ deprecated since version 3.0 . 2 , use __construct instead .
*/
public static function factory ( string $expression , FieldFactoryInterface $fieldFactory = null ) : CronExpression
{
/** @phpstan-ignore-next-line */
return new static ( $expression , $fieldFactory );
2024-05-07 12:17:25 +02:00
}
/**
* Validate a CronExpression .
*
2024-08-13 13:44:16 +00:00
* @ param string $expression the CRON expression to validate
2024-05-07 12:17:25 +02:00
*
* @ return bool True if a valid CRON expression was passed . False if not .
*/
2024-08-13 13:44:16 +00:00
public static function isValidExpression ( string $expression ) : bool
2024-05-07 12:17:25 +02:00
{
try {
2024-08-13 13:44:16 +00:00
new CronExpression ( $expression );
2024-05-07 12:17:25 +02:00
} catch ( InvalidArgumentException $e ) {
return false ;
}
return true ;
}
/**
2024-08-13 13:44:16 +00:00
* Parse a CRON expression .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ param string $expression CRON expression ( e . g . '8 * * * *' )
* @ param null | FieldFactoryInterface $fieldFactory Factory to create cron fields
* @ throws InvalidArgumentException
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
public function __construct ( string $expression , FieldFactoryInterface $fieldFactory = null )
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
$shortcut = strtolower ( $expression );
$expression = self :: $registeredAliases [ $shortcut ] ? ? $expression ;
$this -> fieldFactory = $fieldFactory ? : new FieldFactory ();
2024-05-07 12:17:25 +02:00
$this -> setExpression ( $expression );
}
/**
2024-08-13 13:44:16 +00:00
* Set or change the CRON expression .
2024-05-07 12:17:25 +02:00
*
* @ param string $value CRON expression ( e . g . 8 * * * * )
*
* @ throws \InvalidArgumentException if not a valid CRON expression
2024-08-13 13:44:16 +00:00
*
* @ return CronExpression
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
public function setExpression ( string $value ) : CronExpression
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
$split = preg_split ( '/\s/' , $value , - 1 , PREG_SPLIT_NO_EMPTY );
Assert :: isArray ( $split );
$notEnoughParts = \count ( $split ) < 5 ;
$questionMarkInInvalidPart = array_key_exists ( 0 , $split ) && $split [ 0 ] === '?'
|| array_key_exists ( 1 , $split ) && $split [ 1 ] === '?'
|| array_key_exists ( 3 , $split ) && $split [ 3 ] === '?' ;
$tooManyQuestionMarks = array_key_exists ( 2 , $split ) && $split [ 2 ] === '?'
&& array_key_exists ( 4 , $split ) && $split [ 4 ] === '?' ;
if ( $notEnoughParts || $questionMarkInInvalidPart || $tooManyQuestionMarks ) {
2024-05-07 12:17:25 +02:00
throw new InvalidArgumentException (
$value . ' is not a valid CRON expression'
);
}
2024-08-13 13:44:16 +00:00
$this -> cronParts = $split ;
2024-05-07 12:17:25 +02:00
foreach ( $this -> cronParts as $position => $part ) {
$this -> setPart ( $position , $part );
}
return $this ;
}
/**
2024-08-13 13:44:16 +00:00
* Set part of the CRON expression .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ param int $position The position of the CRON expression to set
* @ param string $value The value to set
2024-05-07 12:17:25 +02:00
*
* @ throws \InvalidArgumentException if the value is not valid for the part
2024-08-13 13:44:16 +00:00
*
* @ return CronExpression
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
public function setPart ( int $position , string $value ) : CronExpression
2024-05-07 12:17:25 +02:00
{
if ( ! $this -> fieldFactory -> getField ( $position ) -> validate ( $value )) {
throw new InvalidArgumentException (
'Invalid CRON field value ' . $value . ' at position ' . $position
);
}
$this -> cronParts [ $position ] = $value ;
return $this ;
}
/**
2024-08-13 13:44:16 +00:00
* Set max iteration count for searching next run dates .
2024-05-07 12:17:25 +02:00
*
* @ param int $maxIterationCount Max iteration count when searching for next run date
*
* @ return CronExpression
*/
2024-08-13 13:44:16 +00:00
public function setMaxIterationCount ( int $maxIterationCount ) : CronExpression
2024-05-07 12:17:25 +02:00
{
$this -> maxIterationCount = $maxIterationCount ;
return $this ;
}
/**
* Get a next run date relative to the current date or a specific date
*
* @ param string | \DateTimeInterface $currentTime Relative calculation date
* @ param int $nth Number of matches to skip before returning a
* matching next run date . 0 , the default , will return the
* current date and time if the next run date falls on the
* current date and time . Setting this value to 1 will
* skip the first match and go to the second match .
* Setting this value to 2 will skip the first 2
* matches and so on .
* @ param bool $allowCurrentDate Set to TRUE to return the current date if
* it matches the cron expression .
* @ param null | string $timeZone TimeZone to use instead of the system default
*
* @ throws \RuntimeException on too many iterations
2024-08-13 13:44:16 +00:00
* @ throws \Exception
*
* @ return \DateTime
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
public function getNextRunDate ( $currentTime = 'now' , int $nth = 0 , bool $allowCurrentDate = false , $timeZone = null ) : DateTime
2024-05-07 12:17:25 +02:00
{
return $this -> getRunDate ( $currentTime , $nth , false , $allowCurrentDate , $timeZone );
}
/**
2024-08-13 13:44:16 +00:00
* Get a previous run date relative to the current date or a specific date .
2024-05-07 12:17:25 +02:00
*
* @ param string | \DateTimeInterface $currentTime Relative calculation date
* @ param int $nth Number of matches to skip before returning
* @ param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @ param null | string $timeZone TimeZone to use instead of the system default
*
* @ throws \RuntimeException on too many iterations
2024-08-13 13:44:16 +00:00
* @ throws \Exception
*
* @ return \DateTime
*
2024-05-07 12:17:25 +02:00
* @ see \Cron\CronExpression :: getNextRunDate
*/
2024-08-13 13:44:16 +00:00
public function getPreviousRunDate ( $currentTime = 'now' , int $nth = 0 , bool $allowCurrentDate = false , $timeZone = null ) : DateTime
2024-05-07 12:17:25 +02:00
{
return $this -> getRunDate ( $currentTime , $nth , true , $allowCurrentDate , $timeZone );
}
/**
2024-08-13 13:44:16 +00:00
* Get multiple run dates starting at the current date or a specific date .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ param int $total Set the total number of dates to calculate
* @ param string | \DateTimeInterface | null $currentTime Relative calculation date
* @ param bool $invert Set to TRUE to retrieve previous dates
* @ param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @ param null | string $timeZone TimeZone to use instead of the system default
2024-05-07 12:17:25 +02:00
*
* @ return \DateTime [] Returns an array of run dates
*/
2024-08-13 13:44:16 +00:00
public function getMultipleRunDates ( int $total , $currentTime = 'now' , bool $invert = false , bool $allowCurrentDate = false , $timeZone = null ) : array
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
$timeZone = $this -> determineTimeZone ( $currentTime , $timeZone );
if ( 'now' === $currentTime ) {
$currentTime = new DateTime ();
} elseif ( $currentTime instanceof DateTime ) {
$currentTime = clone $currentTime ;
} elseif ( $currentTime instanceof DateTimeImmutable ) {
$currentTime = DateTime :: createFromFormat ( 'U' , $currentTime -> format ( 'U' ));
} elseif ( \is_string ( $currentTime )) {
$currentTime = new DateTime ( $currentTime );
}
Assert :: isInstanceOf ( $currentTime , DateTime :: class );
$currentTime -> setTimezone ( new DateTimeZone ( $timeZone ));
$matches = [];
for ( $i = 0 ; $i < $total ; ++ $i ) {
2024-05-07 12:17:25 +02:00
try {
2024-08-13 13:44:16 +00:00
$result = $this -> getRunDate ( $currentTime , 0 , $invert , $allowCurrentDate , $timeZone );
2024-05-07 12:17:25 +02:00
} catch ( RuntimeException $e ) {
break ;
}
2024-08-13 13:44:16 +00:00
$allowCurrentDate = false ;
$currentTime = clone $result ;
$matches [] = $result ;
2024-05-07 12:17:25 +02:00
}
return $matches ;
}
/**
2024-08-13 13:44:16 +00:00
* Get all or part of the CRON expression .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ param int | string | null $part specify the part to retrieve or NULL to get the full
* cron schedule string
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ return null | string Returns the CRON expression , a part of the
2024-05-07 12:17:25 +02:00
* CRON expression , or NULL if the part was specified but not found
*/
2024-08-13 13:44:16 +00:00
public function getExpression ( $part = null ) : ? string
2024-05-07 12:17:25 +02:00
{
if ( null === $part ) {
return implode ( ' ' , $this -> cronParts );
2024-08-13 13:44:16 +00:00
}
if ( array_key_exists ( $part , $this -> cronParts )) {
2024-05-07 12:17:25 +02:00
return $this -> cronParts [ $part ];
}
return null ;
}
2024-08-13 13:44:16 +00:00
/**
* Gets the parts of the cron expression as an array .
*
* @ return string []
* The array of parts that make up this expression .
*/
public function getParts ()
{
return $this -> cronParts ;
}
2024-05-07 12:17:25 +02:00
/**
* Helper method to output the full expression .
*
* @ return string Full CRON expression
*/
2024-08-13 13:44:16 +00:00
public function __toString () : string
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
return ( string ) $this -> getExpression ();
2024-05-07 12:17:25 +02:00
}
/**
* Determine if the cron is due to run based on the current date or a
* specific date . This method assumes that the current number of
* seconds are irrelevant , and should be called once per minute .
*
* @ param string | \DateTimeInterface $currentTime Relative calculation date
* @ param null | string $timeZone TimeZone to use instead of the system default
*
* @ return bool Returns TRUE if the cron is due to run or FALSE if not
*/
2024-08-13 13:44:16 +00:00
public function isDue ( $currentTime = 'now' , $timeZone = null ) : bool
2024-05-07 12:17:25 +02:00
{
$timeZone = $this -> determineTimeZone ( $currentTime , $timeZone );
if ( 'now' === $currentTime ) {
$currentTime = new DateTime ();
} elseif ( $currentTime instanceof DateTime ) {
2024-08-13 13:44:16 +00:00
$currentTime = clone $currentTime ;
2024-05-07 12:17:25 +02:00
} elseif ( $currentTime instanceof DateTimeImmutable ) {
$currentTime = DateTime :: createFromFormat ( 'U' , $currentTime -> format ( 'U' ));
2024-08-13 13:44:16 +00:00
} elseif ( \is_string ( $currentTime )) {
2024-05-07 12:17:25 +02:00
$currentTime = new DateTime ( $currentTime );
}
2024-08-13 13:44:16 +00:00
Assert :: isInstanceOf ( $currentTime , DateTime :: class );
$currentTime -> setTimezone ( new DateTimeZone ( $timeZone ));
2024-05-07 12:17:25 +02:00
// drop the seconds to 0
2024-08-13 13:44:16 +00:00
$currentTime -> setTime (( int ) $currentTime -> format ( 'H' ), ( int ) $currentTime -> format ( 'i' ), 0 );
2024-05-07 12:17:25 +02:00
try {
return $this -> getNextRunDate ( $currentTime , 0 , true ) -> getTimestamp () === $currentTime -> getTimestamp ();
} catch ( Exception $e ) {
return false ;
}
}
/**
2024-08-13 13:44:16 +00:00
* Get the next or previous run date of the expression relative to a date .
2024-05-07 12:17:25 +02:00
*
2024-08-13 13:44:16 +00:00
* @ param string | \DateTimeInterface | null $currentTime Relative calculation date
* @ param int $nth Number of matches to skip before returning
* @ param bool $invert Set to TRUE to go backwards in time
* @ param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @ param string | null $timeZone TimeZone to use instead of the system default
2024-05-07 12:17:25 +02:00
*
* @ throws \RuntimeException on too many iterations
2024-08-13 13:44:16 +00:00
* @ throws Exception
*
* @ return \DateTime
2024-05-07 12:17:25 +02:00
*/
2024-08-13 13:44:16 +00:00
protected function getRunDate ( $currentTime = null , int $nth = 0 , bool $invert = false , bool $allowCurrentDate = false , $timeZone = null ) : DateTime
2024-05-07 12:17:25 +02:00
{
$timeZone = $this -> determineTimeZone ( $currentTime , $timeZone );
if ( $currentTime instanceof DateTime ) {
$currentDate = clone $currentTime ;
} elseif ( $currentTime instanceof DateTimeImmutable ) {
$currentDate = DateTime :: createFromFormat ( 'U' , $currentTime -> format ( 'U' ));
2024-08-13 13:44:16 +00:00
} elseif ( \is_string ( $currentTime )) {
$currentDate = new DateTime ( $currentTime );
2024-05-07 12:17:25 +02:00
} else {
2024-08-13 13:44:16 +00:00
$currentDate = new DateTime ( 'now' );
}
Assert :: isInstanceOf ( $currentDate , DateTime :: class );
$currentDate -> setTimezone ( new DateTimeZone ( $timeZone ));
// Workaround for setTime causing an offset change: https://bugs.php.net/bug.php?id=81074
$currentDate = DateTime :: createFromFormat ( " !Y-m-d H:iO " , $currentDate -> format ( " Y-m-d H:iP " ), $currentDate -> getTimezone ());
if ( $currentDate === false ) {
throw new \RuntimeException ( 'Unable to create date from format' );
2024-05-07 12:17:25 +02:00
}
2024-08-13 13:44:16 +00:00
$currentDate -> setTimezone ( new DateTimeZone ( $timeZone ));
2024-05-07 12:17:25 +02:00
$nextRun = clone $currentDate ;
// We don't have to satisfy * or null fields
2024-08-13 13:44:16 +00:00
$parts = [];
$fields = [];
2024-05-07 12:17:25 +02:00
foreach ( self :: $order as $position ) {
$part = $this -> getExpression ( $position );
if ( null === $part || '*' === $part ) {
continue ;
}
$parts [ $position ] = $part ;
$fields [ $position ] = $this -> fieldFactory -> getField ( $position );
}
2024-08-13 13:44:16 +00:00
if ( isset ( $parts [ self :: DAY ]) && isset ( $parts [ self :: WEEKDAY ])) {
$domExpression = sprintf ( '%s %s %s %s *' , $this -> getExpression ( 0 ), $this -> getExpression ( 1 ), $this -> getExpression ( 2 ), $this -> getExpression ( 3 ));
$dowExpression = sprintf ( '%s %s * %s %s' , $this -> getExpression ( 0 ), $this -> getExpression ( 1 ), $this -> getExpression ( 3 ), $this -> getExpression ( 4 ));
$domExpression = new self ( $domExpression );
$dowExpression = new self ( $dowExpression );
2024-05-07 12:17:25 +02:00
2024-08-13 13:44:16 +00:00
$domRunDates = $domExpression -> getMultipleRunDates ( $nth + 1 , $currentTime , $invert , $allowCurrentDate , $timeZone );
$dowRunDates = $dowExpression -> getMultipleRunDates ( $nth + 1 , $currentTime , $invert , $allowCurrentDate , $timeZone );
if ( $parts [ self :: DAY ] === '?' || $parts [ self :: DAY ] === '*' ) {
$domRunDates = [];
}
if ( $parts [ self :: WEEKDAY ] === '?' || $parts [ self :: WEEKDAY ] === '*' ) {
$dowRunDates = [];
}
$combined = array_merge ( $domRunDates , $dowRunDates );
usort ( $combined , function ( $a , $b ) {
return $a -> format ( 'Y-m-d H:i:s' ) <=> $b -> format ( 'Y-m-d H:i:s' );
});
if ( $invert ) {
$combined = array_reverse ( $combined );
}
return $combined [ $nth ];
}
// Set a hard limit to bail on an impossible date
for ( $i = 0 ; $i < $this -> maxIterationCount ; ++ $i ) {
2024-05-07 12:17:25 +02:00
foreach ( $parts as $position => $part ) {
$satisfied = false ;
// Get the field object used to validate this part
$field = $fields [ $position ];
// Check if this is singular or a list
2024-08-13 13:44:16 +00:00
if ( false === strpos ( $part , ',' )) {
$satisfied = $field -> isSatisfiedBy ( $nextRun , $part , $invert );
2024-05-07 12:17:25 +02:00
} else {
foreach ( array_map ( 'trim' , explode ( ',' , $part )) as $listPart ) {
2024-08-13 13:44:16 +00:00
if ( $field -> isSatisfiedBy ( $nextRun , $listPart , $invert )) {
2024-05-07 12:17:25 +02:00
$satisfied = true ;
2024-08-13 13:44:16 +00:00
2024-05-07 12:17:25 +02:00
break ;
}
}
}
// If the field is not satisfied, then start over
if ( ! $satisfied ) {
$field -> increment ( $nextRun , $invert , $part );
2024-08-13 13:44:16 +00:00
2024-05-07 12:17:25 +02:00
continue 2 ;
}
}
// Skip this match if needed
if (( ! $allowCurrentDate && $nextRun == $currentDate ) || -- $nth > - 1 ) {
2024-08-13 13:44:16 +00:00
$this -> fieldFactory -> getField ( self :: MINUTE ) -> increment ( $nextRun , $invert , $parts [ self :: MINUTE ] ? ? null );
2024-05-07 12:17:25 +02:00
continue ;
}
return $nextRun ;
}
// @codeCoverageIgnoreStart
throw new RuntimeException ( 'Impossible CRON expression' );
// @codeCoverageIgnoreEnd
}
/**
* Workout what timeZone should be used .
*
2024-08-13 13:44:16 +00:00
* @ param string | \DateTimeInterface | null $currentTime Relative calculation date
* @ param string | null $timeZone TimeZone to use instead of the system default
2024-05-07 12:17:25 +02:00
*
* @ return string
*/
2024-08-13 13:44:16 +00:00
protected function determineTimeZone ( $currentTime , ? string $timeZone ) : string
2024-05-07 12:17:25 +02:00
{
2024-08-13 13:44:16 +00:00
if ( null !== $timeZone ) {
2024-05-07 12:17:25 +02:00
return $timeZone ;
}
2024-08-13 13:44:16 +00:00
if ( $currentTime instanceof DateTimeInterface ) {
return $currentTime -> getTimezone () -> getName ();
2024-05-07 12:17:25 +02:00
}
return date_default_timezone_get ();
}
}