nur-sery/nur_src/b/date/Time.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;
}
}