index/src/TimeSpan.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;
}
}