Date et DateTime sont immutable

This commit is contained in:
Jephté Clain 2025-10-07 05:34:58 +04:00
parent 810ead58d6
commit c748fed388
13 changed files with 783 additions and 336 deletions

View File

@ -1,7 +1,7 @@
<?php
namespace nulib\php\time;
use DateTimeZone;
use DateTimeInterface;
/**
* Class Date: une date
@ -9,9 +9,14 @@ use DateTimeZone;
class Date extends DateTime {
const DEFAULT_FORMAT = "d/m/Y";
function __construct($datetime="now", DateTimeZone $timezone=null) {
parent::__construct($datetime, $timezone);
$this->setTime(0, 0);
protected function fix(DateTimeInterface $datetime): \DateTimeInterface {
return $datetime->setTime(0, 0);
}
/** @return MutableDate|self */
function clone(bool $mutable=false): DateTimeInterface {
if ($mutable) return new MutableDate($this);
else return clone $this;
}
function format($format=self::DEFAULT_FORMAT): string {

View File

@ -1,10 +1,10 @@
<?php
namespace nulib\php\time;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
use nulib\str;
/**
* Class DateTime: une date et une heure
@ -24,252 +24,78 @@ use nulib\str;
* @property-read string $YmdHMS
* @property-read string $YmdHMSZ
*/
class DateTime extends \DateTime {
static function with($datetime): self {
if ($datetime instanceof static) return $datetime;
else return new static($datetime);
}
static function withn($datetime): ?self {
if ($datetime === null) return null;
elseif ($datetime instanceof static) return $datetime;
else return new static($datetime);
}
static function ensure(&$datetime): void {
$datetime = static::withn($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]|\+\d{2}:?\d{2})?$/';
protected static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) {
$value = null;
if ($value === null && $key !== null) $value = $datetime[$key] ?? null;
if ($value === null && $k !== null) $value = $datetime[$k] ?? null;
if ($value === null && $index !== null) $value = $datetime[$index] ?? null;
return $value;
}
private static function parse_int(array $datetime, ?string $key, ?string $k, ?int $index, ?int &$part, bool $required=true, ?int $default=null): bool {
$part = null;
$value = self::get_value($datetime, $key, $k, $index);
if ($value === null) {
if ($required && $default === null) return false;
$part = $default;
return true;
}
if (is_numeric($value)) {
$part = intval($value);
return true;
}
return false;
}
private static function parse_str(array $datetime, ?string $key, ?string $k, ?int $index, ?string &$part, bool $required = true, ?string $default=null): bool {
$part = null;
$value = self::get_value($datetime, $key, $k, $index);
if ($value === null) {
if ($required && $default === null) return false;
$part = $default;
return true;
}
if (is_string($value)) {
$part = $value;
return true;
}
return false;
}
protected static function parse_array(array $datetime): ?array {
if (!self::parse_int($datetime, "year", "Y", 0, $year)) return null;
if (!self::parse_int($datetime, "month", "m", 1, $month)) return null;
if (!self::parse_int($datetime, "day", "d", 2, $day)) return null;
self::parse_int($datetime, "hour", "H", 3, $hour, false);
self::parse_int($datetime, "minute", "M", 4, $minute, false);
self::parse_int($datetime, "second", "S", 5, $second, false);
self::parse_str($datetime, "tz", null, 6, $tz, false);
return [$year, $month, $day, $hour, $minute, $second, $tz];
}
static function isa($datetime): bool {
if ($datetime === null) return false;
if ($datetime instanceof DateTimeInterface) return true;
if (is_int($datetime)) return true;
if (is_string($datetime)) {
return preg_match(self::DMY_PATTERN, $datetime) ||
preg_match(self::YMD_PATTERN, $datetime) ||
preg_match(self::DMYHIS_PATTERN, $datetime) ||
preg_match(self::YMDHISZ_PATTERN, $datetime);
}
if (is_array($datetime)) {
return self::parse_array($datetime) !== null;
}
return false;
}
static function isa_datetime($datetime, bool $frOnly=false): bool {
if ($datetime === null) return false;
if ($datetime instanceof DateTimeInterface) return true;
if (is_int($datetime)) return true;
if (is_string($datetime)) {
return preg_match(self::DMYHIS_PATTERN, $datetime) ||
(!$frOnly && preg_match(self::YMDHISZ_PATTERN, $datetime));
}
if (is_array($datetime)) {
return self::parse_array($datetime) !== null;
}
return false;
}
static function isa_date($date, bool $frOnly=false): bool {
if ($date === null) return false;
if ($date instanceof DateTimeInterface) return true;
if (is_int($date)) return true;
if (is_string($date)) {
return preg_match(self::DMY_PATTERN, $date) ||
(!$frOnly && preg_match(self::YMD_PATTERN, $date));
}
if (is_array($date)) {
return self::parse_array($date) !== null;
}
return false;
}
/** retourner le nombre de secondes depuis minuit */
static function _nbsecs_format(\DateTime $datetime): string {
[$h, $m, $s] = explode(",", $datetime->format("H,i,s"));
return $h * 3600 + $m * 60 + $s;
}
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";
}
class DateTime extends \DateTimeImmutable {
use _TDateTime;
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",
"nbsecs" => [self::class, "_nbsecs_format"],
];
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"],
];
/**
* corriger une année à deux chiffres qui est située dans le passé et
* retourner l'année à 4 chiffres.
* $datetime est une spécification de date, avec ou sans fuseau horaire
*
* 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
* si $datetime ne contient pas de fuseau horaire, elle est réputée être dans
* le fuseau $timezone, qui est le fuseau local par défaut
*
* 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'
* si $datetime contient un fuseau horaire et si $forceTimezone est vrai,
* alors $datetime est réexprimée dans le fuseau $timezone.
* si $timezone est null alors $forceTimezone vaut vrai par défaut.
*
* datetime | timezone | forceTimezone | résultat
* -----------------|----------|---------------|---------
* datetime | any | any | datetime+localtz
* datetime+origtz | null | null | datetime+localtz
* datetime+origtz | null | true | datetime+localtz
* datetime+origtz | null | false | datetime+origtz
* datetime+origtz | newtz | null | datetime+origtz
* datetime+origtz | newtz | false | datetime+origtz
* datetime+origtz | newtz | true | datetime+newtz
*/
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;
}
static function fix_z(?string $Z): ?string {
$Z = strtoupper($Z);
str::del_prefix($Z, "+");
if (preg_match('/^\d{4}$/', $Z)) {
$Z = substr($Z, 0, 2).":".substr($Z, 2);
}
if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC";
return "GMT+$Z";
}
function __construct($datetime="now", DateTimeZone $timezone=null, ?bool $forceLocalTimezone=null) {
$forceLocalTimezone ??= $timezone === null;
if ($forceLocalTimezone) {
$setTimezone = $timezone;
function __construct($datetime=null, DateTimeZone $timezone=null, ?bool $forceTimezone=null) {
$resetTimezone = null;
$forceTimezone ??= $timezone === null;
if ($forceTimezone) {
$resetTimezone = $timezone ?? new DateTimeZone(date_default_timezone_get());
$timezone = null;
}
$datetime ??= "now";
if ($datetime instanceof \DateTimeInterface) {
$timezone ??= $datetime->getTimezone();
parent::__construct();
$this->setTimestamp($datetime->getTimestamp());
$this->setTimezone($timezone);
if ($datetime instanceof DateTimeImmutable) {
$datetime = \DateTime::createFromImmutable($datetime);
} elseif ($datetime instanceof \DateTime) {
$datetime = clone $datetime;
#XXX sous PHP 8, remplacer les deux commandes ci-dessus par
# DateTime::createFromInterface
} elseif (is_int($datetime)) {
parent::__construct("now", $timezone);
$this->setTimestamp($datetime);
$timestamp = $datetime;
$datetime = new \DateTime("now", $timezone);
$datetime->setTimestamp($timestamp);
} elseif (is_string($datetime)) {
$Y = $H = $Z = null;
if (preg_match(self::DMY_PATTERN, $datetime, $ms)) {
if (preg_match(_utils::DMY_PATTERN, $datetime, $ms)) {
$Y = $ms[3] ?? null;
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
if ($Y !== null) $Y = _utils::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)) {
} elseif (preg_match(_utils::YMD_PATTERN, $datetime, $ms)) {
$Y = $ms[1];
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
if (strlen($Y) == 2) $Y = _utils::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)) {
} elseif (preg_match(_utils::DMYHIS_PATTERN, $datetime, $ms)) {
$Y = $ms[3];
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
if ($Y !== null) $Y = _utils::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)) {
} elseif (preg_match(_utils::YMDHISZ_PATTERN, $datetime, $ms)) {
$Y = $ms[1];
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1]));
else $Y = intval($Y);
$m = intval($ms[2]);
$d = intval($ms[3]);
@ -281,73 +107,61 @@ class DateTime extends \DateTime {
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(self::fix_z($Z));
if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z));
}
parent::__construct($datetime, $timezone);
$datetime = new \DateTime($datetime, $timezone);
} elseif (is_array($datetime) && ($datetime = self::parse_array($datetime)) !== null) {
$setTimezone = $timezone;
$timezone = null;
} elseif (is_array($datetime) && ($datetime = _utils::parse_array($datetime)) !== null) {
[$Y, $m, $d, $H, $M, $S, $Z] = $datetime;
if ($H === null && $M === null && $S === null) {
$datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
} else {
$datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0);
}
if ($Z !== null) $timezone ??= new DateTimeZone(self::fix_z($Z));
parent::__construct($datetime, $timezone);
if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z));
$datetime = new \DateTime($datetime, $timezone);
}
} else {
if ($datetime instanceof DateTimeInterface) {
if ($resetTimezone !== null) $datetime->setTimezone($resetTimezone);
$datetime = $this->fix($datetime);
parent::__construct($datetime->format("Y-m-d\\TH:i:s.uP"));
} else {
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
}
if ($forceLocalTimezone) {
$setTimezone ??= new DateTimeZone(date_default_timezone_get());
$this->setTimezone($setTimezone);
}
}
function clone(): self {
return clone $this;
protected function fix(DateTimeInterface $datetime): DateTimeInterface {
return $datetime;
}
function diff($target, $absolute=false): DateInterval {
return new DateInterval(parent::diff($target, $absolute));
}
function format($format=self::DEFAULT_FORMAT): string {
if (array_key_exists($format, self::INT_FORMATS)) {
$format = self::INT_FORMATS[$format];
} elseif (array_key_exists($format, self::STRING_FORMATS)) {
$format = self::STRING_FORMATS[$format];
}
if (is_callable($format)) return $format($this);
else return \DateTime::format($format);
/** @return MutableDateTime|self */
function clone(bool $mutable=false): DateTimeInterface {
if ($mutable) return new MutableDateTime($this);
else return clone $this;
}
/**
* modifier cet objet pour que l'heure soit à 00:00:00 ce qui le rend propice
* à l'utilisation comme borne inférieure d'une période
*/
function wrapStartOfDay(): self {
$this->setTime(0, 0);
return $this;
function getStartOfDay(): self {
return new static($this->setTime(0, 0));
}
/**
* modifier cet objet pour que l'heure soit à 23:59:59.999999 ce qui le rend
* propice à l'utilisation comme borne supérieure d'une période
*/
function wrapEndOfDay(): self {
$this->setTime(23, 59, 59, 999999);
return $this;
function getEndOfDay(): self {
return new static($this->setTime(23, 59, 59, 999999));
}
function getPrevDay(int $nbDays=1, bool $skipWeekend=false): self {
if ($nbDays == 1 && $skipWeekend && $this->wday == 1) {
$nbdays = 3;
$nbDays = 3;
}
return static::with($this->sub(new \DateInterval("P${nbDays}D")));
return new static($this->sub(new \DateInterval("P${nbDays}D")));
}
function getNextDay(int $nbDays=1, bool $skipWeekend=false): self {
@ -355,35 +169,6 @@ class DateTime extends \DateTime {
$wday = $this->wday;
if ($wday > 5) $nbDays = 8 - $this->wday;
}
return static::with($this->add(new \DateInterval("P${nbDays}D")));
}
function getElapsedAt(?DateTime $now=null, ?int $resolution=null): string {
return Elapsed::format_at($this, $now, $resolution);
}
function getElapsedSince(?DateTime $now=null, ?int $resolution=null): string {
return Elapsed::format_since($this, $now, $resolution);
}
function getElapsedDelay(?DateTime $now=null, ?int $resolution=null): string {
return Elapsed::format_delay($this, $now, $resolution);
}
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 intval($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");
return new static($this->add(new \DateInterval("P${nbDays}D")));
}
}

View File

@ -39,8 +39,7 @@ class Delay {
"s" => [1, 0],
];
static function compute_dest(int $x, string $u, ?int $y, ?DateTimeInterface $from): array {
$dest = DateTime::with($from)->clone();
protected static function compute_dest(int $x, string $u, ?int $y, MutableDateTime $dest): array {
$yu = null;
switch ($u) {
case "w":
@ -89,9 +88,9 @@ class Delay {
}
function __construct($delay, ?DateTimeInterface $from=null) {
if ($from === null) $from = new DateTime();
$from = MutableDateTime::with($from)->clone(true);
if ($delay === "INF") {
$dest = DateTime::with($from)->clone();
$dest = $from;
$dest->add(new DateInterval("P9999Y"));
$repr = "INF";
} elseif (is_int($delay)) {
@ -126,11 +125,11 @@ class Delay {
[$this->dest, $this->repr] = $data;
}
/** @var DateTime */
/** @var MutableDateTime */
protected $dest;
function getDest(): DateTime {
return $this->dest;
return $this->dest->clone();
}
function addDuration($duration) {
@ -157,7 +156,7 @@ class Delay {
}
protected function _getDiff(?DateTimeInterface $now=null): \DateInterval {
if ($now === null) $now = new DateTime();
$now ??= new \DateTime();
return $this->dest->diff($now);
}

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\time;
use DateTimeInterface;
/**
* Class Elapsed: durée entre deux événements
*/
@ -124,19 +126,19 @@ class Elapsed {
return self::format_generic($prefix, $d, 0, 0);
}
static function format_at(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
static function format_at(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string {
$now ??= new DateTime();
$seconds = $now->getTimestamp() - $start->getTimestamp();
return (new self($seconds, $resolution))->formatAt();
}
static function format_since(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
static function format_since(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string {
$now ??= new DateTime();
$seconds = $now->getTimestamp() - $start->getTimestamp();
return (new self($seconds, $resolution))->formatSince();
}
static function format_delay(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
static function format_delay(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string {
$now ??= new DateTime();
$seconds = $now->getTimestamp() - $start->getTimestamp();
return (new self($seconds, $resolution))->formatDelay();

View File

@ -0,0 +1,24 @@
<?php
namespace nulib\php\time;
use DateTimeInterface;
use DateTimeZone;
class MutableDate extends MutableDateTime {
const DEFAULT_FORMAT = "d/m/Y";
function __construct($datetime="now", DateTimeZone $timezone=null) {
parent::__construct($datetime, $timezone);
$this->setTime(0, 0);
}
/** @return Date|self */
function clone(bool $mutable=false): DateTimeInterface {
if ($mutable) return clone $this;
else return new Date($this);
}
function format($format=self::DEFAULT_FORMAT): string {
return parent::format($format);
}
}

View File

@ -0,0 +1,168 @@
<?php
namespace nulib\php\time;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
/**
* Class DateTime: une date et une heure mutables
*
* @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 MutableDateTime extends \DateTime {
use _TDateTime;
const DEFAULT_FORMAT = "d/m/Y H:i:s";
/**
* $datetime est une spécification de date, avec ou sans fuseau horaire
*
* si $datetime ne contient pas de fuseau horaire, elle est réputée être dans
* le fuseau $timezone, qui est le fuseau local par défaut
*
* si $datetime contient un fuseau horaire et si $forceTimezone est vrai,
* alors $datetime est réexprimée dans le fuseau $timezone.
* si $timezone est null alors $forceTimezone vaut vrai par défaut.
*
* datetime | timezone | forceTimezone | résultat
* -----------------|----------|---------------|---------
* datetime | any | any | datetime+localtz
* datetime+origtz | null | null | datetime+localtz
* datetime+origtz | null | true | datetime+localtz
* datetime+origtz | null | false | datetime+origtz
* datetime+origtz | newtz | null | datetime+origtz
* datetime+origtz | newtz | false | datetime+origtz
* datetime+origtz | newtz | true | datetime+newtz
*/
function __construct($datetime=null, DateTimeZone $timezone=null, ?bool $forceTimezone=null) {
$resetTimezone = null;
$forceTimezone ??= $timezone === null;
if ($forceTimezone) {
$resetTimezone = $timezone ?? new DateTimeZone(date_default_timezone_get());
$timezone = null;
}
$datetime ??= "now";
if ($datetime instanceof \DateTimeInterface) {
$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)) {
$Y = $H = $Z = null;
if (preg_match(_utils::DMY_PATTERN, $datetime, $ms)) {
$Y = $ms[3] ?? null;
if ($Y !== null) $Y = _utils::fix_any_year(intval($Y));
else $Y = intval(date("Y"));
$m = intval($ms[2]);
$d = intval($ms[1]);
} elseif (preg_match(_utils::YMD_PATTERN, $datetime, $ms)) {
$Y = $ms[1];
if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1]));
else $Y = intval($Y);
$m = intval($ms[2]);
$d = intval($ms[3]);
} elseif (preg_match(_utils::DMYHIS_PATTERN, $datetime, $ms)) {
$Y = $ms[3];
if ($Y !== null) $Y = _utils::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(_utils::YMDHISZ_PATTERN, $datetime, $ms)) {
$Y = $ms[1];
if (strlen($Y) == 2) $Y = _utils::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(_utils::fix_z($Z));
}
parent::__construct($datetime, $timezone);
} elseif (is_array($datetime) && ($datetime = _utils::parse_array($datetime)) !== null) {
[$Y, $m, $d, $H, $M, $S, $Z] = $datetime;
if ($H === null && $M === null && $S === null) {
$datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
} else {
$datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0);
}
if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z));
parent::__construct($datetime, $timezone);
} else {
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
}
if ($resetTimezone !== null) $this->setTimezone($resetTimezone);
}
/** @return DateTime|self */
function clone(bool $mutable=false): DateTimeInterface {
if ($mutable) return clone $this;
else return new DateTime($this);
}
/**
* modifier cet objet pour que l'heure soit à 00:00:00 ce qui le rend propice
* à l'utilisation comme borne inférieure d'une période
*/
function setStartOfDay(): self {
$this->setTime(0, 0);
return $this;
}
/**
* modifier cet objet pour que l'heure soit à 23:59:59.999999 ce qui le rend
* propice à l'utilisation comme borne supérieure d'une période
*/
function setEndOfDay(): self {
$this->setTime(23, 59, 59, 999999);
return $this;
}
function setPrevDay(int $nbDays=1, bool $skipWeekend=false): self {
if ($nbDays == 1 && $skipWeekend && $this->wday == 1) {
$nbDays = 3;
}
$this->sub(new \DateInterval("P${nbDays}D"));
return $this;
}
function setNextDay(int $nbDays=1, bool $skipWeekend=false): self {
if ($nbDays == 1 && $skipWeekend) {
$wday = $this->wday;
if ($wday > 5) $nbDays = 8 - $this->wday;
}
$this->add(new \DateInterval("P${nbDays}D"));
return $this;
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace nulib\php\time;
use DateTimeInterface;
use InvalidArgumentException;
trait _TDateTime {
static function with($datetime): self {
if ($datetime instanceof static) return $datetime;
else return new static($datetime);
}
static function withn($datetime): ?self {
if ($datetime === null) return null;
elseif ($datetime instanceof static) return $datetime;
else return new static($datetime);
}
static function ensure(&$datetime): void {
$datetime = static::withn($datetime);
}
static function isa($datetime): bool {
if ($datetime === null) return false;
if ($datetime instanceof DateTimeInterface) return true;
if (is_int($datetime)) return true;
if (is_string($datetime)) {
return preg_match(_utils::DMY_PATTERN, $datetime) ||
preg_match(_utils::YMD_PATTERN, $datetime) ||
preg_match(_utils::DMYHIS_PATTERN, $datetime) ||
preg_match(_utils::YMDHISZ_PATTERN, $datetime);
}
if (is_array($datetime)) return _utils::parse_array($datetime) !== null;
return false;
}
static function isa_datetime($datetime, bool $frOnly=false): bool {
if ($datetime === null) return false;
if ($datetime instanceof DateTimeInterface) return true;
if (is_int($datetime)) return true;
if (is_string($datetime)) {
return preg_match(_utils::DMYHIS_PATTERN, $datetime) ||
(!$frOnly && preg_match(_utils::YMDHISZ_PATTERN, $datetime));
}
if (is_array($datetime)) return _utils::parse_array($datetime) !== null;
return false;
}
static function isa_date($date, bool $frOnly=false): bool {
if ($date === null) return false;
if ($date instanceof DateTimeInterface) return true;
if (is_int($date)) return true;
if (is_string($date)) {
return preg_match(_utils::DMY_PATTERN, $date) ||
(!$frOnly && preg_match(_utils::YMD_PATTERN, $date));
}
if (is_array($date)) return _utils::parse_array($date) !== null;
return false;
}
function diff($target, $absolute=false): DateInterval {
return new DateInterval(parent::diff($target, $absolute));
}
function format($format=self::DEFAULT_FORMAT): string {
if (array_key_exists($format, _utils::INT_FORMATS)) {
$format = _utils::INT_FORMATS[$format];
} elseif (array_key_exists($format, _utils::STRING_FORMATS)) {
$format = _utils::STRING_FORMATS[$format];
}
if (is_callable($format)) return $format($this);
else return parent::format($format);
}
function __toString(): string {
return $this->format();
}
function __get($name) {
if (array_key_exists($name, _utils::INT_FORMATS)) {
$format = _utils::INT_FORMATS[$name];
if (is_callable($format)) return intval($format($this));
else return intval($this->format($format));
} elseif (array_key_exists($name, _utils::STRING_FORMATS)) {
$format = _utils::STRING_FORMATS[$name];
if (is_callable($format)) return $format($this);
else return $this->format($format);
}
throw new InvalidArgumentException("Unknown property $name");
}
function getElapsedAt(?DateTimeInterface $now=null, ?int $resolution=null): string {
return Elapsed::format_at($this, $now, $resolution);
}
function getElapsedSince(?DateTimeInterface $now=null, ?int $resolution=null): string {
return Elapsed::format_since($this, $now, $resolution);
}
function getElapsedDelay(?DateTimeInterface $now=null, ?int $resolution=null): string {
return Elapsed::format_delay($this, $now, $resolution);
}
}

147
php/src/php/time/_utils.php Normal file
View File

@ -0,0 +1,147 @@
<?php
namespace nulib\php\time;
use nulib\str;
class _utils {
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]|\+\d{2}:?\d{2})?$/';
/** retourner le nombre de secondes depuis minuit */
static function _nbsecs_format(\DateTimeInterface $datetime): string {
[$h, $m, $s] = explode(",", $datetime->format("H,i,s"));
return $h * 3600 + $m * 60 + $s;
}
static function _YmdHMSZ_format(\DateTimeInterface $datetime): string {
$YmdHMS = $datetime->format("Ymd\\THis");
$Z = $datetime->format("P");
if ($Z === "+00:00") $Z = "Z";
return "$YmdHMS$Z";
}
const INT_FORMATS = [
"year" => "Y",
"month" => "m",
"day" => "d",
"hour" => "H",
"minute" => "i",
"second" => "s",
"wday" => "N",
"wnum" => "W",
"nbsecs" => [self::class, "_nbsecs_format"],
];
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"],
];
/**
* 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;
}
static function fix_z(?string $Z): ?string {
$Z = strtoupper($Z);
str::del_prefix($Z, "+");
if (preg_match('/^\d{4}$/', $Z)) {
$Z = substr($Z, 0, 2) . ":" . substr($Z, 2);
}
if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC";
return "GMT+$Z";
}
static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) {
$value = null;
if ($value === null && $key !== null) $value = $datetime[$key] ?? null;
if ($value === null && $k !== null) $value = $datetime[$k] ?? null;
if ($value === null && $index !== null) $value = $datetime[$index] ?? null;
return $value;
}
static function parse_int(array $datetime, ?string $key, ?string $k, ?int $index, ?int &$part, bool $required = true, ?int $default = null): bool {
$part = null;
$value = self::get_value($datetime, $key, $k, $index);
if ($value === null) {
if ($required && $default === null) return false;
$part = $default;
return true;
}
if (is_numeric($value)) {
$part = intval($value);
return true;
}
return false;
}
static function parse_str(array $datetime, ?string $key, ?string $k, ?int $index, ?string &$part, bool $required = true, ?string $default = null): bool {
$part = null;
$value = self::get_value($datetime, $key, $k, $index);
if ($value === null) {
if ($required && $default === null) return false;
$part = $default;
return true;
}
if (is_string($value)) {
$part = $value;
return true;
}
return false;
}
static function parse_array(array $datetime): ?array {
if (!self::parse_int($datetime, "year", "Y", 0, $year)) return null;
if (!self::parse_int($datetime, "month", "m", 1, $month)) return null;
if (!self::parse_int($datetime, "day", "d", 2, $day)) return null;
self::parse_int($datetime, "hour", "H", 3, $hour, false);
self::parse_int($datetime, "minute", "M", 4, $minute, false);
self::parse_int($datetime, "second", "S", 5, $second, false);
self::parse_str($datetime, "tz", null, 6, $tz, false);
return [$year, $month, $day, $hour, $minute, $second, $tz];
}
}

View File

@ -29,13 +29,15 @@ class DateTest extends TestCase {
function testClone() {
$date = self::dt("now");
$clone = $date->clone(true);
self::assertInstanceOf(MutableDate::class, $clone);
$clone = $date->clone();
self::assertInstanceOf(DateTime::class, $clone);
self::assertInstanceOf(Date::class, $clone);
}
function testConstruct() {
$y = date("Y");
self::assertSame("05/04/$y", strval(new Date("5/4")));
$Y = date("Y");
self::assertSame("05/04/$Y", strval(new Date("5/4")));
self::assertSame("05/04/2024", strval(new Date("5/4/24")));
self::assertSame("05/04/2024", strval(new Date("5/4/2024")));
self::assertSame("05/04/2024", strval(new Date("05/04/2024")));

View File

@ -5,12 +5,8 @@ use DateTimeZone;
use nulib\tests\TestCase;
class DateTimeTest extends TestCase {
protected static function dt(string $datetime): DateTime {
return new DateTime($datetime, new DateTimeZone("Indian/Reunion"));
}
function testDateTime() {
$date = self::dt("2024-04-05 09:15:23");
$date = new DateTime("2024-04-05 09:15:23");
self::assertEquals("05/04/2024 09:15:23", $date->format());
self::assertEquals("05/04/2024 09:15:23", strval($date));
@ -31,24 +27,36 @@ class DateTimeTest extends TestCase {
}
function testDateTimeZ() {
$date = new DateTime("20240405T091523Z");
self::assertSame("20240405T131523", $date->YmdHMS);
self::assertSame("20240405T131523+04:00", $date->YmdHMSZ);
# comme on spécifie la timezone, la valeur Z est ignorée
$date = new DateTime("20240405T091523Z", new DateTimeZone("Indian/Reunion"));
self::assertSame("20240405T091523", $date->YmdHMS);
$date = new DateTime("20240405T091523");
self::assertSame("20240405T091523+04:00", $date->YmdHMSZ);
$date = new DateTime("20240405T091523+02:00", null, null);
self::assertSame("20240405T111523+04:00", $date->YmdHMSZ);
$date = new DateTime("20240405T091523+02:00", null, true);
self::assertSame("20240405T111523+04:00", $date->YmdHMSZ);
$date = new DateTime("20240405T091523+02:00", null, false);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$newtz = new DateTimeZone("+06:00");
$date = new DateTime("20240405T091523+02:00", $newtz, null);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$date = new DateTime("20240405T091523+02:00", $newtz, false);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$date = new DateTime("20240405T091523+02:00", $newtz, true);
self::assertSame("20240405T131523+06:00", $date->YmdHMSZ);
}
function testClone() {
$date = self::dt("now");
$date = new DateTime("now");
$clone = $date->clone(true);
self::assertInstanceOf(MutableDateTime::class, $clone);
$clone = $date->clone();
self::assertInstanceOf(DateTime::class, $clone);
}
function testConstruct() {
$y = date("Y");
self::assertSame("05/04/$y 00:00:00", strval(new DateTime("5/4")));
$Y = date("Y");
self::assertSame("05/04/$Y 00:00:00", strval(new DateTime("5/4")));
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/24")));
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/2024")));
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("05/04/2024")));

View File

@ -5,72 +5,68 @@ use DateTimeZone;
use nulib\tests\TestCase;
class DelayTest extends TestCase {
protected static function dt(string $datetime): DateTime {
return new DateTime($datetime, new DateTimeZone("Indian/Reunion"));
}
function testDelay() {
$from = self::dt("2024-04-05 09:15:23");
$from = new MutableDateTime("2024-04-05 09:15:23");
$delay = new Delay(10, $from);
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest());
$delay = new Delay("10", $from);
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest());
$delay = new Delay("10s", $from);
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest());
$delay = new Delay("s", $from);
self::assertEquals(self::dt("2024-04-05 09:15:24"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:15:24"), $delay->getDest());
$delay = new Delay("5m", $from);
self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:20:00"), $delay->getDest());
$delay = new Delay("5m0", $from);
self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:20:00"), $delay->getDest());
$delay = new Delay("5m2", $from);
self::assertEquals(self::dt("2024-04-05 09:20:02"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:20:02"), $delay->getDest());
$delay = new Delay("m", $from);
self::assertEquals(self::dt("2024-04-05 09:16:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 09:16:00"), $delay->getDest());
$delay = new Delay("5h", $from);
self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 14:00:00"), $delay->getDest());
$delay = new Delay("5h0", $from);
self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 14:00:00"), $delay->getDest());
$delay = new Delay("5h2", $from);
self::assertEquals(self::dt("2024-04-05 14:02:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 14:02:00"), $delay->getDest());
$delay = new Delay("h", $from);
self::assertEquals(self::dt("2024-04-05 10:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-05 10:00:00"), $delay->getDest());
$delay = new Delay("5d", $from);
self::assertEquals(self::dt("2024-04-10 05:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-10 05:00:00"), $delay->getDest());
$delay = new Delay("5d2", $from);
self::assertEquals(self::dt("2024-04-10 02:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-10 02:00:00"), $delay->getDest());
$delay = new Delay("5d0", $from);
self::assertEquals(self::dt("2024-04-10 00:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-10 00:00:00"), $delay->getDest());
$delay = new Delay("d", $from);
self::assertEquals(self::dt("2024-04-06 05:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-06 05:00:00"), $delay->getDest());
$delay = new Delay("2w", $from);
self::assertEquals(self::dt("2024-04-21 05:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-21 05:00:00"), $delay->getDest());
$delay = new Delay("2w2", $from);
self::assertEquals(self::dt("2024-04-21 02:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-21 02:00:00"), $delay->getDest());
$delay = new Delay("2w0", $from);
self::assertEquals(self::dt("2024-04-21 00:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-21 00:00:00"), $delay->getDest());
$delay = new Delay("w", $from);
self::assertEquals(self::dt("2024-04-07 05:00:00"), $delay->getDest());
self::assertEquals(new MutableDateTime("2024-04-07 05:00:00"), $delay->getDest());
}
function testElapsed() {

View File

@ -0,0 +1,87 @@
<?php
namespace nulib\php\time;
use DateTimeZone;
use nulib\tests\TestCase;
class MutableDateTest extends TestCase {
protected static function dt(string $datetime): MutableDate {
return new MutableDate($datetime, new DateTimeZone("Indian/Reunion"));
}
function testDate() {
$date = self::dt("2024-04-05 09:15:23");
self::assertSame("05/04/2024", $date->format());
self::assertSame("05/04/2024", strval($date));
self::assertSame(2024, $date->year);
self::assertSame(4, $date->month);
self::assertSame(5, $date->day);
self::assertSame(0, $date->hour);
self::assertSame(0, $date->minute);
self::assertSame(0, $date->second);
self::assertSame(5, $date->wday);
self::assertSame(14, $date->wnum);
self::assertSame("+04:00", $date->timezone);
self::assertSame("05/04/2024 00:00:00", $date->datetime);
self::assertSame("05/04/2024", $date->date);
}
function testClone() {
$date = self::dt("now");
$clone = $date->clone(true);
self::assertInstanceOf(MutableDate::class, $clone);
$clone = $date->clone();
self::assertInstanceOf(Date::class, $clone);
}
function testConstruct() {
$Y = date("Y");
self::assertSame("05/04/$Y", strval(new MutableDate("5/4")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/24")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024")));
self::assertSame("05/04/2024", strval(new MutableDate("05/04/2024")));
self::assertSame("05/04/2024", strval(new MutableDate("20240405")));
self::assertSame("05/04/2024", strval(new MutableDate("240405")));
self::assertSame("05/04/2024", strval(new MutableDate("20240405T091523")));
self::assertSame("05/04/2024", strval(new MutableDate("20240405T091523Z")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9:15:23")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9.15.23")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9:15")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9.15")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9h15")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09:15:23")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09:15")));
self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09h15")));
}
function testCompare() {
$a = new MutableDate("10/02/2024");
$b = new MutableDate("15/02/2024");
$c = new MutableDate("20/02/2024");
$a2 = new MutableDate("10/02/2024");
$b2 = new MutableDate("15/02/2024");
$c2 = new MutableDate("20/02/2024");
self::assertTrue($a == $a2);
self::assertFalse($a === $a2);
self::assertTrue($b == $b2);
self::assertTrue($c == $c2);
self::assertFalse($a < $a);
self::assertTrue($a < $b);
self::assertTrue($a < $c);
self::assertTrue($a <= $a);
self::assertTrue($a <= $b);
self::assertTrue($a <= $c);
self::assertFalse($c > $c);
self::assertTrue($c > $b);
self::assertTrue($c > $a);
self::assertTrue($c >= $c);
self::assertTrue($c >= $b);
self::assertTrue($c >= $a);
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace nulib\php\time;
use DateTimeZone;
use nulib\tests\TestCase;
class MutableDateTimeTest extends TestCase {
function testDateTime() {
$date = new MutableDateTime("2024-04-05 09:15:23");
self::assertEquals("05/04/2024 09:15:23", $date->format());
self::assertEquals("05/04/2024 09:15:23", strval($date));
self::assertSame(2024, $date->year);
self::assertSame(4, $date->month);
self::assertSame(5, $date->day);
self::assertSame(9, $date->hour);
self::assertSame(15, $date->minute);
self::assertSame(23, $date->second);
self::assertSame(5, $date->wday);
self::assertSame(14, $date->wnum);
self::assertEquals("+04:00", $date->timezone);
self::assertSame("05/04/2024 09:15:23", $date->datetime);
self::assertSame("05/04/2024", $date->date);
self::assertSame("20240405", $date->Ymd);
self::assertSame("20240405T091523", $date->YmdHMS);
self::assertSame("20240405T091523+04:00", $date->YmdHMSZ);
}
function testDateTimeZ() {
$date = new MutableDateTime("20240405T091523");
self::assertSame("20240405T091523+04:00", $date->YmdHMSZ);
$date = new MutableDateTime("20240405T091523+02:00", null, null);
self::assertSame("20240405T111523+04:00", $date->YmdHMSZ);
$date = new MutableDateTime("20240405T091523+02:00", null, true);
self::assertSame("20240405T111523+04:00", $date->YmdHMSZ);
$date = new MutableDateTime("20240405T091523+02:00", null, false);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$newtz = new DateTimeZone("+06:00");
$date = new MutableDateTime("20240405T091523+02:00", $newtz, null);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$date = new MutableDateTime("20240405T091523+02:00", $newtz, false);
self::assertSame("20240405T091523+02:00", $date->YmdHMSZ);
$date = new MutableDateTime("20240405T091523+02:00", $newtz, true);
self::assertSame("20240405T131523+06:00", $date->YmdHMSZ);
}
function testClone() {
$date = new MutableDateTime("now");
$clone = $date->clone(true);
self::assertInstanceOf(MutableDateTime::class, $clone);
$clone = $date->clone();
self::assertInstanceOf(DateTime::class, $clone);
}
function testConstruct() {
$Y = date("Y");
self::assertSame("05/04/$Y 00:00:00", strval(new MutableDateTime("5/4")));
self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("5/4/24")));
self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("5/4/2024")));
self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("05/04/2024")));
self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("20240405")));
self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("240405")));
self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("20240405T091523")));
self::assertSame("05/04/2024 13:15:23", strval(new MutableDateTime("20240405T091523Z")));
self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 9:15:23")));
self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 9.15.23")));
self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9:15")));
self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9.15")));
self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9h15")));
self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 09:15:23")));
self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 09:15")));
self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 09h15")));
}
function testCompare() {
$a = new MutableDateTime("10/02/2024");
$a2 = new MutableDateTime("10/02/2024 8:30");
$a3 = new MutableDateTime("10/02/2024 15:45");
$b = new MutableDateTime("15/02/2024");
$b2 = new MutableDateTime("15/02/2024 8:30");
$b3 = new MutableDateTime("15/02/2024 15:45");
$x = new MutableDateTime("10/02/2024");
$x2 = new MutableDateTime("10/02/2024 8:30");
$x3 = new MutableDateTime("10/02/2024 15:45");
self::assertTrue($a == $x);
self::assertFalse($a === $x);
self::assertTrue($a2 == $x2);
self::assertTrue($a3 == $x3);
self::assertFalse($a < $a);
self::assertTrue($a < $a2);
self::assertTrue($a < $a3);
self::assertTrue($a < $b);
self::assertTrue($a < $b2);
self::assertTrue($a < $b3);
self::assertTrue($a <= $a);
self::assertTrue($a <= $a2);
self::assertTrue($a <= $a3);
self::assertTrue($a <= $b);
self::assertTrue($a <= $b2);
self::assertTrue($a <= $b3);
self::assertTrue($b > $a);
self::assertTrue($b > $a2);
self::assertTrue($b > $a3);
self::assertFalse($b > $b);
self::assertFalse($b > $b2);
self::assertFalse($b > $b3);
self::assertTrue($b >= $a);
self::assertTrue($b >= $a2);
self::assertTrue($b >= $a3);
self::assertTrue($b >= $b);
self::assertFalse($b >= $b2);
self::assertFalse($b >= $b3);
}
}