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