Removed dodgy DateTimeInterface wrappers, use Carbon or the XDateTime utils instead.

This commit is contained in:
flash 2024-07-31 20:53:06 +00:00
parent 81d0baedce
commit d3c7b8f496
19 changed files with 255 additions and 1849 deletions

View file

@ -13,8 +13,6 @@
- Add RSS/Atom feed construction utilities.
- Verify structure of the DateTime types.
## Low Prio
- Get guides working on phpdoc.

View file

@ -1 +1 @@
0.2407.311946
0.2407.312052

View file

@ -1,14 +1,15 @@
<?php
// DbMigrationManager.php
// Created: 2023-01-07
// Updated: 2023-01-07
// Updated: 2024-07-31
namespace Index\Data\Migration;
use stdClass;
use InvalidArgumentException;
use DateTimeImmutable;
use DateTimeInterface;
use Index\DateTime;
use Index\XDateTime;
use Index\Data\IDbConnection;
use Index\Data\IDbStatement;
use Index\Data\DbType;
@ -76,7 +77,8 @@ EOF;
}
public function completeMigration(string $name, ?DateTimeInterface $dateTime = null): void {
$dateTime = ($dateTime ?? DateTime::utcNow())->format(DateTimeInterface::ATOM);
$dateTime = XDateTime::toISO8601String($dateTime);
$this->insertStmt->reset();
$this->insertStmt->addParameter(1, $name, DbType::STRING);
$this->insertStmt->addParameter(2, $dateTime, DbType::STRING);
@ -88,8 +90,6 @@ EOF;
}
public function createFileName(string $name, ?DateTimeInterface $dateTime = null): string {
$dateTime ??= DateTime::utcNow();
if(empty($name))
throw new InvalidArgumentException('$name may not be empty.');
@ -97,12 +97,12 @@ EOF;
if(!preg_match('#^([a-z_]+)$#', $name))
throw new InvalidArgumentException('$name may only contain alphabetical, spaces and _ characters.');
$dateTime ??= new DateTimeImmutable('now');
return $dateTime->format('Y_m_d_His_') . trim($name, '_');
}
public function createClassName(string $name, ?DateTimeInterface $dateTime = null): string {
$dateTime ??= DateTime::utcNow();
if(empty($name))
throw new InvalidArgumentException('$name may not be empty.');
@ -110,6 +110,8 @@ EOF;
if(!preg_match('#^([a-z_]+)$#', $name))
throw new InvalidArgumentException('$name may only contain alphabetical, spaces and _ characters.');
$dateTime ??= new DateTimeImmutable('now');
$parts = explode('_', trim($name, '_'));
$name = '';
@ -120,7 +122,7 @@ EOF;
}
public function createNames(string $baseName, ?DateTimeInterface $dateTime = null): object {
$dateTime ??= DateTime::utcNow();
$dateTime ??= new DateTimeImmutable('now');
$names = new stdClass;
$names->name = $this->createFileName($baseName, $dateTime);

View file

@ -1,11 +1,10 @@
<?php
// FsDbMigrationInfo.php
// Created: 2023-01-07
// Updated: 2023-01-07
// Updated: 2024-07-31
namespace Index\Data\Migration;
use DateTime;
use Index\Data\IDbConnection;
class FsDbMigrationInfo implements IDbMigrationInfo {

View file

@ -1,659 +0,0 @@
<?php
// DateTime.php
// Created: 2021-06-11
// Updated: 2024-01-04
namespace Index;
use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
use JsonSerializable;
use RuntimeException;
use Stringable;
/**
* Represents a date and time
*/
class DateTime extends DateTimeImmutable implements JsonSerializable, Stringable, IComparable, IEquatable {
/**
* Represents Sunday for getDayOfWeek.
*
* @var int
*/
public const SUNDAY = 0;
/**
* Represents Monday for getDayOfWeek.
*
* @var int
*/
public const MONDAY = 1;
/**
* Represents Tuesday for getDayOfWeek.
*
* @var int
*/
public const TUESDAY = 2;
/**
* Represents Wednesday for getDayOfWeek.
*
* @var int
*/
public const WEDNESDAY = 3;
/**
* Represents Thursday for getDayOfWeek.
*
* @var int
*/
public const THURSDAY = 4;
/**
* Represents Friday for getDayOfWeek.
*
* @var int
*/
public const FRIDAY = 5;
/**
* Represents Saturday for getDayOfWeek.
*
* @var int
*/
public const SATURDAY = 6;
private const COMPARE = 'YmdHisu';
/**
* Creates a new DateTime object.
*
* @see https://www.php.net/manual/en/datetime.formats.php
* @param string $dateTime A date/time string.
* @param DateTimeZone|null $timeZone An object representing the desired time zone.
* @return DateTime A new DateTime instance.
*/
public function __construct(string $dateTime = 'now', DateTimeZone|null $timeZone = null) {
parent::__construct($dateTime, $timeZone);
}
/**
* Gets the date component of this instance.
*
* @return DateTime New instance with the same date but the time set to 00:00:00.
*/
public function getDate(): DateTime {
return self::create($this->getYear(), $this->getMonth(), $this->getDay(), 0, 0, 0, 0, $this->getTimezone());
}
/**
* Gets the year component of this instance.
*
* @return int Year component.
*/
public function getYear(): int {
return (int)$this->format('Y');
}
/**
* Gets the month component of this instance.
*
* @return int Month component, ranging from 1 to 12.
*/
public function getMonth(): int {
return (int)$this->format('n');
}
/**
* Gets the day component of this instance.
*
* @return int Day component, ranging from 1 to 31.
*/
public function getDay(): int {
return (int)$this->format('j');
}
/**
* Gets the day of the week represented by this instance.
*
* See the SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY and SATURDAY constants for the possible return values of this function.
*
* @return int Integer between 0 and 6 representing the day of the week.
*/
public function getDayOfWeek(): int {
return (int)$this->format('w');
}
/**
* Gets the day of the year represented by this instance.
*
* @return int Day of the year, ranging from 1-366.
*/
public function getDayOfYear(): int {
return ((int)$this->format('z')) + 1;
}
/**
* Gets the ISO8601 week number of this instance.
*
* @return int Integer representing the current week number.
*/
public function getWeek(): int {
return (int)$this->format('W');
}
/**
* Gets the time of day for this instance.
*
* @return TimeSpan A TimeSpan representing the amount of time elapsed since midnight.
*/
public function getTimeOfDay(): TimeSpan {
return TimeSpan::create(0, $this->getHour(), $this->getMinute(), $this->getSecond(), $this->getMicrosecond());
}
/**
* Gets the hour component of this instance.
*
* @return int Hour component, ranging from 0 to 23.
*/
public function getHour(): int {
return (int)$this->format('G');
}
/**
* Gets the minute component of this instance.
*
* @return int Minute component, ranging from 0 to 59.
*/
public function getMinute(): int {
return (int)$this->format('i');
}
/**
* Gets the second component of this instance.
*
* @return int Second component, ranging from 0 to 59.
*/
public function getSecond(): int {
return (int)$this->format('s');
}
/**
* Gets the millisecond component of this instance.
*
* @return int Millisecond component, ranging from 0 to 999.
*/
public function getMillisecond(): int {
return (int)$this->format('v');
}
/**
* Gets the microsecond component of this instance.
*
* @return int Microsecond component, ranging from 0 to 999999.
*/
public function getMicrosecond(): int {
return (int)$this->format('u');
}
/**
* Gets the number of seconds that have elapsed since midnight January 1st, 1970 UTC for this instance.
*
* @return int Number of seconds.
*/
public function getUnixTimeSeconds(): int {
return (int)$this->format('U');
}
/**
* Gets the number of milliseconds that have elapsed since midnight January 1st, 1970 UTC for this instance.
*
* @return float Number of milliseconds.
*/
public function getUnixTimeMilliseconds(): float {
return (float)$this->format('Uv');
}
/**
* Gets whether Daylight Savings is in effect during the time this instance represents.
*
* @return bool true if DST is in effect, false if not.
*/
public function isDaylightSavingTime(): bool {
return $this->format('I') !== '0';
}
/**
* Gets whether the year this instance represents is a leap year.
*
* @return bool true if the year is a leap year, false if not.
*/
public function isLeapYear(): bool {
return $this->format('L') !== '0';
}
/**
* Gets whether this DateTime uses UTC date/time.
*
* @return bool true if UTC, false if not.
*/
public function isUTC(): bool {
return $this->getTimezone()->equals(TimeZoneInfo::utc());
}
/**
* Gets the time zone for this instance.
*
* @return TimeZoneInfo Time zone for this instance.
*/
public function getTimezone(): TimeZoneInfo {
return TimeZoneInfo::cast(parent::getTimezone());
}
/**
* Adds a period of time to this date time object.
*
* @param DateInterval $timeSpan Time period to add to this DateTime.
* @return DateTime A DateTime instance which is the sum of this instance and the provided period.
*/
public function add(DateInterval $timeSpan): DateTime {
return self::cast(parent::add($timeSpan));
}
/**
* Adds a number of years to this instance.
*
* @param float $years Number of years to add.
* @return DateTime New DateTime instance with the years added.
*/
public function addYears(float $years): DateTime {
return $this->add(TimeSpan::fromDays(365 * $years));
}
/**
* Adds a number of months to this instance.
*
* @param float $months Number of months to add.
* @return DateTime New DateTime instance with the months added.
*/
public function addMonths(float $months): DateTime {
return $this->add(TimeSpan::fromDays(365 * 31 * $months));
}
/**
* Adds a number of days to this instance.
*
* @param float $days Number of days to add.
* @return DateTime New DateTime instance with the days added.
*/
public function addDays(float $days): DateTime {
return $this->add(TimeSpan::fromDays($days));
}
/**
* Adds a number of hours to this instance.
*
* @param float $hours Number of hours to add.
* @return DateTime New DateTime instance with the hours added.
*/
public function addHours(float $hours): DateTime {
return $this->add(TimeSpan::fromHours($hours));
}
/**
* Adds a number of minutes to this instance.
*
* @param float $minutes Number of minutes to add.
* @return DateTime New DateTime instance with the minutes added.
*/
public function addMinutes(float $minutes): DateTime {
return $this->add(TimeSpan::fromMinutes($minutes));
}
/**
* Adds a number of seconds to this instance.
*
* @param float $seconds Number of seconds to add.
* @return DateTime New DateTime instance with the seconds added.
*/
public function addSeconds(float $seconds): DateTime {
return $this->add(TimeSpan::fromSeconds($seconds));
}
/**
* Adds a number of milliseconds to this instance.
*
* @param float $millis Number of milliseconds to add.
* @return DateTime New DateTime instance with the milliseconds added.
*/
public function addMilliseconds(float $millis): DateTime {
return $this->add(TimeSpan::fromMilliseconds($millis));
}
/**
* Adds a number of microseconds to this instance.
*
* @param float $micros Number of microseconds to add.
* @return DateTime New DateTime instance with the microseconds added.
*/
public function addMicroseconds(float $micros): DateTime {
return $this->add(TimeSpan::fromMicroseconds($micros));
}
/**
* Subtracts a time period from this DateTime.
*
* @param DateInterval $timeSpan Time period to be subtracted.
* @return DateTime A new DateTime instance which is this instance minus the given time period.
*/
public function subtract(DateInterval $timeSpan): DateTime {
return self::cast(parent::sub($timeSpan));
}
/**
* Alias for difference, must exist because of the DateTimeImmutable inheritance but I don't like short names.
*
* @internal
*/
public function diff(DateTimeInterface $dateTime, bool $absolute = false): TimeSpan {
return $this->difference($dateTime, $absolute);
}
/**
* Subtracts another Date/Time from this DateTime.
*
* @param DateTimeInterface $dateTime Date/time to subtract.
* @return TimeSpan Difference between this DateTime and the provided one.
*/
public function difference(DateTimeInterface $dateTime, bool $absolute = false): TimeSpan {
return TimeSpan::cast(parent::diff($dateTime, $absolute));
}
/**
* Applies a modifier on this instance and returns the result as a new instance.
*
* @see https://www.php.net/manual/en/datetime.formats.php
* @param string $modifier A date/time format.
* @return DateTime The modified date/time.
*/
public function modify(string $modifier): DateTime {
return self::cast(parent::modify($modifier));
}
/**
* Creates a new DateTime with the given date component.
*
* @param int $year Desired year component.
* @param int $month Desired month component.
* @param int $day Desired day component.
* @return DateTime A new DateTime instance with the given date component.
*/
public function setDate(int $year, int $month, int $day): DateTime {
return self::cast(parent::setDate($year, $month, $day));
}
/**
* Creates a new DateTime with the given ISO date for the date component.
*
* @param int $year Year of the date.
* @param int $week Week of the date.
* @param int $dayOfWeek Offset from the first of the week.
* @return DateTime A new DateTime instance with the given date component.
*/
public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime {
return self::cast(parent::setISODate($year, $week, $dayOfWeek));
}
/**
* Creates a new DateTime with the given time component.
*
* @param int $hour Desired hour component.
* @param int $minute Desired minute component.
* @param int $second Desired second component.
* @param int $microsecond Desired microsecond component.
* @return DateTime A new DateTime instance with the given time component.
*/
public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTime {
return self::cast(parent::setTime($hour, $minute, $second, $microsecond));
}
/**
* Creates a new DateTime with the date and time components based on a Unix timestamp.
*
* @param int $timestamp Unix timestamp representing the date.
* @return DateTime A new DateTime instance with the given date and time.
*/
public function setTimestamp(int $timestamp): DateTime {
return self::cast(parent::setTimestamp($timestamp));
}
/**
* Creates a new DateTime with the given time zone.
*
* @param DateTimeZone $timeZone An object representing the desired time zone.
* @return DateTime A new DateTime with the given time zone.
*/
public function setTimezone(DateTimeZone $timeZone): DateTime {
return self::cast(parent::setTimezone($timeZone));
}
public function compare(mixed $other): int {
if($other instanceof DateTimeInterface)
return strcmp($this->format(self::COMPARE), $other->format(self::COMPARE));
return -1;
}
public function equals(mixed $other): bool {
if($other instanceof DateTimeInterface)
return $this->format(self::COMPARE) === $other->format(self::COMPARE);
return false;
}
public function isLessThan(DateTimeInterface $other): bool {
return $this->compare($other) < 0;
}
public function isLessThanOrEqual(DateTimeInterface $other): bool {
return $this->compare($other) <= 0;
}
public function isMoreThan(DateTimeInterface $other): bool {
return $this->compare($other) > 0;
}
public function isMoreThanOrEqual(DateTimeInterface $other): bool {
return $this->compare($other) >= 0;
}
/**
* Formats this DateTime as an ISO8601 date/time string.
*
* @return string This object represented as an ISO8601 date/time string.
*/
public function toISO8601String(): string {
return $this->format(DateTimeInterface::ATOM);
}
/**
* Formats this DateTime as a Cookie date/time string.
*
* @return string This object represented as a Cookie date/time string.
*/
public function toCookieString(): string {
return $this->format(DateTimeInterface::COOKIE);
}
/**
* Formats this DateTime as an RFC 822 date/time string.
*
* @return string This object represented as an RFC 822 date/time string.
*/
public function toRFC822String(): string {
return $this->format(DateTimeInterface::RFC822);
}
/**
* Creates a string representation of this object.
*
* @return string Representation of this object.
*/
public function __toString(): string {
return $this->format(DateTimeInterface::ATOM);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return (string)$this;
}
/**
* Converts this immutable \Index\DateTime type to a mutable \DateTime type.
*
* @return \DateTime A new mutable \DateTime instance.
*/
public function toNative(): \DateTime {
return \DateTime::createFromImmutable($this);
}
/**
* Creates a DateTime object that is set to the current date and time, expressed in the provided time zone.
*
* @param DateTimeZone $timeZone Desired time zone, null for the current default time zone.
* @return DateTime An instance representing now.
*/
public static function now(?DateTimeZone $timeZone = null): DateTime {
return new DateTime('now', $timeZone);
}
/**
* Creates a DateTime object that is set to the current date and time, expressed in UTC.
*
* @return DateTime An instance representing now in UTC.
*/
public static function utcNow(): DateTime {
return self::now(TimeZoneInfo::utc());
}
/**
* Converts Unix time seconds to a DateTime object.
*
* @param int $seconds Unix time seconds.
* @return DateTime A DateTime instance representing the Unix time.
*/
public static function fromUnixTimeSeconds(int $seconds): DateTime {
return new DateTime('@' . $seconds);
}
/**
* Converts Unix time milliseconds to a DateTime object.
*
* @param float $millis Unix time milliseconds.
* @return DateTime A DateTime instance representing the Unix time.
*/
public static function fromUnixTimeMilliseconds(float $millis): DateTime {
return new DateTime('@' . ($millis / 1000));
}
/**
* Creates a new DateTime instance with the given components.
*
* @param int $year Desired year component.
* @param int $month Desired month component.
* @param int $day Desired day component.
* @param int $hour Desired hour component.
* @param int $minute Desired minute component.
* @param int $second Desired second component.
* @param int $micros Desired microsecond component.
* @param ?DateTimeZone $timeZone Desired time zone.
*/
public static function create(int $year, int $month = 1, int $day = 1, int $hour = 0, int $minute = 0, int $second = 0, int $micros = 0, ?DateTimeZone $timeZone = null): DateTime {
if($year < 1 || $year > 9999)
throw new InvalidArgumentException('$year may not be less than 1 or more than 9999.');
if($month < 1 || $month > 12)
throw new InvalidArgumentException('$month may not be less than 1 or more than 12.');
if($day < 1 || $day > 31)
throw new InvalidArgumentException('$day may not be less than 1 or more than 31.');
if($hour < 0 || $hour > 23)
throw new InvalidArgumentException('$hour may not be less than 0 or more than 23.');
if($minute < 0 || $minute > 59)
throw new InvalidArgumentException('$minute may not be less than 0 or more than 59.');
if($second < 0 || $second > 59)
throw new InvalidArgumentException('$second may not be less than 0 or more than 59.');
if($micros < 0 || $micros > 999999)
throw new InvalidArgumentException('$micros may not be less than 0 or more than 999999.');
return new DateTime(
sprintf(
'%04d-%02d-%02dT%02d:%02d:%02d.%06d',
$year, $month, $day, $hour, $minute, $second, $micros
),
$timeZone
);
}
/**
* Parses a time string and creates a DateTime using it.
*
* @see https://www.php.net/manual/en/datetime.createfromformat
* @param string $format Format that the passed string should be in.
* @param string $dateTime String representing the time.
* @param ?DateTimeZone $timeZone Desired time zone.
* @return DateTime|false A DateTime instance, or false on failure.
*/
public static function createFromFormat(string $format, string $dateTime, ?DateTimeZone $timeZone = null): DateTime|false {
$instance = parent::createFromFormat($format, $dateTime, $timeZone);
if($instance === false)
return false;
return self::cast($instance);
}
/**
* Creates a new DateTime instance from an interface implementation.
*
* @param DateTimeInterface $object Object that should be sourced.
* @return DateTime A new DateTime instance.
*/
public static function createFromInterface(DateTimeInterface $object): DateTime {
return self::cast($object);
}
/**
* Creates a new immutable DateTime instance from a mutable instance.
*
* @param \DateTime $object Mutable object that should be sourced.
* @return DateTime New immutable DateTime representing the same value.
*/
#[\ReturnTypeWillChange]
public static function createFromMutable(\DateTime $object): DateTime {
return self::cast($object);
}
/**
* @internal
*/
public static function __set_state(array $array): DateTime {
return self::cast(parent::__set_state($array));
}
/**
* Makes sure a DateTimeInterface implementing object is a DateTime instance.
*
* @param DateTimeInterface $dateTime Input object.
* @return DateTime If the input was a DateTime, the same instance will be returned.
* If the input was something else, a new DateTime instance will be returned based on the input.
*/
public static function cast(DateTimeInterface $dateTime): DateTime {
if($dateTime instanceof DateTime)
return $dateTime;
return new DateTime($dateTime->format('@U.u'), $dateTime->getTimezone());
}
}

View file

@ -1,25 +1,31 @@
<?php
// DateHeader.php
// Created: 2022-02-14
// Updated: 2022-02-14
// Updated: 2024-07-31
namespace Index\Http\Headers;
use Index\DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use RuntimeException;
use Index\Http\HttpHeader;
class DateHeader {
private DateTime $dateTime;
private DateTimeImmutable $dateTime;
public function __construct(DateTime $dateTime) {
public function __construct(DateTimeImmutable $dateTime) {
$this->dateTime = $dateTime;
}
public function getDateTime(): DateTime {
public function getDateTime(): DateTimeImmutable {
return $this->dateTime;
}
public static function parse(HttpHeader $header): DateHeader {
return new DateHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
$parsed = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC7231, $header->getFirstLine());
if($parsed === false)
throw new RuntimeException('Failed to parse Date header.');
return new DateHeader($parsed);
}
}

View file

@ -1,30 +1,36 @@
<?php
// IfModifiedSinceHeader.php
// Created: 2022-02-14
// Updated: 2022-02-14
// Updated: 2024-07-31
namespace Index\Http\Headers;
use DateTimeImmutable;
use DateTimeInterface;
use Index\DateTime;
use RuntimeException;
use Index\XDateTime;
use Index\Http\HttpHeader;
class IfModifiedSinceHeader {
private DateTime $dateTime;
private DateTimeImmutable $dateTime;
public function __construct(DateTime $dateTime) {
public function __construct(DateTimeImmutable $dateTime) {
$this->dateTime = $dateTime;
}
public function getDateTime(): DateTime {
public function getDateTime(): DateTimeImmutable {
return $this->dateTime;
}
public function isLessThanOrEqual(DateTimeInterface $dateTime): bool {
return $this->dateTime->isLessThanOrEqual($dateTime);
public function isLessThanOrEqual(DateTimeInterface $other): bool {
return XDateTime::compare($this->dateTime, $other) <= 0;
}
public static function parse(HttpHeader $header): IfModifiedSinceHeader {
return new IfModifiedSinceHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
$parsed = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC7231, $header->getFirstLine());
if($parsed === false)
throw new RuntimeException('Failed to parse If-Modified-Since header.');
return new IfModifiedSinceHeader($parsed);
}
}

View file

@ -1,19 +1,21 @@
<?php
// IfRangeHeader.php
// Created: 2022-02-14
// Updated: 2022-02-27
// Updated: 2024-07-31
namespace Index\Http\Headers;
use DateTimeImmutable;
use DateTimeInterface;
use Index\DateTime;
use RuntimeException;
use Index\XDateTime;
use Index\Http\HttpHeader;
class IfRangeHeader {
private ?string $tag;
private ?DateTime $dateTime;
private ?DateTimeImmutable $dateTime;
public function __construct(?string $tag, ?DateTime $dateTime) {
public function __construct(?string $tag, ?DateTimeImmutable $dateTime) {
$this->tag = $tag;
$this->dateTime = $dateTime;
}
@ -30,7 +32,7 @@ class IfRangeHeader {
return $this->dateTime !== null;
}
public function getDateTime(): DateTime {
public function getDateTime(): DateTimeImmutable {
return $this->dateTime;
}
@ -42,7 +44,7 @@ class IfRangeHeader {
}
if($this->hasDateTime() && $other instanceof DateTimeInterface)
return $this->dateTime->isLessThanOrEqual($other);
return XDateTime::compare($this->dateTime, $other) <= 0;
return false;
}
@ -50,8 +52,13 @@ class IfRangeHeader {
public static function parse(HttpHeader $header): IfRangeHeader {
$line = $header->getFirstLine();
if($line[0] !== '"' && !str_starts_with($line, 'W/"'))
return new IfRangeHeader(null, DateTime::createFromFormat(\DateTimeInterface::RFC7231, $line));
if($line[0] !== '"' && !str_starts_with($line, 'W/"')) {
$parsed = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC7231, $line);
if($parsed === false)
throw new RuntimeException('Failed to parse If-Range header.');
return new IfRangeHeader(null, $parsed);
}
return new IfRangeHeader($line, null);
}

View file

@ -1,30 +1,36 @@
<?php
// IfUnmodifiedSinceHeader.php
// Created: 2022-02-14
// Updated: 2022-02-14
// Updated: 2024-07-31
namespace Index\Http\Headers;
use DateTimeImmutable;
use DateTimeInterface;
use Index\DateTime;
use RuntimeException;
use Index\XDateTime;
use Index\Http\HttpHeader;
class IfUnmodifiedSinceHeader {
private DateTime $dateTime;
private DateTimeImmutable $dateTime;
public function __construct(DateTime $dateTime) {
public function __construct(DateTimeImmutable $dateTime) {
$this->dateTime = $dateTime;
}
public function getDateTime(): DateTime {
public function getDateTime(): DateTimeImmutable {
return $this->dateTime;
}
public function isMoreThan(DateTimeInterface $dateTime): bool {
return $this->dateTime->isMoreThan($dateTime);
public function isMoreThan(DateTimeInterface $other): bool {
return XDateTime::compare($this->dateTime, $other) > 0;
}
public static function parse(HttpHeader $header): IfUnmodifiedSinceHeader {
return new IfUnmodifiedSinceHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
$parsed = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC7231, $header->getFirstLine());
if($parsed === false)
throw new RuntimeException('Failed to parse If-Unmodified-Since header.');
return new IfUnmodifiedSinceHeader($parsed);
}
}

View file

@ -1,25 +1,31 @@
<?php
// LastModifiedHeader.php
// Created: 2022-02-14
// Updated: 2022-02-14
// Updated: 2024-07-31
namespace Index\Http\Headers;
use Index\DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use RuntimeException;
use Index\Http\HttpHeader;
class LastModifiedHeader {
private DateTime $dateTime;
private DateTimeImmutable $dateTime;
public function __construct(DateTime $dateTime) {
public function __construct(DateTimeImmutable $dateTime) {
$this->dateTime = $dateTime;
}
public function getDateTime(): DateTime {
public function getDateTime(): DateTimeImmutable {
return $this->dateTime;
}
public static function parse(HttpHeader $header): LastModifiedHeader {
return new LastModifiedHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
$parsed = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC7231, $header->getFirstLine());
if($parsed === false)
throw new RuntimeException('Failed to parse Last-Modified header.');
return new LastModifiedHeader($parsed);
}
}

View file

@ -1,11 +1,10 @@
<?php
// OriginHeader.php
// Created: 2022-02-14
// Updated: 2022-02-27
// Updated: 2024-07-31
namespace Index\Http\Headers;
use Index\DateTime;
use Index\Http\HttpHeader;
class OriginHeader {

View file

@ -1,14 +1,14 @@
<?php
// HttpResponseBuilder.php
// Created: 2022-02-08
// Updated: 2023-01-10
// Updated: 2024-07-31
namespace Index\Http;
use DateTimeInterface;
use Index\DateTime;
use Index\UrlEncoding;
use Index\MediaType;
use Index\XDateTime;
use Index\Performance\Timings;
class HttpResponseBuilder extends HttpMessageBuilder {
@ -53,16 +53,8 @@ class HttpResponseBuilder extends HttpMessageBuilder {
$cookie = rawurlencode($name) . '=' . rawurlencode($value)
. '; SameSite=' . ($sameSiteStrict ? 'Strict' : 'Lax');
if($expires !== null) {
if(!($expires instanceof DateTime)) {
if(is_int($expires))
$expires = DateTime::fromUnixTimeSeconds($expires);
else
$expires = DateTime::createFromInterface($expires);
}
$cookie .= '; Expires=' . $expires->toCookieString();
}
if($expires !== null)
$cookie .= '; Expires=' . XDateTime::toCookieString($expires);
if(!empty($domain))
$cookie .= '; Domain=' . $domain;

View file

@ -1,526 +0,0 @@
<?php
// TimeSpan.php
// Created: 2021-06-11
// Updated: 2022-02-27
namespace Index;
use DateInterval;
use JsonSerializable;
use Stringable;
/**
* Represents a duration of time.
*/
class TimeSpan extends DateInterval implements JsonSerializable, Stringable, IComparable, IEquatable {
private const HOURS_IN_DAY = 24;
private const MINS_IN_HOUR = 60;
private const SECS_IN_MIN = 60;
private const MILLIS_IN_SEC = 1000;
private const MINS_IN_DAY = 1440;
private const SECS_IN_HOUR = 3600;
private const MILLIS_IN_MIN = 60000;
private const SECS_IN_DAY = 86400;
private const MICROS_IN_SEC = 1000000;
private const MILLIS_IN_HOUR = 3600000;
private const MICROS_IN_MIN = 60000000;
private const MILLIS_IN_DAY = 86400000;
private const MICROS_IN_HOUR = 3600000000;
private const MICROS_IN_DAY = 86400000000;
private int $realDays = -1;
/**
* Creates a new instance of TimeSpan.
*
* @param string $duration An ISO8601 duration string.
* @return TimeSpan A TimeSpan instance representing the given duration.
*/
public function __construct(string $duration = 'PT0S') {
parent::__construct($duration);
$this->realDays = $this->days === false ? $this->calculateRealDays() : $this->days;
}
private function calculateRealDays(): int {
return ($this->y * 365)
+ ($this->m * 31)
+ $this->d;
}
/**
* Gets the amount of days this TimeSpan represents.
*
* @return int Amount of days.
*/
public function getDays(): int {
return $this->realDays;
}
/**
* Gets the amount of hours this TimeSpan represents.
*
* @retunr int Amount of hours.
*/
public function getHours(): int {
return $this->h;
}
/**
* Gets the amount of minutes this TimeSpan represents.
*
* @return int Amount of minutes.
*/
public function getMinutes(): int {
return $this->i;
}
/**
* Gets the amount of seconds this TimeSpan represents.
*
* @return int Amount of seconds.
*/
public function getSeconds(): int {
return $this->s;
}
/**
* Gets the amount of milliseconds this TimeSpan represents.
*
* @return int Amount of milliseconds.
*/
public function getMilliseconds(): int {
return (int)($this->f * 1000);
}
/**
* Gets the amount of microseconds this TimeSpan represents.
*
* @return int Amount of microseconds.
*/
public function getMicroseconds(): int {
return (int)($this->f * 1000000);
}
/**
* Gets whether this TimeSpan represents a negative amount of time.
*
* @return bool true if negative, false if positive.
*/
public function isNegative(): bool {
return $this->invert !== 0;
}
/**
* Counts the total amount of days this TimeSpan contains.
*
* @return float Total amount of days.
*/
public function totalDays(): float {
$days = $this->getDays();
$days += $this->getHours() / self::HOURS_IN_DAY;
$days += $this->getMinutes() / self::MINS_IN_DAY;
$days += $this->getSeconds() / self::SECS_IN_DAY;
$days += $this->getMicroseconds() / self::MICROS_IN_DAY;
return $days;
}
/**
* Counts the total amount of hours this TimeSpan contains.
*
* @return float Total amount of hours.
*/
public function totalHours(): float {
$hours = $this->getDays() * self::HOURS_IN_DAY;
$hours += $this->getHours();
$hours += $this->getMinutes() / self::MINS_IN_HOUR;
$hours += $this->getSeconds() / self::SECS_IN_HOUR;
$hours += $this->getMicroseconds() / self::MICROS_IN_HOUR;
return $hours;
}
/**
* Counts the total amount of minutes this TimeSpan contains.
*
* @return float Total amount of minutes.
*/
public function totalMinutes(): float {
$mins = $this->getDays() * self::MINS_IN_DAY;
$mins += $this->getHours() * self::MINS_IN_HOUR;
$mins += $this->getMinutes();
$mins += $this->getSeconds() / self::SECS_IN_MIN;
$mins += $this->getMicroseconds() / self::MICROS_IN_MIN;
return $mins;
}
/**
* Counts the total amount of seconds this TimeSpan contains.
*
* @return float Total amount of seconds.
*/
public function totalSeconds(): float {
$secs = $this->getDays() * self::SECS_IN_DAY;
$secs += $this->getHours() * self::SECS_IN_HOUR;
$secs += $this->getMinutes() * self::SECS_IN_MIN;
$secs += $this->getSeconds();
$secs += $this->getMicroseconds() * self::MICROS_IN_SEC;
return $secs;
}
/**
* Counts the total amount of milliseconds this TimeSpan contains.
*
* @return float Total amount of milliseconds.
*/
public function totalMilliseconds(): float {
$millis = $this->getDays() * self::MILLIS_IN_DAY;
$millis += $this->getHours() * self::MILLIS_IN_HOUR;
$millis += $this->getMinutes() * self::MILLIS_IN_MIN;
$millis += $this->getSeconds() * self::MILLIS_IN_SEC;
$millis += $this->getMilliseconds();
return $millis;
}
/**
* Counts the total amount of microseconds this TimeSpan contains.
*
* @return float Total amount of microseconds.
*/
public function totalMicroseconds(): float {
$micros = $this->getDays() * self::MICROS_IN_DAY;
$micros += $this->getHours() * self::MICROS_IN_HOUR;
$micros += $this->getMinutes() * self::MICROS_IN_MIN;
$micros += $this->getSeconds() * self::MICROS_IN_SEC;
$micros += $this->getMicroseconds();
return $micros;
}
/**
* Returns a new TimeSpan whose value is the sum of this instance and the provided instance.
*
* @param DateInterval $timeSpan Interval to add.
* @return TimeSpan Instance that represents this instance plus the value of $timeSpan.
*/
public function add(DateInterval $timeSpan): TimeSpan {
$timeSpan = self::cast($timeSpan);
$days1 = $this->totalDays();
if($this->isNegative())
$days1 *= -1;
$days2 = $timeSpan->totalDays();
if($timeSpan->isNegative())
$days2 *= -1;
return self::fromDays($days1 + $days2);
}
/**
* Returns a new TimeSpan whose value is the difference of this instance and the provided instance.
*
* @param DateInterval $timeSpan Interval to subtract.
* @return TimeSpan Instance that represents this instance minus the value of $timeSpan.
*/
public function subtract(DateInterval $timeSpan): TimeSpan {
$timeSpan = self::cast($timeSpan);
$days1 = $this->totalDays();
if($this->isNegative())
$days1 *= -1;
$days2 = $timeSpan->totalDays();
if($timeSpan->isNegative())
$days2 *= -1;
return self::fromDays($days1 - $days2);
}
/**
* Returns the result of a division of this instance and the provided instance.
*
* @param DateInterval $timeSpan Interval to be divided by.
* @return float $timeSpan Number that represents this instance divided by the value of $timeSpan.
*/
public function divideTimeSpan(DateInterval $timeSpan): float {
$timeSpan = self::cast($timeSpan);
$days1 = $this->totalDays();
if($this->isNegative())
$days1 *= -1;
$days2 = $timeSpan->totalDays();
if($timeSpan->isNegative())
$days2 *= -1;
if($days2 === 0.0)
return 0.0;
return $days1 / $days2;
}
/**
* Returns the result of a division of this instance and the provided divisor.
*
* @param float $divisor Value to divide by.
* @return TimeSpan Instance that represents this instance divided by the value of $divisor.
*/
public function divideFloat(float $divisor): TimeSpan {
$totalDays = $this->totalDays();
if($this->isNegative())
$totalDays *= -1;
$totalDays /= $divisor;
return self::fromDays($totalDays);
}
/**
* Returns a new TimeSpan whose alue is the result of a multiplication of this oinstance and the provided factor.
*
* @param float $factor Value to multiply with.
* @return TimeSpan Instance that represents this instance multiplied by the value of $factor.
*/
public function multiply(float $factor): TimeSpan {
$totalDays = $this->totalDays();
if($this->isNegative())
$totalDays *= -1;
$totalDays *= $factor;
return self::fromDays($totalDays);
}
/**
* Return a new TimeSpan whose value is the negated value of this instance.
*
* @return TimeSpan Negated copy of this instance.
*/
public function negate(): TimeSpan {
return self::create(
$this->getDays(),
$this->getHours(),
$this->getMinutes(),
$this->getSeconds(),
$this->getMicroseconds(),
!$this->isNegative()
);
}
public function compare(mixed $other): int {
if(!($other instanceof DateInterval))
return -1;
$other = self::cast($other);
$diff = $this->getDays() <=> $other->getDays();
if($diff) return $diff;
$diff = $this->getHours() <=> $other->getHours();
if($diff) return $diff;
$diff = $this->getMinutes() <=> $other->getMinutes();
if($diff) return $diff;
$diff = $this->getSeconds() <=> $other->getSeconds();
if($diff) return $diff;
$diff = $this->getMicroseconds() <=> $other->getMicroseconds();
if($diff) return $diff;
$diff = $this->isNegative() <=> $other->isNegative();
if($diff) return $diff;
return 0;
}
public function equals(mixed $other): bool {
if(!($other instanceof DateInterval))
return false;
$other = self::cast($other);
return $this->getDays() === $other->getDays()
&& $this->getHours() === $other->getHours()
&& $this->getMinutes() === $other->getMinutes()
&& $this->getSeconds() === $other->getSeconds()
&& $this->getMicroseconds() === $other->getMicroseconds()
&& $this->isNegative() === $other->isNegative();
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return (string)$this;
}
/**
* Creates a string representation of this object.
*
* @return string Representation of this object.
*/
public function __toString(): string {
$string = $this->isNegative() ? '-' : '';
return sprintf(
'%sP%dY%dM%dDT%dH%dM%d.%dS',
$this->isNegative() ? '-' : '',
$this->y,
$this->m,
$this->d,
$this->getHours(),
$this->getMinutes(),
$this->getSeconds(),
$this->getMicroseconds()
);
}
/**
* Creates a new TimeSpan instance that represents the specified number of days.
*
* @param float $days Number of days.
* @return TimeSpan Instance representing the provided number of days.
*/
public static function fromDays(float $days): TimeSpan {
$abs = abs($days);
$round = (int)floor($abs);
$hours = ($abs - $round) * self::HOURS_IN_DAY;
$hoursRound = (int)floor($hours);
$minutes = ($hours - $hoursRound) * self::MINS_IN_HOUR;
$minsRound = (int)floor($minutes);
$seconds = ($minutes - $minsRound) * self::SECS_IN_MIN;
$secsRound = (int)floor($seconds);
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
return self::create($round, $hoursRound, $minsRound, $secsRound, $micros, $days < 0);
}
/**
* Creates a new TimeSpan instance that represents the specified number of hours.
*
* @param float $hours Number of hours.
* @return TimeSpan Instance representing the provided number of hours.
*/
public static function fromHours(float $hours): TimeSpan {
$abs = abs($hours);
$round = (int)floor($abs);
$minutes = ($abs - $round) * self::MINS_IN_HOUR;
$minsRound = (int)floor($minutes);
$seconds = ($minutes - $minsRound) * self::SECS_IN_MIN;
$secsRound = (int)floor($seconds);
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
return self::create(0, $round, $minsRound, $secsRound, $micros, $hours < 0);
}
/**
* Creates a new TimeSpan instance that represents the specified number of minutes.
*
* @param float $minutes Number of minutes.
* @return TimeSpan Instance representing the provided number of minutes.
*/
public static function fromMinutes(float $minutes): TimeSpan {
$abs = abs($minutes);
$round = (int)floor($abs);
$seconds = ($abs - $round) * self::SECS_IN_MIN;
$secsRound = (int)floor($seconds);
$micros = (int)floor(($seconds - $secsRound) * self::MICROS_IN_SEC);
return self::create(0, 0, $round, $secsRound, $micros, $minutes < 0);
}
/**
* Creates a new TimeSpan instance that represents the specified number of seconds.
*
* @param float $seconds Number of seconds.
* @return TimeSpan Instance representing the provided number of seconds.
*/
public static function fromSeconds(float $seconds): TimeSpan {
$abs = abs($seconds);
$round = (int)floor($abs);
$micros = (int)floor(($abs - $round) * self::MICROS_IN_SEC);
return self::create(0, 0, 0, $round, $micros, $seconds < 0);
}
/**
* Creates a new TimeSpan instance that represents the specified number of milliseconds.
*
* @param float $millis Number of milliseconds.
* @return TimeSpan Instance representing the provided number of milliseconds.
*/
public static function fromMilliseconds(float $millis): TimeSpan {
return self::fromMicroseconds($millis * 1000);
}
/**
* Creates a new TimeSpan instance that represents the specified number of microseconds.
*
* @param float $micros Number of microseconds.
* @return TimeSpan Instance representing the provided number of microseconds.
*/
public static function fromMicroseconds(float $micros): TimeSpan {
return self::create(0, 0, 0, 0, (int)floor(abs($micros)), $micros < 0);
}
/**
* Creates a new TimeSpan using the given parameters.
*
* @param int $days Number of days.
* @param int $hours Number of hours.
* @param int $minutes Number of minutes.
* @param int $seconds Number of seconds.
* @param int $micros Number of microseconds.
* @param bool $negative true for a negative TimeSpan, false for positive.
* @return TimeSpan A new TimeSpan from the provided parameters.
*/
public static function create(int $days, int $hours, int $minutes, int $seconds, int $micros = 0, bool $negative = false): TimeSpan {
$ts = new TimeSpan;
$ts->realDays = $days = max(0, $days);
$ts->h = max(0, min(23, $hours));
$ts->i = max(0, min(59, $minutes));
$ts->s = max(0, min(59, $seconds));
$ts->f = $micros / self::MICROS_IN_SEC;
$ts->invert = $negative ? 1 : 0;
// this may still be stupid
if($days > 0) {
$days /= 365;
$ts->y = (int)$days;
$days -= $ts->y;
$days *= 12;
$ts->m = (int)$days;
$days -= $ts->m;
$days *= 31;
$ts->d = (int)$days;
}
return $ts;
}
/**
* Creates a TimeSpan from the relative parts of a string.
*
* Uses the same parser as strtotime and the DateTime constructor internally.
*
* @param string $dateTime A date with relative parts.
* @return TimeSpan A TimeSpan representing the given
*/
public static function createFromDateString(string $dateTime): TimeSpan {
return self::cast(parent::createFromDateString($dateTime));
}
/**
* Makes sure a DateInterval inheriting object is a TimeSpan instance.
*
* @param DateInterval $dateInterval Input object.
* @return TimeSpan If the input was a TimeSpan, the same instance will be returned.
* If the input was something else, a new TimeSpan instance will be returned based on the input.
*/
public static function cast(DateInterval $dateInterval): TimeSpan {
if($dateInterval instanceof TimeSpan)
return $dateInterval;
$timeSpan = new TimeSpan;
$timeSpan->y = $dateInterval->y;
$timeSpan->m = $dateInterval->m;
$timeSpan->d = $dateInterval->d;
$timeSpan->h = $dateInterval->h;
$timeSpan->i = $dateInterval->i;
$timeSpan->s = $dateInterval->s;
$timeSpan->f = $dateInterval->f;
$timeSpan->invert = $dateInterval->invert;
$timeSpan->realDays = $timeSpan->calculateRealDays();
return $timeSpan;
}
}

View file

@ -1,213 +0,0 @@
<?php
// TimeZoneInfo.php
// Created: 2021-06-12
// Updated: 2022-02-27
namespace Index;
use DateTimeInterface;
use DateTimeZone;
use JsonSerializable;
use RuntimeException;
use Stringable;
/**
* Represents a time zone.
*/
class TimeZoneInfo extends DateTimeZone implements JsonSerializable, Stringable, IComparable, IEquatable {
private array|null $location = null;
private static bool $constructed = false;
private static TimeZoneInfo $utc;
/**
* Constructs a new TimeZoneInfo object.
*
* @param string $timeZone Time zone identifier.,
* @return TimeZoneInfo New instance of TimeZoneInfo.
*/
public function __construct(string $timeZone) {
parent::__construct($timeZone);
}
/**
* @internal
*/
public static function construct(): void {
if(self::$constructed)
throw new RuntimeException('Static constructor was already called.');
self::$constructed = true;
self::$utc = new TimeZoneInfo('UTC');
}
/**
* Gets a TimeZoneInfo instance representing the UTC (+00:00) time zone.
*
* @return TimeZoneInfo Instance representing UTC.
*/
public static function utc(): TimeZoneInfo {
return self::$utc;
}
/**
* Gets the current default time zone.
*
* Uses date_default_timezone_get internally.
*
* @return TimeZoneInfo Instance representing the default time zone.
*/
public static function default(): TimeZoneInfo {
return new TimeZoneInfo(date_default_timezone_get());
}
/**
* Sets the current default time zone.
*
* Uses date_default_timezone_set internally.
*
* @param DateTimeZone|string $timeZone Desired default time zone.
* @throws RuntimeException If an invalid time zone was supplied.
*/
public static function setDefault(DateTimeZone|string $timeZone): void {
if($timeZone instanceof DateTimeZone)
$timeZone = $timeZone->getName();
if(!date_default_timezone_set($timeZone))
throw new RuntimeException('Invalid default time zone specified.');
}
/**
* Gets location info for a time zone.
*
* It makes more sense to use getCountryCode, getLatitude, getLongitude and getComments, if you can.
*
* @return array Location information.
*/
public function getLocation(): array {
if($this->location === null)
$this->location = parent::getLocation();
return $this->location;
}
/**
* Gets the country code of the country which this time zone falls in.
*
* @return string Country code for this time zone.
*/
public function getCountryCode(): string {
return $this->getLocation()['country_code'];
}
/**
* Gets the latitude of this time zone.
*
* @return float Latitude of this time zone.
*/
public function getLatitude(): float {
return $this->getLocation()['latitude'];
}
/**
* Gets the longitude of this time zone.
*
* @return float Longitude of this time zone.
*/
public function getLongitude(): float {
return $this->getLocation()['longitude'];
}
/**
* Gets location comments for this time zone.
*
* @return string Location comments.
*/
public function getComments(): string {
return $this->getLocation()['comments'];
}
/**
* Gets the offset of this timezone relative to a given date/time instance.
*
* @param DateTimeInterface|null $dateTime Date/time to use as a base, null for UTC Now.
* @return int Offset relative to given date/time.
*/
public function getOffset(DateTimeInterface|null $dateTime = null): int {
return parent::getOffset($dateTime ?? new DateTime('now', self::$utc));
}
/**
* Creates a string representation of this object.
*
* @return string Representation of this object.
*/
public function __toString(): string {
$offset = $this->getOffset();
return sprintf(
'(UTC%s%s) %s',
$offset < 0 ? '-' : '+',
date('H:i', abs($offset)),
$this->getName()
);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return $this->getName();
}
public function compare(mixed $other): int {
if(!($other instanceof DateTimeZone))
return -1;
$other = self::cast($other);
$diff = $this->getOffset() <=> $other->getOffset();
if($diff) return $diff;
return strcmp($this->getName(), $other->getName());
}
public function equals(mixed $other): bool {
if(!($other instanceof DateTimeZone))
return false;
return $this->getName() === $other->getName()
|| $this->getOffset() === self::cast($other)->getOffset();
}
/**
* Gets a list of all time zones.
*
* @param bool $ordered true if the list should be ordered, false if not.
* @param int $timeZoneGroup ID of a group to filter the generated list.
* @param string|null $countryCode Country to filter the generated list by.
* @return array Array containing the time zones.
*/
public static function all(bool $ordered = false, int $timeZoneGroup = DateTimeZone::ALL, string|null $countryCode = null): array {
$timeZones = self::listIdentifiers($timeZoneGroup, $countryCode === null ? null : $countryCode);
$timeZones = XArray::select($timeZones, fn($id) => new TimeZoneInfo($id));
if($ordered)
$timeZones = XArray::sort($timeZones, fn($a, $b) => $a->compare($b));
return $timeZones;
}
/**
* Makes sure a DateTimeZone inheriting object is a TimeZoneInfo instance.
*
* @param DateTimeZone $timeZone Input object.
* @return TimeZoneInfo If the input was a TimeZoneInfo, the same instance will be returned.
* If the input was something else, a new TimeZoneInfo instance will be returned based on the input.
*/
public static function cast(DateTimeZone $timeZone): TimeZoneInfo {
if($timeZone instanceof TimeZoneInfo)
return $timeZone;
return new TimeZoneInfo($timeZone->getName());
}
}
TimeZoneInfo::construct();

103
src/XDateTime.php Normal file
View file

@ -0,0 +1,103 @@
<?php
// XDateTime.php
// Created: 2024-07-31
// Updated: 2024-07-31
namespace Index;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
final class XDateTime {
private const COMPARE_FORMAT = 'YmdHisu';
public static function now(): DateTimeInterface {
return new DateTimeImmutable('now');
}
private static function toCompareFormat(DateTimeInterface|string|int $dt): string {
if($dt instanceof DateTimeInterface)
return $dt->format(self::COMPARE_FORMAT);
if(is_string($dt))
$dt = strtotime($dt);
return gmdate(self::COMPARE_FORMAT, $dt);
}
public static function compare(
DateTimeInterface|string|int $dt1,
DateTimeInterface|string|int $dt2
): int {
return strcmp(
self::toCompareFormat($dt1),
self::toCompareFormat($dt2)
);
}
public static function compareTimeZone(
DateTimeZone|string $tz1,
DateTimeZone|string $tz2
): int {
if(is_string($tz1))
$tz1 = new DateTimeZone($tz1);
if(is_string($tz2))
$tz2 = new DateTimeZone($tz2);
$now = self::now();
$diff = $tz1->getOffset($now) <=> $tz2->getOffset($now);
if($diff) return $diff;
return strcmp(
$tz1->getName(),
$tz2->getName()
);
}
public static function timeZoneListName(DateTimeZone $dtz): string {
$offset = $dtz->getOffset(self::now());
return sprintf(
'(UTC%s%s) %s',
$offset < 0 ? '-' : '+',
date('H:i', abs($offset)),
$dtz->getName()
);
}
public static function listTimeZones(bool $sort = false, int $group = DateTimeZone::ALL, string|null $countryCode = null): array {
$list = DateTimeZone::listIdentifiers($group, $countryCode);
$list = XArray::select($list, fn($id) => new DateTimeZone($id));
if($sort)
$list = XArray::sort($list, self::compareTimeZone(...));
return $list;
}
public static function format(DateTimeInterface|string|int|null $dt, string $format): string {
if($dt === null) {
$dt = time();
} else {
if($dt instanceof DateTimeInterface)
return $dt->format($format);
if(is_string($dt))
$dt = strtotime($dt);
}
return gmdate($format, $dt);
}
public static function toISO8601String(DateTimeInterface|string|int|null $dt): string {
return self::format($dt, DateTimeInterface::ATOM);
}
public static function toCookieString(DateTimeInterface|string|int|null $dt): string {
return self::format($dt, DateTimeInterface::COOKIE);
}
public static function toRFC822String(DateTimeInterface|string|int|null $dt): string {
return self::format($dt, DateTimeInterface::RFC822);
}
}

View file

@ -1,386 +0,0 @@
<?php
// DateTimeTest.php
// Created: 2021-06-14
// Updated: 2024-07-31
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\DateTime;
use Index\TimeSpan;
use Index\TimeZoneInfo;
#[CoversClass(DateTime::class)]
#[CoversClass(TimeSpan::class)]
#[CoversClass(TimeZoneInfo::class)]
final class DateTimeTest extends TestCase {
public function testAttributes(): void {
$index = new DateTime('2021-06-14T21:07:14.359324 CEST');
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
$this->assertEquals($index->getDayOfWeek(), DateTime::MONDAY);
$this->assertEquals($index->getDayOfYear(), 165);
$this->assertEquals($index->getWeek(), 24);
$this->assertTrue($index->isDaylightSavingTime());
$this->assertFalse($index->isLeapYear());
$this->assertFalse($index->isUTC());
$indexDate = $index->getDate();
$this->assertEquals($indexDate->getYear(), 2021);
$this->assertEquals($indexDate->getMonth(), 6);
$this->assertEquals($indexDate->getDay(), 14);
$this->assertEquals($indexDate->getHour(), 0);
$this->assertEquals($indexDate->getMinute(), 0);
$this->assertEquals($indexDate->getSecond(), 0);
$this->assertEquals($indexDate->getMillisecond(), 0);
$this->assertEquals($indexDate->getMicrosecond(), 0);
$this->assertEquals($indexDate->getDayOfWeek(), DateTime::MONDAY);
$this->assertEquals($indexDate->getDayOfYear(), 165);
$this->assertEquals($indexDate->getWeek(), 24);
$indexTime = $index->getTimeOfDay();
$this->assertEquals($indexTime->getHours(), 21);
$this->assertEquals($indexTime->getMinutes(), 7);
$this->assertEquals($indexTime->getSeconds(), 14);
$this->assertEquals($indexTime->getMilliseconds(), 359);
$this->assertEquals($indexTime->getMicroseconds(), 359324);
}
public function testTimeZone(): void {
$index1 = DateTime::utcNow();
$index2 = DateTime::now(new TimeZoneInfo('Australia/Melbourne'));
$this->assertEquals($index1->getUnixTimeSeconds(), $index2->getUnixTimeSeconds());
}
public function testSetters(): void {
$index = new DateTime('2021-06-14T21:07:14.359324 CEST');
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
$index = $index->setDate(2005, 3, 9);
$this->assertEquals($index->getYear(), 2005);
$this->assertEquals($index->getMonth(), 3);
$this->assertEquals($index->getDay(), 9);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
$index = $index->setISODate(2011, 20, 5);
$this->assertEquals($index->getYear(), 2011);
$this->assertEquals($index->getMonth(), 5);
$this->assertEquals($index->getDay(), 20);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
$index = $index->setTime(10, 46, 50, 987654);
$this->assertEquals($index->getYear(), 2011);
$this->assertEquals($index->getMonth(), 5);
$this->assertEquals($index->getDay(), 20);
$this->assertEquals($index->getHour(), 10);
$this->assertEquals($index->getMinute(), 46);
$this->assertEquals($index->getSecond(), 50);
$this->assertEquals($index->getMillisecond(), 987);
$this->assertEquals($index->getMicrosecond(), 987654);
$index = $index->setTimestamp(1623702634);
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 22);
$this->assertEquals($index->getMinute(), 30);
$this->assertEquals($index->getSecond(), 34);
$this->assertEquals($index->getMillisecond(), 0);
$this->assertEquals($index->getMicrosecond(), 0);
$this->assertFalse($index->isUTC());
}
public function testUnixTimestamp(): void {
$index = DateTime::fromUnixTimeSeconds(1623696039);
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 18);
$this->assertEquals($index->getMinute(), 40);
$this->assertEquals($index->getSecond(), 39);
$this->assertEquals($index->getMillisecond(), 0);
$this->assertEquals($index->getMicrosecond(), 0);
$this->assertEquals($index->getUnixTimeSeconds(), 1623696039);
$this->assertEquals($index->getUnixTimeMilliseconds(), 1623696039000);
$this->assertTrue($index->isUTC());
$index = DateTime::fromUnixTimeMilliseconds(1623696422656);
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 18);
$this->assertEquals($index->getMinute(), 47);
$this->assertEquals($index->getSecond(), 2);
$this->assertEquals($index->getMillisecond(), 656);
$this->assertEquals($index->getMicrosecond(), 656000);
$this->assertEquals($index->getUnixTimeSeconds(), 1623696422);
$this->assertEquals($index->getUnixTimeMilliseconds(), 1623696422656);
$this->assertTrue($index->isUTC());
}
// verify the results of this at some point
public function testAdding(): void {
$index = new DateTime('2021-06-14T21:07:14.359324Z');
// Confirm initial state
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
// Add
$index = $index->add(TimeSpan::create(1, 0, 10, 0));
$this->assertEquals($index->getDay(), 15);
$this->assertEquals($index->getMinute(), 17);
// Add years
$index = $index->addYears(1);
$this->assertEquals($index->getYear(), 2022);
$this->assertEquals($index->getMonth(), 6);
$index = $index->addYears(1.5);
$this->assertEquals($index->getYear(), 2023);
$this->assertEquals($index->getMonth(), 12);
// Add months
$index = $index->addMonths(1);
$this->assertEquals($index->getMonth(), 12);
$this->assertEquals($index->getDay(), 16);
$index = $index->addMonths(1.5);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 15);
// Add days
$index = $index->addDays(1);
$this->assertEquals($index->getDay(), 16);
$this->assertEquals($index->getHour(), 21);
$index = $index->addDays(1.5);
$this->assertEquals($index->getDay(), 18);
$this->assertEquals($index->getHour(), 9);
// Add hours
$index = $index->addHours(1);
$this->assertEquals($index->getHour(), 10);
$this->assertEquals($index->getMinute(), 17);
$index = $index->addHours(1.5);
$this->assertEquals($index->getHour(), 11);
$this->assertEquals($index->getMinute(), 47);
// Add minutes
$index = $index->addMinutes(1);
$this->assertEquals($index->getMinute(), 48);
$this->assertEquals($index->getSecond(), 14);
$index = $index->addMinutes(1.5);
$this->assertEquals($index->getMinute(), 49);
$this->assertEquals($index->getSecond(), 44);
// Add seconds
$index = $index->addSeconds(1);
$this->assertEquals($index->getSecond(), 45);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
$index = $index->addSeconds(1.5);
$this->assertEquals($index->getSecond(), 46);
$this->assertEquals($index->getMillisecond(), 859);
$this->assertEquals($index->getMicrosecond(), 859324);
// Add milliseconds
$index = $index->addMilliseconds(1);
$this->assertEquals($index->getMillisecond(), 860);
$this->assertEquals($index->getMicrosecond(), 860324);
$index = $index->addMilliseconds(1.5);
$this->assertEquals($index->getMillisecond(), 861);
$this->assertEquals($index->getMicrosecond(), 861824);
// Add microseconds
$index = $index->addMicroseconds(1);
$this->assertEquals($index->getMicrosecond(), 861825);
$index = $index->addMicroseconds(1.5);
$this->assertEquals($index->getMicrosecond(), 861826);
}
public function testSubtract(): void {
$index = new DateTime('2021-06-14T21:07:14.359324Z');
// Confirm initial state
$this->assertEquals($index->getYear(), 2021);
$this->assertEquals($index->getMonth(), 6);
$this->assertEquals($index->getDay(), 14);
$this->assertEquals($index->getHour(), 21);
$this->assertEquals($index->getMinute(), 7);
$this->assertEquals($index->getSecond(), 14);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
// Subtract
$index = $index->subtract(TimeSpan::fromDays(400.12));
$this->assertEquals($index->getYear(), 2020);
$this->assertEquals($index->getMonth(), 5);
$this->assertEquals($index->getDay(), 10);
$this->assertEquals($index->getHour(), 18);
$this->assertEquals($index->getMinute(), 14);
$this->assertEquals($index->getSecond(), 26);
$this->assertEquals($index->getMillisecond(), 359);
$this->assertEquals($index->getMicrosecond(), 359324);
}
public function testDifference(): void {
$index1 = new DateTime('2021-06-14T21:07:14Z');
$index2 = DateTime::create(2013, 1, 27, 23, 14, 44, 0, new TimeZoneInfo('Europe/Amsterdam'));
$diff = $index1->difference($index2);
$this->assertEquals($diff->getDays(), 3061);
$this->assertEquals($diff->getHours(), 22);
$this->assertEquals($diff->getMinutes(), 52);
$this->assertEquals($diff->getSeconds(), 30);
$this->assertTrue($diff->isNegative());
$this->assertEquals($diff->totalDays(), 3061.953125);
$this->assertEquals($diff->totalHours(), 73486.875);
$this->assertEquals($diff->totalMinutes(), 4409212.5);
$this->assertEquals($diff->totalSeconds(), 264552750);
$this->assertEquals($diff->totalMilliseconds(), 264552750000);
$this->assertEquals($diff->totalMicroseconds(), 264552750000000);
$negated = $diff->negate();
$this->assertEquals($negated->getDays(), 3061);
$this->assertEquals($negated->getHours(), 22);
$this->assertEquals($negated->getMinutes(), 52);
$this->assertEquals($negated->getSeconds(), 30);
$this->assertFalse($negated->isNegative());
}
public function testModify(): void {
$index = new DateTime('2021-06-14T21:07:14Z');
$this->assertEquals($index->getMonth(), 6);
$index = $index->modify('+1 month');
$this->assertEquals($index->getMonth(), 7);
}
public function testCreate(): void {
$index = DateTime::create(2013, 1, 27, 23, 14, 44, 0, new TimeZoneInfo('Europe/Amsterdam'));
$this->assertEquals($index->getYear(), 2013);
$this->assertEquals($index->getMonth(), 1);
$this->assertEquals($index->getDay(), 27);
$this->assertEquals($index->getHour(), 23);
$this->assertEquals($index->getMinute(), 14);
$this->assertEquals($index->getSecond(), 44);
$index = $index->setTimezone(new TimeZoneInfo('UTC'));
$this->assertEquals($index->getHour(), 22);
$index = $index->setTimezone(new TimeZoneInfo('Asia/Tokyo'));
$this->assertEquals($index->getDay(), 28);
$this->assertEquals($index->getHour(), 7);
$index = $index->setTimezone(new TimeZoneInfo('America/New_York'));
$this->assertEquals($index->getDay(), 27);
$this->assertEquals($index->getHour(), 17);
$index = DateTime::createFromFormat('Y-m-d H:i:s', '2012-05-20 10:14:28');
$this->assertEquals($index->format('Y-m-d H:i:s'), '2012-05-20 10:14:28');
$this->assertFalse(DateTime::createFromFormat('Y-m-d H:i:s', 'mewow'));
$mutable = new \DateTime('2021-06-14T20:20:46Z');
$index = DateTime::createFromInterface($mutable);
$this->assertEquals($index->getUnixTimeMilliseconds(), (float)$mutable->format('Uv'));
$this->assertEquals($index->getTimezone()->getName(), '+00:00');
$index = DateTime::createFromMutable($mutable);
$this->assertEquals($index->getUnixTimeMilliseconds(), (float)$mutable->format('Uv'));
}
public function testToString(): void {
$dateTime = DateTime::create(2013, 1, 27, 23, 14, 44, 0, new TimeZoneInfo('Europe/Amsterdam'));
$this->assertEquals((string)$dateTime, '2013-01-27T23:14:44+01:00');
$this->assertEquals(json_encode($dateTime), '"2013-01-27T23:14:44+01:00"');
$this->assertEquals((string)$dateTime->toISO8601String(), '2013-01-27T23:14:44+01:00');
$this->assertEquals((string)$dateTime->toCookieString(), 'Sunday, 27-Jan-2013 23:14:44 CET');
$this->assertEquals((string)$dateTime->toRFC822String(), 'Sun, 27 Jan 13 23:14:44 +0100');
$dateTime = $dateTime->setTimezone(TimeZoneInfo::utc());
$this->assertEquals((string)$dateTime, '2013-01-27T22:14:44+00:00');
$this->assertEquals(json_encode($dateTime), '"2013-01-27T22:14:44+00:00"');
$this->assertEquals((string)$dateTime->toISO8601String(), '2013-01-27T22:14:44+00:00');
$this->assertEquals((string)$dateTime->toCookieString(), 'Sunday, 27-Jan-2013 22:14:44 UTC');
$this->assertEquals((string)$dateTime->toRFC822String(), 'Sun, 27 Jan 13 22:14:44 +0000');
}
public function testCompare(): void {
$index1 = new DateTime('2021-06-14T21:07:14Z');
$index2 = DateTime::create(2013, 1, 27, 23, 14, 44, 0, new TimeZoneInfo('Europe/Amsterdam'));
$this->assertGreaterThan(0, $index1->compare($index2));
$this->assertLessThan(0, $index2->compare($index1));
$this->assertEquals(0, $index1->compare($index1));
}
public function testEquals(): void {
$index1 = new DateTime('2021-06-14T21:07:14Z');
$index2 = DateTime::create(2013, 1, 27, 23, 14, 44, 0, new TimeZoneInfo('Europe/Amsterdam'));
$this->assertFalse($index1->equals($index2));
$this->assertFalse($index2->equals($index1));
$this->assertTrue($index1->equals($index1));
}
public function testCasting(): void {
$nativeDT = new \DateTime;
$this->assertInstanceOf(DateTime::class, $nativeDTCast = DateTime::cast($nativeDT));
$this->assertNotSame($nativeDT, $nativeDTCast);
$indexDT = new DateTime;
$this->assertInstanceOf(DateTime::class, $indexDTCast = DateTime::cast($indexDT));
$this->assertSame($indexDT, $indexDTCast);
}
public function testTimeZoneOrder(): void {
$all = TimeZoneInfo::all(true);
$lastOffset = null;
$lastString = null;
foreach($all as $timeZone) {
$offset = $timeZone->getOffset();
$string = $timeZone->getName();
if($lastOffset !== null) {
$diff = $lastOffset <=> $offset;
$this->assertLessThanOrEqual(0, $diff);
if($diff === 0)
$this->assertLessThanOrEqual(0, $lastString <=> $string);
}
$lastOffset = $offset;
$lastString = $string;
}
}
}

66
tests/XDateTimeTest.php Normal file
View file

@ -0,0 +1,66 @@
<?php
// XDateTimeTest.php
// Created: 2021-06-14
// Updated: 2024-07-31
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\XDateTime;
#[CoversClass(XDateTime::class)]
final class XDateTimeTest extends TestCase {
public function testToString(): void {
$dateTime = new DateTime('2013-01-27T23:14:44', new DateTimeZone('Europe/Amsterdam'));
$this->assertEquals(XDateTime::toISO8601String($dateTime), '2013-01-27T23:14:44+01:00');
$this->assertEquals(XDateTime::toCookieString($dateTime), 'Sunday, 27-Jan-2013 23:14:44 CET');
$this->assertEquals(XDateTime::toRFC822String($dateTime), 'Sun, 27 Jan 13 23:14:44 +0100');
$dateTime = $dateTime->setTimezone(new DateTimeZone('UTC'));
$this->assertEquals(XDateTime::toISO8601String($dateTime), '2013-01-27T22:14:44+00:00');
$this->assertEquals(XDateTime::toCookieString($dateTime), 'Sunday, 27-Jan-2013 22:14:44 UTC');
$this->assertEquals(XDateTime::toRFC822String($dateTime), 'Sun, 27 Jan 13 22:14:44 +0000');
$this->assertEquals(XDateTime::toISO8601String(1359324884), '2013-01-27T22:14:44+00:00');
$this->assertEquals(XDateTime::toCookieString(1359324884), 'Sunday, 27-Jan-2013 22:14:44 GMT');
$this->assertEquals(XDateTime::toRFC822String(1359324884), 'Sun, 27 Jan 13 22:14:44 +0000');
$this->assertEquals(XDateTime::toISO8601String('January 27th 2013 22:14:44 UTC'), '2013-01-27T22:14:44+00:00');
$this->assertEquals(XDateTime::toCookieString('January 27th 2013 22:14:44 UTC'), 'Sunday, 27-Jan-2013 22:14:44 GMT');
$this->assertEquals(XDateTime::toRFC822String('January 27th 2013 22:14:44 UTC'), 'Sun, 27 Jan 13 22:14:44 +0000');
}
public function testCompare(): void {
$dt1 = '2021-06-14T21:07:14Z';
$dt2 = new DateTimeImmutable('2013-01-27T23:14:44', new DateTimeZone('Europe/Amsterdam'));
$this->assertGreaterThan(0, XDateTime::compare($dt1, $dt2));
$this->assertLessThan(0, XDateTime::compare($dt2, $dt1));
$this->assertEquals(0, XDateTime::compare($dt1, $dt1));
$this->assertEquals(0, XDateTime::compare($dt2, $dt2));
}
public function testTimeZoneOrder(): void {
$timeZones = XDateTime::listTimeZones(true);
$lastOffset = null;
$lastString = null;
$now = XDateTime::now();
foreach($timeZones as $timeZone) {
$offset = $timeZone->getOffset($now);
$string = $timeZone->getName();
if($lastOffset !== null) {
$diff = $lastOffset <=> $offset;
$this->assertLessThanOrEqual(0, $diff);
if($diff === 0)
$this->assertLessThanOrEqual(0, $lastString <=> $string);
}
$lastOffset = $offset;
$lastString = $string;
}
}
}

View file

@ -56,7 +56,7 @@ $files = array_merge($sources, $tests);
$topDir = dirname(__DIR__) . DIRECTORY_SEPARATOR;
$now = date('Y-m-d');
$now = gmdate('Y-m-d');
foreach($files as $file) {
echo 'Scanning ' . str_replace($topDir, '', $file) . '...' . PHP_EOL;

View file

@ -3,7 +3,7 @@
date_default_timezone_set('utc');
$version = date('0.ym.jHi');
$version = gmdate('0.ym.jHi');
echo 'Updating VERSION file to ' . $version . PHP_EOL;