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