189 lines
5.9 KiB
PHP
189 lines
5.9 KiB
PHP
|
<?php
|
||
|
namespace nulib\php\time;
|
||
|
|
||
|
use DateTimeInterface;
|
||
|
use DateTimeZone;
|
||
|
use InvalidArgumentException;
|
||
|
|
||
|
/**
|
||
|
* Class DateTime: une date et une heure
|
||
|
*
|
||
|
* @property-read int $year
|
||
|
* @property-read int $month
|
||
|
* @property-read int $day
|
||
|
* @property-read int $hour
|
||
|
* @property-read int $minute
|
||
|
* @property-read int $second
|
||
|
* @property-read int $wday
|
||
|
* @property-read int $wnum
|
||
|
* @property-read string $timezone
|
||
|
* @property-read string $datetime
|
||
|
* @property-read string $date
|
||
|
* @property-read string $Ymd
|
||
|
* @property-read string $YmdHMS
|
||
|
* @property-read string $YmdHMSZ
|
||
|
*/
|
||
|
class DateTime extends \DateTime {
|
||
|
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||
|
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||
|
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||
|
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})[tT](\d{2})(\d{2})(\d{2})?([zZ])?$/';
|
||
|
|
||
|
static function _YmdHMSZ_format(\DateTime $datetime): string {
|
||
|
$YmdHMS = $datetime->format("Ymd\\THis");
|
||
|
$Z = $datetime->format("P");
|
||
|
if ($Z === "+00:00") $Z = "Z";
|
||
|
return "$YmdHMS$Z";
|
||
|
}
|
||
|
|
||
|
const DEFAULT_FORMAT = "d/m/Y H:i:s";
|
||
|
const INT_FORMATS = [
|
||
|
"year" => "Y",
|
||
|
"month" => "m",
|
||
|
"day" => "d",
|
||
|
"hour" => "H",
|
||
|
"minute" => "i",
|
||
|
"second" => "s",
|
||
|
"wday" => "N",
|
||
|
"wnum" => "W",
|
||
|
];
|
||
|
const STRING_FORMATS = [
|
||
|
"timezone" => "P",
|
||
|
"datetime" => "d/m/Y H:i:s",
|
||
|
"date" => "d/m/Y",
|
||
|
"Ymd" => "Ymd",
|
||
|
"YmdHMS" => "Ymd\\THis",
|
||
|
"YmdHMSZ" => [self::class, "_YmdHMSZ_format"],
|
||
|
];
|
||
|
|
||
|
static function clone(DateTimeInterface $dateTime): self {
|
||
|
if ($dateTime instanceof static) return clone $dateTime;
|
||
|
$clone = new static();
|
||
|
$clone->setTimestamp($dateTime->getTimestamp());
|
||
|
$clone->setTimezone($dateTime->getTimezone());
|
||
|
return $clone;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 = intval(date("Y"));
|
||
|
$r = $y % 100;
|
||
|
$c = $y - $r;
|
||
|
if ($year >= 70) $year += $c - 100;
|
||
|
else $year += $c;
|
||
|
}
|
||
|
return $year;
|
||
|
}
|
||
|
|
||
|
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
||
|
if ($datetime instanceof \DateTimeInterface) {
|
||
|
if ($timezone === null) $timezone = $datetime->getTimezone();
|
||
|
parent::__construct();
|
||
|
$this->setTimestamp($datetime->getTimestamp());
|
||
|
$this->setTimezone($timezone);
|
||
|
} elseif (is_int($datetime)) {
|
||
|
parent::__construct("now", $timezone);
|
||
|
$this->setTimestamp($datetime);
|
||
|
} elseif (!is_string($datetime)) {
|
||
|
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
|
||
|
} else {
|
||
|
$Y = $H = $Z = null;
|
||
|
if (preg_match(self::DMY_PATTERN, $datetime, $ms)) {
|
||
|
$Y = $ms[3] ?? null;
|
||
|
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
|
||
|
else $Y = intval(date("Y"));
|
||
|
$m = intval($ms[2]);
|
||
|
$d = intval($ms[1]);
|
||
|
} elseif (preg_match(self::YMD_PATTERN, $datetime, $ms)) {
|
||
|
$Y = $ms[1];
|
||
|
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
|
||
|
else $Y = intval($Y);
|
||
|
$m = intval($ms[2]);
|
||
|
$d = intval($ms[3]);
|
||
|
} elseif (preg_match(self::DMYHIS_PATTERN, $datetime, $ms)) {
|
||
|
$Y = $ms[3];
|
||
|
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
|
||
|
else $Y = intval(date("Y"));
|
||
|
$m = intval($ms[2]);
|
||
|
$d = intval($ms[1]);
|
||
|
$H = intval($ms[4]);
|
||
|
$M = intval($ms[5]);
|
||
|
$S = intval($ms[6] ?? 0);
|
||
|
} elseif (preg_match(self::YMDHISZ_PATTERN, $datetime, $ms)) {
|
||
|
$Y = $ms[1];
|
||
|
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
|
||
|
else $Y = intval($Y);
|
||
|
$m = intval($ms[2]);
|
||
|
$d = intval($ms[3]);
|
||
|
$H = intval($ms[4]);
|
||
|
$M = intval($ms[5]);
|
||
|
$S = intval($ms[6] ?? 0);
|
||
|
$Z = $ms[7] ?? null;
|
||
|
}
|
||
|
if ($Y !== null) {
|
||
|
if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
||
|
else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S);
|
||
|
if ($Z !== null) $timezone = new DateTimeZone("UTC");
|
||
|
}
|
||
|
parent::__construct($datetime, $timezone);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function diff($target, $absolute=false): DateInterval {
|
||
|
return new DateInterval(parent::diff($target, $absolute));
|
||
|
}
|
||
|
|
||
|
function format($format=self::DEFAULT_FORMAT): string {
|
||
|
return \DateTime::format($format);
|
||
|
}
|
||
|
|
||
|
function __toString(): string {
|
||
|
return $this->format();
|
||
|
}
|
||
|
|
||
|
function __get($name) {
|
||
|
if (array_key_exists($name, self::INT_FORMATS)) {
|
||
|
$format = self::INT_FORMATS[$name];
|
||
|
if (is_callable($format)) return $format($this);
|
||
|
else return intval($this->format($format));
|
||
|
} elseif (array_key_exists($name, self::STRING_FORMATS)) {
|
||
|
$format = self::STRING_FORMATS[$name];
|
||
|
if (is_callable($format)) return $format($this);
|
||
|
else return $this->format($format);
|
||
|
}
|
||
|
throw new InvalidArgumentException("Unknown property $name");
|
||
|
}
|
||
|
}
|