Spaces:
No application file
No application file
namespace Mautic\CoreBundle\Helper; | |
use Mautic\CoreBundle\Loader\ParameterLoader; | |
class DateTimeHelper | |
{ | |
public const FORMAT_DB = 'Y-m-d H:i:s'; | |
private static ?string $defaultLocalTimezone = null; | |
/** | |
* @var string | |
*/ | |
private $string; | |
private string $format; | |
/** | |
* @var string | |
*/ | |
private $timezone; | |
private \DateTimeZone $utc; | |
private \DateTimeZone $local; | |
/** | |
* @var \DateTimeInterface | |
*/ | |
private $datetime; | |
/** | |
* @param \DateTimeInterface|string $string | |
* @param string $fromFormat Format the string is in | |
* @param string $timezone Timezone the string is in | |
*/ | |
public function __construct($string = '', $fromFormat = self::FORMAT_DB, $timezone = 'UTC') | |
{ | |
$this->setDefaultTimezone(); | |
$this->setDateTime($string, $fromFormat, $timezone); | |
} | |
/** | |
* @param \DateTimeInterface|string $datetime | |
*/ | |
public function setDateTime($datetime = '', ?string $fromFormat = self::FORMAT_DB, string $timezone = 'local'): void | |
{ | |
if ('local' == $timezone) { | |
$timezone = self::$defaultLocalTimezone; | |
} elseif (empty($timezone)) { | |
$timezone = 'UTC'; | |
} | |
$this->format = (empty($fromFormat)) ? self::FORMAT_DB : $fromFormat; | |
$this->timezone = $timezone; | |
$this->utc = new \DateTimeZone('UTC'); | |
$this->local = new \DateTimeZone(self::$defaultLocalTimezone); | |
if ($datetime instanceof \DateTimeInterface) { | |
$this->datetime = $datetime; | |
$this->timezone = $datetime->getTimezone()->getName(); | |
$this->string = $fromFormat ? $this->datetime->format($fromFormat) : $datetime; | |
} elseif (empty($datetime)) { | |
$this->datetime = new \DateTime('now', new \DateTimeZone($this->timezone)); | |
$this->string = $fromFormat ? $this->datetime->format($fromFormat) : $datetime; | |
} elseif (null === $fromFormat) { | |
$this->string = $datetime; | |
$this->datetime = new \DateTime($datetime, new \DateTimeZone($this->timezone)); | |
} else { | |
$this->string = $datetime; | |
$this->datetime = \DateTime::createFromFormat( | |
$this->format, | |
$this->string, | |
new \DateTimeZone($this->timezone) | |
); | |
if (false === $this->datetime) { | |
// the format does not match the string so let's attempt to fix that | |
$this->string = date($this->format, strtotime($datetime)); | |
$this->datetime = \DateTime::createFromFormat( | |
$this->format, | |
$this->string | |
); | |
} | |
} | |
} | |
/** | |
* @param string $format | |
* | |
* @return string | |
*/ | |
public function toUtcString($format = null) | |
{ | |
if ($this->datetime) { | |
$dateTime = clone $this->datetime; | |
$utc = ('UTC' == $this->timezone) ? $dateTime : $dateTime->setTimezone($this->utc); | |
if (empty($format)) { | |
$format = $this->format; | |
} | |
return $utc->format($format); | |
} | |
return $this->string; | |
} | |
/** | |
* @param string $format | |
* | |
* @return string | |
*/ | |
public function toLocalString($format = null) | |
{ | |
if ($this->datetime) { | |
$local = $this->datetime->setTimezone($this->local); | |
if (empty($format)) { | |
$format = $this->format; | |
} | |
return $local->format($format); | |
} | |
return $this->string; | |
} | |
/** | |
* @return \DateTime | |
*/ | |
public function getUtcDateTime() | |
{ | |
return $this->datetime->setTimezone($this->utc); | |
} | |
/** | |
* @return \DateTime | |
*/ | |
public function getLocalDateTime() | |
{ | |
return $this->datetime->setTimezone($this->local); | |
} | |
/** | |
* @param string|null $format | |
*/ | |
public function getString($format = null): string | |
{ | |
if (empty($format)) { | |
$format = $this->format; | |
} | |
return $this->datetime->format($format); | |
} | |
/** | |
* @return \DateTime | |
*/ | |
public function getDateTime() | |
{ | |
return $this->datetime; | |
} | |
/** | |
* @return bool|int | |
*/ | |
public function getLocalTimestamp() | |
{ | |
if ($this->datetime) { | |
$local = $this->datetime->setTimezone($this->local); | |
return $local->getTimestamp(); | |
} | |
return false; | |
} | |
/** | |
* @return bool|int | |
*/ | |
public function getUtcTimestamp() | |
{ | |
if ($this->datetime) { | |
$utc = $this->datetime->setTimezone($this->utc); | |
return $utc->getTimestamp(); | |
} | |
return false; | |
} | |
/** | |
* Gets a difference. | |
* | |
* @param string|\DateTime $compare | |
* @param bool|false $resetTime | |
* | |
* @return bool|\DateInterval|string | |
*/ | |
public function getDiff($compare = 'now', $format = null, $resetTime = false) | |
{ | |
if ('now' == $compare) { | |
$compare = new \DateTime('now', $this->datetime->getTimezone()); | |
} | |
$with = clone $this->datetime; | |
if ($resetTime) { | |
$compare->setTime(0, 0, 0); | |
$with->setTime(0, 0, 0); | |
} | |
$interval = $compare->diff($with); | |
return (null == $format) ? $interval : $interval->format($format); | |
} | |
/** | |
* Add to datetime. | |
* | |
* @param bool|false $clone If true, return a new \DateTime rather than update current one | |
* | |
* @return \DateTimeInterface | |
*/ | |
public function add($intervalString, $clone = false) | |
{ | |
$interval = new \DateInterval($intervalString); | |
if ($clone) { | |
$dt = clone $this->datetime; | |
$dt->add($interval); | |
return $dt; | |
} else { | |
$this->datetime->add($interval); | |
} | |
} | |
/** | |
* Subtract from datetime. | |
* | |
* @param bool|false $clone If true, return a new \DateTime rather than update current one | |
* | |
* @return \DateTimeInterface | |
*/ | |
public function sub($intervalString, $clone = false) | |
{ | |
$interval = new \DateInterval($intervalString); | |
if ($clone) { | |
$dt = clone $this->datetime; | |
$dt->sub($interval); | |
return $dt; | |
} else { | |
$this->datetime->sub($interval); | |
} | |
} | |
/** | |
* Returns interval based on $interval number and $unit. | |
* | |
* @param int $interval | |
* @param string $unit | |
* | |
* @throws \Exception | |
*/ | |
public function buildInterval($interval, $unit): \DateInterval | |
{ | |
$possibleUnits = ['Y', 'M', 'D', 'I', 'H', 'S']; | |
$unit = strtoupper($unit); | |
if (!in_array($unit, $possibleUnits)) { | |
throw new \InvalidArgumentException($unit.' is invalid unit for DateInterval'); | |
} | |
$spec = match ($unit) { | |
'I' => "PT{$interval}M", | |
'H', 'S' => "PT{$interval}{$unit}", | |
default => "P{$interval}{$unit}", | |
}; | |
return new \DateInterval($spec); | |
} | |
/** | |
* Modify datetime. | |
* | |
* @param bool|false $clone If true, return a new \DateTime rather than update current one | |
* | |
* @return \DateTimeInterface | |
*/ | |
public function modify($string, $clone = false) | |
{ | |
if ($clone) { | |
$dt = clone $this->datetime; | |
$dt->modify($string); | |
return $dt; | |
} else { | |
$this->datetime->modify($string); | |
} | |
} | |
/** | |
* Returns today, yesterday, tomorrow or false if before yesterday or after tomorrow. | |
* | |
* @return bool|string | |
*/ | |
public function getTextDate($interval = null) | |
{ | |
if (null == $interval) { | |
$interval = $this->getDiff('now', null, true); | |
} | |
$diffDays = (int) $interval->format('%R%a'); | |
return match ($diffDays) { | |
0 => 'today', | |
-1 => 'yesterday', | |
+1 => 'tomorrow', | |
default => false, | |
}; | |
} | |
/** | |
* Tries to guess timezone from timezone offset. | |
* | |
* @param int $offset in seconds | |
* | |
* @return string | |
*/ | |
public function guessTimezoneFromOffset($offset = 0) | |
{ | |
// Sanitize input | |
$offset = (int) $offset; | |
$timezone = timezone_name_from_abbr('', $offset, 0); | |
// In case http://bugs.php.net/44780 bug happens | |
if (empty($timezone)) { | |
foreach (timezone_abbreviations_list() as $abbr) { | |
foreach ($abbr as $city) { | |
if ($city['offset'] == $offset) { | |
return $city['timezone_id']; | |
} | |
} | |
} | |
} | |
return $timezone; | |
} | |
/** | |
* @param string $unit | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public static function validateMysqlDateTimeUnit($unit): void | |
{ | |
$possibleUnits = ['s', 'i', 'H', 'd', 'W', 'm', 'Y']; | |
if (!in_array($unit, $possibleUnits, true)) { | |
$possibleUnitsString = implode(', ', $possibleUnits); | |
throw new \InvalidArgumentException("Unit '$unit' is not supported. Use one of these: $possibleUnitsString"); | |
} | |
} | |
public function getLocalTimezoneOffset(): string | |
{ | |
return $this->getLocalDateTime()->format('P'); | |
} | |
protected function setDefaultTimezone(): void | |
{ | |
if (null === self::$defaultLocalTimezone) { | |
$parameterLoader = new ParameterLoader(); | |
self::$defaultLocalTimezone = $parameterLoader->getParameterBag()->get('default_timezone') ?? date_default_timezone_get(); | |
} | |
} | |
} | |