527 lines
17 KiB
PHP
527 lines
17 KiB
PHP
|
<?php
|
||
|
// TimeSpan.php
|
||
|
// Created: 2021-06-11
|
||
|
// Updated: 2022-02-27
|
||
|
|
||
|
namespace Index;
|
||
|
|
||
|
use DateInterval;
|
||
|
use JsonSerializable;
|
||
|
use Stringable;
|
||
|
|
||
|
/**
|
||
|
* Represents a duration of time.
|
||
|
*/
|
||
|
class TimeSpan extends DateInterval implements JsonSerializable, Stringable, IComparable, IEquatable {
|
||
|
private const HOURS_IN_DAY = 24;
|
||
|
private const MINS_IN_HOUR = 60;
|
||
|
private const SECS_IN_MIN = 60;
|
||
|
private const MILLIS_IN_SEC = 1000;
|
||
|
private const MINS_IN_DAY = 1440;
|
||
|
private const SECS_IN_HOUR = 3600;
|
||
|
private const MILLIS_IN_MIN = 60000;
|
||
|
private const SECS_IN_DAY = 86400;
|
||
|
private const MICROS_IN_SEC = 1000000;
|
||
|
private const MILLIS_IN_HOUR = 3600000;
|
||
|
private const MICROS_IN_MIN = 60000000;
|
||
|
private const MILLIS_IN_DAY = 86400000;
|
||
|
private const MICROS_IN_HOUR = 3600000000;
|
||
|
private const MICROS_IN_DAY = 86400000000;
|
||
|
|
||
|
private int $realDays = -1;
|
||
|
|
||
|
/**
|
||
|
* Creates a new instance of TimeSpan.
|
||
|
*
|
||
|
* @param string $duration An ISO8601 duration string.
|
||
|
* @return TimeSpan A TimeSpan instance representing the given duration.
|
||
|
*/
|
||
|
public function __construct(string $duration = 'PT0S') {
|
||
|
parent::__construct($duration);
|
||
|
$this->realDays = $this->days === false ? $this->calculateRealDays() : $this->days;
|
||
|
}
|
||
|
|
||
|
private function calculateRealDays(): int {
|
||
|
return ($this->y * 365)
|
||
|
+ ($this->m * 31)
|
||
|
+ $this->d;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of days this TimeSpan represents.
|
||
|
*
|
||
|
* @return int Amount of days.
|
||
|
*/
|
||
|
public function getDays(): int {
|
||
|
return $this->realDays;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of hours this TimeSpan represents.
|
||
|
*
|
||
|
* @retunr int Amount of hours.
|
||
|
*/
|
||
|
public function getHours(): int {
|
||
|
return $this->h;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of minutes this TimeSpan represents.
|
||
|
*
|
||
|
* @return int Amount of minutes.
|
||
|
*/
|
||
|
public function getMinutes(): int {
|
||
|
return $this->i;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of seconds this TimeSpan represents.
|
||
|
*
|
||
|
* @return int Amount of seconds.
|
||
|
*/
|
||
|
public function getSeconds(): int {
|
||
|
return $this->s;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of milliseconds this TimeSpan represents.
|
||
|
*
|
||
|
* @return int Amount of milliseconds.
|
||
|
*/
|
||
|
public function getMilliseconds(): int {
|
||
|
return (int)($this->f * 1000);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the amount of microseconds this TimeSpan represents.
|
||
|
*
|
||
|
* @return int Amount of microseconds.
|
||
|
*/
|
||
|
public function getMicroseconds(): int {
|
||
|
return (int)($this->f * 1000000);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets whether this TimeSpan represents a negative amount of time.
|
||
|
*
|
||
|
* @return bool true if negative, false if positive.
|
||
|
*/
|
||
|
public function isNegative(): bool {
|
||
|
return $this->invert !== 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of days this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of days.
|
||
|
*/
|
||
|
public function totalDays(): float {
|
||
|
$days = $this->getDays();
|
||
|
$days += $this->getHours() / self::HOURS_IN_DAY;
|
||
|
$days += $this->getMinutes() / self::MINS_IN_DAY;
|
||
|
$days += $this->getSeconds() / self::SECS_IN_DAY;
|
||
|
$days += $this->getMicroseconds() / self::MICROS_IN_DAY;
|
||
|
return $days;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of hours this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of hours.
|
||
|
*/
|
||
|
public function totalHours(): float {
|
||
|
$hours = $this->getDays() * self::HOURS_IN_DAY;
|
||
|
$hours += $this->getHours();
|
||
|
$hours += $this->getMinutes() / self::MINS_IN_HOUR;
|
||
|
$hours += $this->getSeconds() / self::SECS_IN_HOUR;
|
||
|
$hours += $this->getMicroseconds() / self::MICROS_IN_HOUR;
|
||
|
return $hours;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of minutes this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of minutes.
|
||
|
*/
|
||
|
public function totalMinutes(): float {
|
||
|
$mins = $this->getDays() * self::MINS_IN_DAY;
|
||
|
$mins += $this->getHours() * self::MINS_IN_HOUR;
|
||
|
$mins += $this->getMinutes();
|
||
|
$mins += $this->getSeconds() / self::SECS_IN_MIN;
|
||
|
$mins += $this->getMicroseconds() / self::MICROS_IN_MIN;
|
||
|
return $mins;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of seconds this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of seconds.
|
||
|
*/
|
||
|
public function totalSeconds(): float {
|
||
|
$secs = $this->getDays() * self::SECS_IN_DAY;
|
||
|
$secs += $this->getHours() * self::SECS_IN_HOUR;
|
||
|
$secs += $this->getMinutes() * self::SECS_IN_MIN;
|
||
|
$secs += $this->getSeconds();
|
||
|
$secs += $this->getMicroseconds() * self::MICROS_IN_SEC;
|
||
|
return $secs;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of milliseconds this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of milliseconds.
|
||
|
*/
|
||
|
public function totalMilliseconds(): float {
|
||
|
$millis = $this->getDays() * self::MILLIS_IN_DAY;
|
||
|
$millis += $this->getHours() * self::MILLIS_IN_HOUR;
|
||
|
$millis += $this->getMinutes() * self::MILLIS_IN_MIN;
|
||
|
$millis += $this->getSeconds() * self::MILLIS_IN_SEC;
|
||
|
$millis += $this->getMilliseconds();
|
||
|
return $millis;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the total amount of microseconds this TimeSpan contains.
|
||
|
*
|
||
|
* @return float Total amount of microseconds.
|
||
|
*/
|
||
|
public function totalMicroseconds(): float {
|
||
|
$micros = $this->getDays() * self::MICROS_IN_DAY;
|
||
|
$micros += $this->getHours() * self::MICROS_IN_HOUR;
|
||
|
$micros += $this->getMinutes() * self::MICROS_IN_MIN;
|
||
|
$micros += $this->getSeconds() * self::MICROS_IN_SEC;
|
||
|
$micros += $this->getMicroseconds();
|
||
|
return $micros;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a new TimeSpan whose value is the sum of this instance and the provided instance.
|
||
|
*
|
||
|
* @param DateInterval $timeSpan Interval to add.
|
||
|
* @return TimeSpan Instance that represents this instance plus the value of $timeSpan.
|
||
|
*/
|
||
|
public function add(DateInterval $timeSpan): TimeSpan {
|
||
|
$timeSpan = self::cast($timeSpan);
|
||
|
|
||
|
$days1 = $this->totalDays();
|
||
|
if($this->isNegative())
|
||
|
$days1 *= -1;
|
||
|
|
||
|
$days2 = $timeSpan->totalDays();
|
||
|
if($timeSpan->isNegative())
|
||
|
$days2 *= -1;
|
||
|
|
||
|
return self::fromDays($days1 + $days2);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a new TimeSpan whose value is the difference of this instance and the provided instance.
|
||
|
*
|
||
|
* @param DateInterval $timeSpan Interval to subtract.
|
||
|
* @return TimeSpan Instance that represents this instance minus the value of $timeSpan.
|
||
|
*/
|
||
|
public function subtract(DateInterval $timeSpan): TimeSpan {
|
||
|
$timeSpan = self::cast($timeSpan);
|
||
|
|
||
|
$days1 = $this->totalDays();
|
||
|
if($this->isNegative())
|
||
|
$days1 *= -1;
|
||
|
|
||
|
$days2 = $timeSpan->totalDays();
|
||
|
if($timeSpan->isNegative())
|
||
|
$days2 *= -1;
|
||
|
|
||
|
return self::fromDays($days1 - $days2);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the result of a division of this instance and the provided instance.
|
||
|
*
|
||
|
* @param DateInterval $timeSpan Interval to be divided by.
|
||
|
* @return float $timeSpan Number that represents this instance divided by the value of $timeSpan.
|
||
|
*/
|
||
|
public function divideTimeSpan(DateInterval $timeSpan): float {
|
||
|
$timeSpan = self::cast($timeSpan);
|
||
|
|
||
|
$days1 = $this->totalDays();
|
||
|
if($this->isNegative())
|
||
|
$days1 *= -1;
|
||
|
|
||
|
$days2 = $timeSpan->totalDays();
|
||
|
if($timeSpan->isNegative())
|
||
|
$days2 *= -1;
|
||
|
|
||
|
if($days2 === 0.0)
|
||
|
return 0.0;
|
||
|
|
||
|
return $days1 / $days2;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the result of a division of this instance and the provided divisor.
|
||
|
*
|
||
|
* @param float $divisor Value to divide by.
|
||
|
* @return TimeSpan Instance that represents this instance divided by the value of $divisor.
|
||
|
*/
|
||
|
public function divideFloat(float $divisor): TimeSpan {
|
||
|
$totalDays = $this->totalDays();
|
||
|
if($this->isNegative())
|
||
|
$totalDays *= -1;
|
||
|
$totalDays /= $divisor;
|
||
|
return self::fromDays($totalDays);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a new TimeSpan whose alue is the result of a multiplication of this oinstance and the provided factor.
|
||
|
*
|
||
|
* @param float $factor Value to multiply with.
|
||
|
* @return TimeSpan Instance that represents this instance multiplied by the value of $factor.
|
||
|
*/
|
||
|
public function multiply(float $factor): TimeSpan {
|
||
|
$totalDays = $this->totalDays();
|
||
|
if($this->isNegative())
|
||
|
$totalDays *= -1;
|
||
|
$totalDays *= $factor;
|
||
|
return self::fromDays($totalDays);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a new TimeSpan whose value is the negated value of this instance.
|
||
|
*
|
||
|
* @return TimeSpan Negated copy of this instance.
|
||
|
*/
|
||
|
public function negate(): TimeSpan {
|
||
|
return self::create(
|
||
|
$this->getDays(),
|
||
|
$this->getHours(),
|
||
|
$this->getMinutes(),
|
||
|
$this->getSeconds(),
|
||
|
$this->getMicroseconds(),
|
||
|
!$this->isNegative()
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function compare(mixed $other): int {
|
||
|
if(!($other instanceof DateInterval))
|
||
|
return -1;
|
||
|
$other = self::cast($other);
|
||
|
|
||
|
$diff = $this->getDays() <=> $other->getDays();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
$diff = $this->getHours() <=> $other->getHours();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
$diff = $this->getMinutes() <=> $other->getMinutes();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
$diff = $this->getSeconds() <=> $other->getSeconds();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
$diff = $this->getMicroseconds() <=> $other->getMicroseconds();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
$diff = $this->isNegative() <=> $other->isNegative();
|
||
|
if($diff) return $diff;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
public function equals(mixed $other): bool {
|
||
|
if(!($other instanceof DateInterval))
|
||
|
return false;
|
||
|
$other = self::cast($other);
|
||
|
return $this->getDays() === $other->getDays()
|
||
|
&& $this->getHours() === $other->getHours()
|
||
|
&& $this->getMinutes() === $other->getMinutes()
|
||
|
&& $this->getSeconds() === $other->getSeconds()
|
||
|
&& $this->getMicroseconds() === $other->getMicroseconds()
|
||
|
&& $this->isNegative() === $other->isNegative();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the data which should be serialized as json.
|
||
|
*
|
||
|
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
|
||
|
* @return mixed Data to be passed to json_encode.
|
||
|
*/
|
||
|
public function jsonSerialize(): mixed {
|
||
|
return (string)$this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a string representation of this object.
|
||
|
*
|
||
|
* @return string Representation of this object.
|
||
|
*/
|
||
|
public function __toString(): string {
|
||
|
$string = $this->isNegative() ? '-' : '';
|
||
|
return sprintf(
|
||
|
'%sP%dY%dM%dDT%dH%dM%d.%dS',
|
||
|
$this->isNegative() ? '-' : '',
|
||
|
$this->y,
|
||
|
$this->m,
|
||
|
$this->d,
|
||
|
$this->getHours(),
|
||
|
$this->getMinutes(),
|
||
|
$this->getSeconds(),
|
||
|
$this->getMicroseconds()
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of days.
|
||
|
*
|
||
|
* @param float $days Number of days.
|
||
|
* @return TimeSpan Instance representing the provided number of days.
|
||
|
*/
|
||
|
public static function fromDays(float $days): TimeSpan {
|
||
|
$abs = abs($days);
|
||
|
$round = (int)floor($abs);
|
||
|
$hours = ($abs - $round) * self::HOURS_IN_DAY;
|
||
|
$hoursRound = (int)floor($hours);
|
||
|
$minutes = ($hours - $hoursRound) * self::MINS_IN_HOUR;
|
||
|
$minsRound = (int)floor($minutes);
|
||
|
$seconds = ($minutes - $minsRound) * self::SECS_IN_MIN;
|
||
|
$secsRound = (int)floor($seconds);
|
||
|
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
|
||
|
return self::create($round, $hoursRound, $minsRound, $secsRound, $micros, $days < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of hours.
|
||
|
*
|
||
|
* @param float $hours Number of hours.
|
||
|
* @return TimeSpan Instance representing the provided number of hours.
|
||
|
*/
|
||
|
public static function fromHours(float $hours): TimeSpan {
|
||
|
$abs = abs($hours);
|
||
|
$round = (int)floor($abs);
|
||
|
$minutes = ($abs - $round) * self::MINS_IN_HOUR;
|
||
|
$minsRound = (int)floor($minutes);
|
||
|
$seconds = ($minutes - $minsRound) * self::SECS_IN_MIN;
|
||
|
$secsRound = (int)floor($seconds);
|
||
|
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
|
||
|
return self::create(0, $round, $minsRound, $secsRound, $micros, $hours < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of minutes.
|
||
|
*
|
||
|
* @param float $minutes Number of minutes.
|
||
|
* @return TimeSpan Instance representing the provided number of minutes.
|
||
|
*/
|
||
|
public static function fromMinutes(float $minutes): TimeSpan {
|
||
|
$abs = abs($minutes);
|
||
|
$round = (int)floor($abs);
|
||
|
$seconds = ($abs - $round) * self::SECS_IN_MIN;
|
||
|
$secsRound = (int)floor($seconds);
|
||
|
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
|
||
|
return self::create(0, 0, $round, $secsRound, $micros, $minutes < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of seconds.
|
||
|
*
|
||
|
* @param float $seconds Number of seconds.
|
||
|
* @return TimeSpan Instance representing the provided number of seconds.
|
||
|
*/
|
||
|
public static function fromSeconds(float $seconds): TimeSpan {
|
||
|
$abs = abs($seconds);
|
||
|
$round = (int)floor($abs);
|
||
|
$micros = (int)floor(($abs - $round) * self::MICROS_IN_SEC);
|
||
|
return self::create(0, 0, 0, $round, $micros, $seconds < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of milliseconds.
|
||
|
*
|
||
|
* @param float $millis Number of milliseconds.
|
||
|
* @return TimeSpan Instance representing the provided number of milliseconds.
|
||
|
*/
|
||
|
public static function fromMilliseconds(float $millis): TimeSpan {
|
||
|
return self::fromMicroseconds($millis * 1000);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan instance that represents the specified number of microseconds.
|
||
|
*
|
||
|
* @param float $micros Number of microseconds.
|
||
|
* @return TimeSpan Instance representing the provided number of microseconds.
|
||
|
*/
|
||
|
public static function fromMicroseconds(float $micros): TimeSpan {
|
||
|
return self::create(0, 0, 0, 0, (int)floor(abs($micros)), $micros < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new TimeSpan using the given parameters.
|
||
|
*
|
||
|
* @param int $days Number of days.
|
||
|
* @param int $hours Number of hours.
|
||
|
* @param int $minutes Number of minutes.
|
||
|
* @param int $seconds Number of seconds.
|
||
|
* @param int $micros Number of microseconds.
|
||
|
* @param bool $negative true for a negative TimeSpan, false for positive.
|
||
|
* @return TimeSpan A new TimeSpan from the provided parameters.
|
||
|
*/
|
||
|
public static function create(int $days, int $hours, int $minutes, int $seconds, int $micros = 0, bool $negative = false): TimeSpan {
|
||
|
$ts = new TimeSpan;
|
||
|
$ts->realDays = $days = max(0, $days);
|
||
|
$ts->h = max(0, min(23, $hours));
|
||
|
$ts->i = max(0, min(59, $minutes));
|
||
|
$ts->s = max(0, min(59, $seconds));
|
||
|
$ts->f = $micros / self::MICROS_IN_SEC;
|
||
|
$ts->invert = $negative ? 1 : 0;
|
||
|
|
||
|
// this may still be stupid
|
||
|
if($days > 0) {
|
||
|
$days /= 365;
|
||
|
$ts->y = (int)$days;
|
||
|
$days -= $ts->y;
|
||
|
$days *= 12;
|
||
|
$ts->m = (int)$days;
|
||
|
$days -= $ts->m;
|
||
|
$days *= 31;
|
||
|
$ts->d = (int)$days;
|
||
|
}
|
||
|
|
||
|
return $ts;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a TimeSpan from the relative parts of a string.
|
||
|
*
|
||
|
* Uses the same parser as strtotime and the DateTime constructor internally.
|
||
|
*
|
||
|
* @param string $dateTime A date with relative parts.
|
||
|
* @return TimeSpan A TimeSpan representing the given
|
||
|
*/
|
||
|
public static function createFromDateString(string $dateTime): TimeSpan {
|
||
|
return self::cast(parent::createFromDateString($dateTime));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Makes sure a DateInterval inheriting object is a TimeSpan instance.
|
||
|
*
|
||
|
* @param DateInterval $dateInterval Input object.
|
||
|
* @return TimeSpan If the input was a TimeSpan, the same instance will be returned.
|
||
|
* If the input was something else, a new TimeSpan instance will be returned based on the input.
|
||
|
*/
|
||
|
public static function cast(DateInterval $dateInterval): TimeSpan {
|
||
|
if($dateInterval instanceof TimeSpan)
|
||
|
return $dateInterval;
|
||
|
|
||
|
$timeSpan = new TimeSpan;
|
||
|
$timeSpan->y = $dateInterval->y;
|
||
|
$timeSpan->m = $dateInterval->m;
|
||
|
$timeSpan->d = $dateInterval->d;
|
||
|
$timeSpan->h = $dateInterval->h;
|
||
|
$timeSpan->i = $dateInterval->i;
|
||
|
$timeSpan->s = $dateInterval->s;
|
||
|
$timeSpan->f = $dateInterval->f;
|
||
|
$timeSpan->invert = $dateInterval->invert;
|
||
|
$timeSpan->realDays = $timeSpan->calculateRealDays();
|
||
|
return $timeSpan;
|
||
|
}
|
||
|
}
|