302 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\b\date;
 | 
						|
 | 
						|
use nur\b\IllegalAccessException;
 | 
						|
use nur\b\ValueException;
 | 
						|
use nur\b\values\IValueState;
 | 
						|
 | 
						|
/**
 | 
						|
 * Class Time: une heure allant de 0h à 24h inclus.
 | 
						|
 *
 | 
						|
 * la seule utilisation autorisée de "24h" est comme borne supérieure pour une
 | 
						|
 * plage horaire.
 | 
						|
 *
 | 
						|
 * Cet objet est considéré comme immutable. les seules méthodes publiques qui
 | 
						|
 * permettent de le modifier en place sont {@link Time::wrapStart()},
 | 
						|
 * {@link Time::wrapEnd()} et {@link Time::set()}
 | 
						|
 */
 | 
						|
class Time implements IValueState {
 | 
						|
  const UNIT_HOURS = 3600;
 | 
						|
  const UNIT_MINUTES = 60;
 | 
						|
  const UNIT_SECONDS = 1;
 | 
						|
 | 
						|
  /** @var string format normalisé pour l'analyse */
 | 
						|
  const PATTERN = '/^(\d+):(\d{2}):(\d{2})$/';
 | 
						|
  /** @var string format par défaut pour l'affichage */
 | 
						|
  const FORMAT = "HH:MM:SS";
 | 
						|
 | 
						|
  static function parse_time($time, int $unit=self::UNIT_SECONDS): int {
 | 
						|
    if ($time instanceof Time) {
 | 
						|
      $seconds = $time->getSeconds();
 | 
						|
    } elseif ($time === null || is_int($time)) {
 | 
						|
      $seconds = $time !== null? $time: 0;
 | 
						|
      if ($unit !== null) $seconds *= $unit;
 | 
						|
    } elseif (is_array($time)) {
 | 
						|
      [$h, $m, $s] = $time;
 | 
						|
      $seconds = $h * 3600 + $m * 60 + $s;
 | 
						|
    } elseif (is_string($time) && preg_match(self::PATTERN, $time, $ms)) {
 | 
						|
      $seconds = intval($ms[1]) * 3600 + intval($ms[2]) * 60 + intval($ms[3]);
 | 
						|
    } else {
 | 
						|
      throw ValueException::invalid_value($time, "time");
 | 
						|
    }
 | 
						|
    return $seconds;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return int la valeur de l'unité en secondes, pour le constructeur et les
 | 
						|
   * méthodes {@link newu()}, {@link addu()} et {@link subu()}
 | 
						|
   */
 | 
						|
  function UNIT(): int {
 | 
						|
    return static::UNIT;
 | 
						|
  } const UNIT = self::UNIT_SECONDS;
 | 
						|
 | 
						|
  /** @return bool s'il faut garder les heures dans la plage [0, 24h] */
 | 
						|
  protected function WRAP(): bool {
 | 
						|
    return static::WRAP;
 | 
						|
  } const WRAP = true;
 | 
						|
 | 
						|
  function __construct($time=null) {
 | 
						|
    if ($time !== false) {
 | 
						|
      $this->setSeconds(self::parse_time($time, $this->UNIT()));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /** cet objet est-il l'instance nulle? */
 | 
						|
  function isNull(): bool { return false; }
 | 
						|
 | 
						|
  /** cet objet est-il l'heure indéfinie? */
 | 
						|
  function isUndef(): bool { return false; }
 | 
						|
 | 
						|
  /**
 | 
						|
   * wrapper l'heure pour la garder dans la plage [0h, 24h[ ce qui la rend
 | 
						|
   * propice à l'utilisation comme borne inférieure d'une période
 | 
						|
   */
 | 
						|
  function wrapStart(): self {
 | 
						|
    $seconds = $this->seconds;
 | 
						|
    while ($seconds < 0) $seconds += 86400;
 | 
						|
    if ($seconds >= 86400) $seconds %= 86400;
 | 
						|
    $this->seconds = $seconds;
 | 
						|
    return $this;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * wrapper l'heure pour la garder dans la plage [0h, 24h] ce qui la rend
 | 
						|
   * propice à l'utilisation comme borne supérieure d'une période
 | 
						|
   */
 | 
						|
  function wrapEnd(): self {
 | 
						|
    $seconds = $this->seconds;
 | 
						|
    while ($seconds < 0) $seconds += 86400;
 | 
						|
    if ($seconds > 86400) $seconds %= 86400;
 | 
						|
    $this->seconds = $seconds;
 | 
						|
    return $this;
 | 
						|
  }
 | 
						|
 | 
						|
  protected function afterUpdate(): void {
 | 
						|
    if ($this->WRAP()) $this->wrapEnd();
 | 
						|
  }
 | 
						|
 | 
						|
  /** @var int */
 | 
						|
  protected $seconds;
 | 
						|
 | 
						|
  /** @return int le nombre de secondes */
 | 
						|
  function getSeconds(): int {
 | 
						|
    return $this->seconds;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * mettre à jour cet objet avec le nombre de secondes spécifié
 | 
						|
   *
 | 
						|
   * @return int le nombre de seconde effectif, après correction
 | 
						|
   */
 | 
						|
  protected function setSeconds(?int $seconds): int {
 | 
						|
    if ($seconds === null) {
 | 
						|
      $hms = date('His');
 | 
						|
      $seconds = intval(substr($hms, 0, 2)) * 3600
 | 
						|
        + intval(substr($hms, 2, 2)) * 60
 | 
						|
        + intval(substr($hms, 4, 2));
 | 
						|
    }
 | 
						|
    $adjust = $seconds % $this->UNIT();
 | 
						|
    if ($seconds < 0) $adjust = -$adjust;
 | 
						|
    $seconds -= $adjust;
 | 
						|
    $this->seconds = $seconds;
 | 
						|
    $this->afterUpdate();
 | 
						|
    return $this->seconds;
 | 
						|
  }
 | 
						|
 | 
						|
  /** formatter cette heure pour affichage */
 | 
						|
  function format(?string $format=null): string {
 | 
						|
    if ($format === null) $format = static::FORMAT;
 | 
						|
    $v = $this->seconds;
 | 
						|
    $h = intdiv($v, 3600); $v = $v % 3600;
 | 
						|
    $m = intdiv($v, 60);
 | 
						|
    $s = $v % 60;
 | 
						|
    $searches = [
 | 
						|
      "HH", "H",
 | 
						|
      "MM", "M",
 | 
						|
      "SS", "S",
 | 
						|
    ];
 | 
						|
    $replaces = [
 | 
						|
      sprintf("%02u", $h), strval($h),
 | 
						|
      sprintf("%02u", $m), strval($m),
 | 
						|
      sprintf("%02u", $s), strval($s),
 | 
						|
    ];
 | 
						|
    return str_replace($searches, $replaces, $format);
 | 
						|
  }
 | 
						|
 | 
						|
  function __toString(): string {
 | 
						|
    return $this->format();
 | 
						|
  }
 | 
						|
 | 
						|
  /** @return int le nombre d'unités */
 | 
						|
  function getu(): int {
 | 
						|
    return intdiv($this->seconds, $this->UNIT());
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure avec le nombre d'unités spécifiées */
 | 
						|
  function newu(int $count): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($count * $this->UNIT());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure en ajoutant à cette heure le nombre d'unités spécifiées */
 | 
						|
  function addu(int $count): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($clone->getSeconds() + $count * $this->UNIT());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure en soustrayant à cette heure le nombre d'unités spécifiées */
 | 
						|
  function subu(int $count): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($clone->getSeconds() - $count * $this->UNIT());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /** mettre à jour cette heure avec le nombre de secondes d'une autre instance */
 | 
						|
  function set(Time $time): self {
 | 
						|
    $this->setSeconds($time->getSeconds());
 | 
						|
    return $this;
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure copie de l'heure spécifiée */
 | 
						|
  function new(Time $time): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef() || $time->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($time->getSeconds());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure en ajoutant à cette heure l'heure spécifiée */
 | 
						|
  function add(Time $time): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef() || $time->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($clone->getSeconds() + $time->getSeconds());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /** créer une nouvelle heure en soustrayant à cette heure l'heure spécifiée */
 | 
						|
  function sub(Time $time): self {
 | 
						|
    if ($this->isNull()) return self::null();
 | 
						|
    if ($this->isUndef() || $time->isUndef()) return self::undef();
 | 
						|
    $clone = clone $this;
 | 
						|
    $clone->setSeconds($clone->getSeconds() - $time->getSeconds());
 | 
						|
    return $clone;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * comparer avec l'heure spécifiée. retourner une valeur négative, égale à
 | 
						|
   * zéro ou positive suivant le résultat de la comparaison
 | 
						|
   */
 | 
						|
  function cmp(Time $time): int {
 | 
						|
    return $this->seconds - $time->getSeconds();
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si cette heure est avant l'heure spécifiée */
 | 
						|
  function before(Time $other): bool {
 | 
						|
    return $this->seconds <= $other->getSeconds();
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si cette heure est après l'heure spécifiée */
 | 
						|
  function after(Time $other): bool {
 | 
						|
    return $this->seconds >= $other->getSeconds();
 | 
						|
  }
 | 
						|
 | 
						|
  #############################################################################
 | 
						|
 | 
						|
  private static $null;
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return Time une instance immutable représentant la valeur nulle.
 | 
						|
   *
 | 
						|
   * En général, on utilise directement null pour indiquer qu'il n'y a pas de
 | 
						|
   * valeur, mais cette instance peut etre utilisée dans les contextes où null
 | 
						|
   * n'est pas autorisé
 | 
						|
   */
 | 
						|
  static final function null(): self {
 | 
						|
    if (self::$null === null) {
 | 
						|
      self::$null = new class extends Time {
 | 
						|
        function __construct() {
 | 
						|
          parent::__construct(false);
 | 
						|
          $this->seconds = 0;
 | 
						|
        }
 | 
						|
        function isNull(): bool { return true; }
 | 
						|
        function wrapStart(): Time {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function wrapEnd(): Time {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function setSeconds(?int $seconds): int {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function __toString(): string {
 | 
						|
          return "";
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
    return self::$null;
 | 
						|
  }
 | 
						|
 | 
						|
  private static $undef;
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return Time une instance immutable représentant une heure non définie,
 | 
						|
   * (c'est à dire qu'on ne sait pas de quelle heure il s'agit). Cette
 | 
						|
   * instance peut etre utilisée s'il faut pouvoir faire la différence entre
 | 
						|
   * pas de valeur (null) et une valeur non définie (false)
 | 
						|
   */
 | 
						|
  static final function undef(): self {
 | 
						|
    if (self::$undef === null) {
 | 
						|
      self::$undef = new class extends Time {
 | 
						|
        function __construct() {
 | 
						|
          parent::__construct(false);
 | 
						|
          $this->seconds = 0;
 | 
						|
        }
 | 
						|
        function isUndef(): bool { return true; }
 | 
						|
        function wrapStart(): Time {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function wrapEnd(): Time {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function setSeconds(?int $seconds): int {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function __toString(): string {
 | 
						|
          return "";
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
    return self::$undef;
 | 
						|
  }
 | 
						|
}
 |