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; } }