diff --git a/php/src/php/time/Date.php b/php/src/php/time/Date.php index c681e68..69a14c8 100644 --- a/php/src/php/time/Date.php +++ b/php/src/php/time/Date.php @@ -1,7 +1,7 @@ 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 { diff --git a/php/src/php/time/DateTime.php b/php/src/php/time/DateTime.php index 438b614..264e945 100644 --- a/php/src/php/time/DateTime.php +++ b/php/src/php/time/DateTime.php @@ -1,10 +1,10 @@ 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"))); } } diff --git a/php/src/php/time/Delay.php b/php/src/php/time/Delay.php index c8f52f7..1d11b72 100644 --- a/php/src/php/time/Delay.php +++ b/php/src/php/time/Delay.php @@ -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); } diff --git a/php/src/php/time/Elapsed.php b/php/src/php/time/Elapsed.php index 37f22c6..8bd2f8d 100644 --- a/php/src/php/time/Elapsed.php +++ b/php/src/php/time/Elapsed.php @@ -1,6 +1,8 @@ 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(); diff --git a/php/src/php/time/MutableDate.php b/php/src/php/time/MutableDate.php new file mode 100644 index 0000000..1f01428 --- /dev/null +++ b/php/src/php/time/MutableDate.php @@ -0,0 +1,24 @@ +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); + } +} diff --git a/php/src/php/time/MutableDateTime.php b/php/src/php/time/MutableDateTime.php new file mode 100644 index 0000000..c7e69a7 --- /dev/null +++ b/php/src/php/time/MutableDateTime.php @@ -0,0 +1,168 @@ +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; + } +} diff --git a/php/src/php/time/_TDateTime.php b/php/src/php/time/_TDateTime.php new file mode 100644 index 0000000..755ec31 --- /dev/null +++ b/php/src/php/time/_TDateTime.php @@ -0,0 +1,103 @@ +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); + } +} diff --git a/php/src/php/time/_utils.php b/php/src/php/time/_utils.php new file mode 100644 index 0000000..aeec99e --- /dev/null +++ b/php/src/php/time/_utils.php @@ -0,0 +1,147 @@ +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]; + } +} diff --git a/php/tests/php/time/DateTest.php b/php/tests/php/time/DateTest.php index 458e42b..4656fc5 100644 --- a/php/tests/php/time/DateTest.php +++ b/php/tests/php/time/DateTest.php @@ -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"))); diff --git a/php/tests/php/time/DateTimeTest.php b/php/tests/php/time/DateTimeTest.php index 67cc9de..0880c67 100644 --- a/php/tests/php/time/DateTimeTest.php +++ b/php/tests/php/time/DateTimeTest.php @@ -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"))); diff --git a/php/tests/php/time/DelayTest.php b/php/tests/php/time/DelayTest.php index 132bc4d..b86f6af 100644 --- a/php/tests/php/time/DelayTest.php +++ b/php/tests/php/time/DelayTest.php @@ -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() { diff --git a/php/tests/php/time/MutableDateTest.php b/php/tests/php/time/MutableDateTest.php new file mode 100644 index 0000000..26f425d --- /dev/null +++ b/php/tests/php/time/MutableDateTest.php @@ -0,0 +1,87 @@ +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); + } +} diff --git a/php/tests/php/time/MutableDateTimeTest.php b/php/tests/php/time/MutableDateTimeTest.php new file mode 100644 index 0000000..a83f129 --- /dev/null +++ b/php/tests/php/time/MutableDateTimeTest.php @@ -0,0 +1,121 @@ +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); + } +}