278 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\b\date;
 | 
						|
 | 
						|
use nur\b\IllegalAccessException;
 | 
						|
use nur\b\ValueException;
 | 
						|
use nur\b\values\IValueState;
 | 
						|
 | 
						|
/**
 | 
						|
 * Class Datetime: une date et une heure
 | 
						|
 *
 | 
						|
 * Cet objet est considéré comme immutable.
 | 
						|
 */
 | 
						|
class Datetime implements IValueState {
 | 
						|
  /** @var string format normalisé pour l'analyse */
 | 
						|
  const PATTERN = '/^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/';
 | 
						|
  /** @var string format par défaut pour l'affichage */
 | 
						|
  const FORMAT = "d/m/Y H:i:s";
 | 
						|
 | 
						|
  /**
 | 
						|
   * corriger une année à deux chiffres qui est située dans le passé et
 | 
						|
   * retourner l'année à 4 chiffres.
 | 
						|
   *
 | 
						|
   * par exemple, si l'année courante est 2019, alors:
 | 
						|
   * - fix_past_year('18') === '2018'
 | 
						|
   * - fix_past_year('19') === '1919'
 | 
						|
   * - fix_past_year('20') === '1920'
 | 
						|
   */
 | 
						|
  static function fix_past_year(int $year): int {
 | 
						|
    if ($year < 100) {
 | 
						|
      $y = getdate(); $y = $y["year"];
 | 
						|
      $r = $y % 100;
 | 
						|
      $c = $y - $r;
 | 
						|
      if ($year >= $r) $year += $c - 100;
 | 
						|
      else $year += $c;
 | 
						|
    }
 | 
						|
    return $year;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * corriger une année à deux chiffres et retourner l'année à 4 chiffres.
 | 
						|
   * l'année charnière entre année passée et année future est 70
 | 
						|
   *
 | 
						|
   * par exemple, si l'année courante est 2019, alors:
 | 
						|
   * - fix_past_year('18') === '2018'
 | 
						|
   * - fix_past_year('19') === '2019'
 | 
						|
   * - fix_past_year('20') === '2020'
 | 
						|
   * - fix_past_year('69') === '2069'
 | 
						|
   * - fix_past_year('70') === '1970'
 | 
						|
   * - fix_past_year('71') === '1971'
 | 
						|
   */
 | 
						|
  static function fix_any_year(int $year): int {
 | 
						|
    if ($year < 100) {
 | 
						|
      $y = getdate(); $y = $y["year"];
 | 
						|
      $r = $y % 100;
 | 
						|
      $c = $y - $r;
 | 
						|
      if ($year >= 70) $year += $c - 100;
 | 
						|
      else $year += $c;
 | 
						|
    }
 | 
						|
    return $year;
 | 
						|
  }
 | 
						|
 | 
						|
  static function parse_datetime($datetime): int {
 | 
						|
    if ($datetime instanceof Datetime) {
 | 
						|
      $datetime = $datetime->getTime();
 | 
						|
    } elseif ($datetime === null || is_int($datetime)) {
 | 
						|
      if ($datetime === null) $datetime = time();
 | 
						|
    } elseif (is_array($datetime)) {
 | 
						|
      [$y, $m, $d, $H, $M, $S] = $datetime;
 | 
						|
      $datetime = mktime($H, $M, $S, $m, $d, $y);
 | 
						|
    } elseif (is_string($datetime) && preg_match(self::PATTERN, $datetime, $ms)) {
 | 
						|
      $d = intval($ms[1]);
 | 
						|
      $m = intval($ms[2]);
 | 
						|
      $y = intval($ms[3]);
 | 
						|
      $H = intval($ms[4]);
 | 
						|
      $M = intval($ms[5]);
 | 
						|
      $S = intval($ms[6]);
 | 
						|
      $datetime = mktime($H, $M, $S, $m, $d, $y);
 | 
						|
    } else {
 | 
						|
      throw ValueException::invalid_value($datetime, "datetime");
 | 
						|
    }
 | 
						|
    return $datetime;
 | 
						|
  }
 | 
						|
 | 
						|
  function __construct($datetime=null) {
 | 
						|
    if ($datetime !== false) {
 | 
						|
      $this->setTime(self::parse_datetime($datetime));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /** 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; }
 | 
						|
 | 
						|
  /**
 | 
						|
   * modifier la date pour que l'heure soit à 00:00:00 ce qui la rend propice à
 | 
						|
   * l'utilisation comme borne inférieure d'une période
 | 
						|
   */
 | 
						|
  function wrapStart(): self {
 | 
						|
    $time = new Time(date("H:i:s", $this->time));
 | 
						|
    $this->time -= $time->getSeconds();
 | 
						|
    return $this;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * modifier la date pour que l'heure soit à 23:59:59 ce qui la rend propice à
 | 
						|
   * l'utilisation comme borne supérieure d'une période
 | 
						|
   */
 | 
						|
  function wrapEnd(): self {
 | 
						|
    [$d, $m, $y] = explode("/", date("d/m/Y", $this->time));
 | 
						|
    $this->time = mktime(0, 0, 0, intval($m), intval($d) + 1, intval($y)) - 1;
 | 
						|
    return $this;
 | 
						|
  }
 | 
						|
 | 
						|
  protected function afterUpdate(): void {
 | 
						|
  }
 | 
						|
 | 
						|
  /** @var int */
 | 
						|
  protected $time;
 | 
						|
 | 
						|
  function getTime(): int {
 | 
						|
    return $this->time;
 | 
						|
  }
 | 
						|
 | 
						|
  protected function setTime(?int $time): int {
 | 
						|
    if ($time === null) $time = time();
 | 
						|
    $this->time = $time;
 | 
						|
    $this->afterUpdate();
 | 
						|
    return $this->time;
 | 
						|
  }
 | 
						|
 | 
						|
  protected static function new_d(int $time): \DateTime {
 | 
						|
    $d = new \DateTime();
 | 
						|
    return $d->setTimestamp($time);
 | 
						|
  }
 | 
						|
  protected static function parse_d(string $date, $format="d/m/Y"): \DateTime {
 | 
						|
    return date_create_from_format($format, $date);
 | 
						|
  }
 | 
						|
 | 
						|
  protected static function wday(\DateTime $d): int {
 | 
						|
    return intval($d->format("N"));
 | 
						|
  }
 | 
						|
  /** obtenir le numéro de jour de la semaine de 1 (lundi) à 7 (dimanche) */
 | 
						|
  function getWday(): int {
 | 
						|
    return self::wday(self::new_d($this->time));
 | 
						|
  }
 | 
						|
 | 
						|
  protected static function wnum(\DateTime $d): int {
 | 
						|
    return intval($d->format("W"));
 | 
						|
  }
 | 
						|
  /** obtenir le numéro de la semaine 1-52 */
 | 
						|
  function getWnum(): int {
 | 
						|
    return self::wnum(self::new_d($this->time));
 | 
						|
  }
 | 
						|
 | 
						|
  function getPrevDay(int $nbdays=1, bool $skipWeekend=false): self {
 | 
						|
    $d = self::new_d($this->time);
 | 
						|
    if ($nbdays == 1 && $skipWeekend) {
 | 
						|
      $wday = self::wday($d);
 | 
						|
      if ($wday == 1) $nbdays = 3;
 | 
						|
    }
 | 
						|
    $d->sub(new \DateInterval("P".$nbdays."D"));
 | 
						|
    return new static($d->getTimestamp());
 | 
						|
  }
 | 
						|
 | 
						|
  function getNextDay(int $nbdays=1, bool $skipWeekend=false): self {
 | 
						|
    $d = self::new_d($this->time);
 | 
						|
    if ($nbdays == 1 && $skipWeekend) {
 | 
						|
      $wday = self::wday($d);
 | 
						|
      if ($wday >= 5) $nbdays = 8 - $wday;
 | 
						|
    }
 | 
						|
    $d->add(new \DateInterval("P".$nbdays."D"));
 | 
						|
    return new static($d->getTimestamp());
 | 
						|
  }
 | 
						|
 | 
						|
  function format(?string $format=null) {
 | 
						|
    if ($format === null) $format = static::FORMAT;
 | 
						|
    return date($format, $this->time);
 | 
						|
  }
 | 
						|
 | 
						|
  function formatRfc4517(): string {
 | 
						|
    return gmdate("YmdHis", $this->time)."Z";
 | 
						|
  }
 | 
						|
 | 
						|
  function __toString(): string {
 | 
						|
    return $this->format();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * comparer avec la datetime spécifiée. retourner une valeur négative, égale à
 | 
						|
   * zéro ou positive suivant le résultat de la comparaison
 | 
						|
   */
 | 
						|
  function cmp(Datetime $other): int {
 | 
						|
    return $this->time - $other->getTime();
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si cette datetime est avant la datetime spécifiée */
 | 
						|
  function before(Datetime $other): bool {
 | 
						|
    return $this->time <= $other->getTime();
 | 
						|
  }
 | 
						|
 | 
						|
  /** tester si cette datetime est après la datetime spécifiée */
 | 
						|
  function after(Datetime $other): bool {
 | 
						|
    return $this->time >= $other->getTime();
 | 
						|
  }
 | 
						|
 | 
						|
  function getElapsed(?Datetime $now=null, int $resolution=Elapsed::RES_SECONDS): Elapsed {
 | 
						|
    if ($now === null) $now = new Datetime();
 | 
						|
    if ($resolution === Elapsed::RES_DAYS) {
 | 
						|
      $now = (new static($now))->wrapStart();
 | 
						|
      $self = (new static($this))->wrapStart();
 | 
						|
    } else {
 | 
						|
      $self = $this;
 | 
						|
    }
 | 
						|
    return new Elapsed($now->getTime() - $self->getTime(), $resolution);
 | 
						|
  }
 | 
						|
 | 
						|
  #############################################################################
 | 
						|
 | 
						|
  private static $null;
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return Datetime 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 Datetime {
 | 
						|
        function __construct() {
 | 
						|
          parent::__construct(false);
 | 
						|
          $this->seconds = 0;
 | 
						|
          self::$null = $this;
 | 
						|
        }
 | 
						|
        function isNull(): bool { return true; }
 | 
						|
        function setTime(?int $time): int {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function __toString(): string {
 | 
						|
          return "";
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
    return self::$null;
 | 
						|
  }
 | 
						|
 | 
						|
  private static $undef;
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return Datetime une instance immutable représentant une date non définie,
 | 
						|
   * (c'est à dire qu'on ne sait pas de quelle date 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 Datetime {
 | 
						|
        function __construct() {
 | 
						|
          parent::__construct(false);
 | 
						|
          $this->seconds = 0;
 | 
						|
          self::$undef = $this;
 | 
						|
        }
 | 
						|
        function isUndef(): bool { return true; }
 | 
						|
        function setTime(?int $time): int {
 | 
						|
          throw IllegalAccessException::immutable_object();
 | 
						|
        }
 | 
						|
        function __toString(): string {
 | 
						|
          return "";
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
    return self::$undef;
 | 
						|
  }
 | 
						|
}
 |