Skip to content

Instantly share code, notes, and snippets.

@Radiergummi
Last active July 12, 2018 16:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Radiergummi/991c9629fde500d5006bd21eac455d09 to your computer and use it in GitHub Desktop.
Save Radiergummi/991c9629fde500d5006bd21eac455d09 to your computer and use it in GitHub Desktop.
TimeSpan class - a flexible PHP class to handle time calculations
<?php
/**
* TimeSpan class
* ==============
*
* Provides a convenience class to handle time amounts and time spans: Ever wondered how to easily figure out how many
* hours there are between 09:15:31 and 18:55:01? This class is for you. Going with the example, it's as easy as:
*
* `echo (new TimeSpan('18:55:01'))->sub('09:15:31')->format('h:m'); // '09:39'`
*
* The class provides several helper methods to work with time in different formats, as integers, strings or other TimeSpan
* instances. Internally, every value coming in is converted to seconds, calculated with the existing seconds and formatted
* on demand. Easy stuff, really, but it comes in handy at times (employee statistics for me).
*
* @author Moritz Friedrich <m@9dev.de>
*
* @example $timespan = new TimeSpan('13:35:05') // Creates a time span from a string (13:35:05)
* @example $timespan = new TimeSpan('13:35') // Creates a time span from a string (13:35:00)
* @example $timespan = new TimeSpan('13:1') // Creates a time span from a string, leading zeros or not (13:01:00)
* @example $timespan = new TimeSpan('150') // Creates a time span from a string, assumes seconds for single numbers (00:01:30)
* @example $timespan = new TimeSpan('02:190:376') // Creates a time span from a string, calculates correctly (05:16:16)
* @example $timespan = new TimeSpan(13); // Creates a time span from hours only (13:00:00)
* @example $timespan = new TimeSpan(13, 35); // Creates a time span from hours only (13:35:00)
* @example $timespan = new TimeSpan(13, 35, 05); // Creates a time span from hours, minutes and seconds (13:35:05)
* @example $timespan = new TimeSpan($otherTimeSpan) // Effectively clones a time span
*/
class TimeSpan
{
/**
* Holds the amount of time in seconds
*
* @var int
*/
protected $time = 0;
/**
* Creates a new TimeSpan from any valid parse argument
*
* @param string|int|\TimeSpan $timeAmountOrHours
* @param int $minutes
* @param int $seconds
*/
public function __construct($timeAmountOrHours = null, int $minutes = 0, int $seconds = 0)
{
if ( ! is_null($timeAmountOrHours)) {
$this->time = static::parse($timeAmountOrHours, $minutes, $seconds);
}
}
/**
* Parses a time amount
*
* @param string|int|\TimeSpan $timeAmountOrHours time string, amount of seconds or another TimeSpan instance
* @param int $minutes
* @param int $seconds
*
* @return int
*/
public static function parse($timeAmountOrHours, int $minutes = 0, int $seconds = 0): int
{
if ($timeAmountOrHours instanceof TimeSpan) {
return $timeAmountOrHours->asSeconds();
}
if (is_int($timeAmountOrHours)) {
return $timeAmountOrHours * 3600 + $minutes * 60 + $seconds;
}
if (is_string($timeAmountOrHours)) {
$hours = 0;
$minutes = 0;
$seconds = 0;
switch (substr_count($timeAmountOrHours, ':')) {
case 0:
$seconds = intval($timeAmountOrHours);
break;
case 1:
list ($hours, $minutes) = array_map('intval', explode(':', $timeAmountOrHours));
break;
case 2:
list ($hours, $minutes, $seconds) = array_map('intval', explode(':', $timeAmountOrHours));
break;
}
return $hours * 3600 + $minutes * 60 + $seconds;
}
}
/**
* Retrieves the total number of seconds for the time span
*
* @return int
*/
public function asSeconds(): int
{
return $this->time;
}
/**
* Calculates the distance between two time spans
*
* @param \TimeSpan $firstTimeSpan
* @param \TimeSpan $secondTimeSpan
*
* @return \TimeSpan
*/
public static function distance(TimeSpan $firstTimeSpan, TimeSpan $secondTimeSpan): TimeSpan
{
$firstSeconds = $firstTimeSpan->asSeconds();
$secondSeconds = $secondTimeSpan->asSeconds();
return new TimeSpan(0, 0, max($firstSeconds, $secondSeconds) - min($firstSeconds, $secondSeconds));
}
/**
* Returns the difference between two time spans
*
* @param \TimeSpan $timeSpan
*
* @return \TimeSpan
*/
public function diff(TimeSpan $timeSpan): TimeSpan
{
return new TimeSpan(0, 0, $this->time - $timeSpan->asSeconds());
}
/**
* Adds a time amount to the time span
*
* @param string|int|\TimeSpan $timeAmount
*
* @return $this
*/
public function add($timeAmount): TimeSpan
{
$this->time += self::parse($timeAmount);
return $this;
}
/**
* Adds seconds to the time span
*
* @param int $seconds
*
* @return $this
*/
public function addSeconds($seconds): TimeSpan
{
$this->time += (int)$seconds;
return $this;
}
/**
* Adds minutes to the time span
*
* @param int $minutes
*
* @return $this
*/
public function addMinutes($minutes): TimeSpan
{
$this->time += (int)$minutes * 60;
return $this;
}
/**
* Adds hours to the time span
*
* @param int $hours
*
* @return $this
*/
public function addHours($hours): TimeSpan
{
$this->time += (int)$hours * 3600;
return $this;
}
/**
* Subtracts a time amount from the time span
*
* @param string|int|\TimeSpan $timeAmount
*
* @return $this
*/
public function sub($timeAmount): TimeSpan
{
$this->time -= self::parse($timeAmount);
return $this;
}
/**
* Subtracts seconds from the time span
*
* @param $seconds
*
* @return $this
*/
public function subSeconds($seconds): TimeSpan
{
$this->time -= (int)$seconds;
return $this;
}
/**
* Subtracts minutes from the time span
*
* @param int $minutes
*
* @return $this
*/
public function subMinutes($minutes): TimeSpan
{
$this->time -= (int)$minutes * 60;
return $this;
}
/**
* Subtracts hours from the time span
*
* @param int $hours
*
* @return $this
*/
public function subHours($hours): TimeSpan
{
$this->time -= (int)$hours * 3600;
return $this;
}
/**
* Stringifies the time to the most common format
*
* @return string
*/
public function __toString(): string
{
return $this->format('h:m:s');
}
/**
* Formats the time span according to a format. Available formatting tokens are:
*
* - h: Hours with leading zeros
* - H: Hours without leading zeros
* - m: Minutes with leading zeros
* - M: Minutes without leading zeros
* - s: Seconds with leading zeros
* - S: Seconds without leading zeros
* - B: Total time in seconds
*
* Token letters can be escaped using a backslash.
*
* @param string $formatStr
*
* @return string
*
* @example $t->format('h:m:s'); // 03:05:09
* @example $t->format('H:M:S'); // 3:5:9
* @example $t->format('B'); // 11109
* @example $t->format('\H: H, \m: m'); // H: 3, m: 05
*/
public function format($formatStr): string
{
$output = '';
$skip = false;
if ($this->time < 0) {
$unsignedTime = 0 - $this->time;
$hours = floor($unsignedTime / 3600);
$minutes = floor(($unsignedTime / 60) % 60);
$seconds = $unsignedTime % 60;
} else {
$hours = floor($this->time / 3600);
$minutes = floor(($this->time / 60) % 60);
$seconds = $this->time % 60;
}
for ($i = 0; $i < strlen($formatStr); $i++) {
if ($skip) {
$skip = false;
continue;
}
switch ($formatStr[$i]) {
case '\\':
$skip = true;
break;
case 'h':
$output .= ($this->isNegative() ? '-' : '') . str_pad($hours, 2, '0', STR_PAD_LEFT);
break;
case 'H':
$output .= ($this->isNegative() ? '-' : '') . $hours;
break;
case 'm':
$output .= str_pad($minutes, 2, '0', STR_PAD_LEFT);
break;
case 'M':
$output .= $minutes;
break;
case 's':
$output .= str_pad($seconds, 2, '0', STR_PAD_LEFT);
break;
case 'S':
$output .= $seconds;
break;
case 'B':
$output .= $this->time;
break;
default:
$output .= $formatStr[$i];
}
}
return $output;
}
/**
* Checks whether the time is a negative value
*
* @return bool
*/
public function isNegative(): bool
{
return $this->time < 0;
}
/**
* Checks if the time is a positive value
*
* @return bool
*/
public function isPositive(): bool
{
return $this->time >= 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment