Upstream brick/date-time state of the touched files before 9dd1445216
This commit is contained in:
commit
d37085b46a
5 files changed
+3249
No files matched your search
+377
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\DateTime;
|
||||
|
||||
use JsonSerializable;
|
||||
use Override;
|
||||
use Stringable;
|
||||
|
||||
use function intdiv;
|
||||
use function rtrim;
|
||||
use function str_pad;
|
||||
|
||||
use const PHP_INT_MAX;
|
||||
use const PHP_INT_MIN;
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
/**
|
||||
* Represents a point in time, with a nanosecond precision.
|
||||
*
|
||||
* Instant represents the computer view of the timeline. It unambiguously represents a point in time,
|
||||
* without any calendar concept of date, time or time zone. It is not very meaningful to humans,
|
||||
* but can be converted to a `ZonedDateTime` by providing a time zone.
|
||||
*/
|
||||
final readonly class Instant implements JsonSerializable, Stringable
|
||||
{
|
||||
/**
|
||||
* Private constructor. Use of() to obtain an Instant.
|
||||
*
|
||||
* @param int $epochSecond The number of seconds since the epoch of 1970-01-01T00:00:00Z.
|
||||
* @param int $nano The nanosecond adjustment to the epoch second, validated in the range 0 to 999,999,999.
|
||||
*/
|
||||
private function __construct(
|
||||
private int $epochSecond,
|
||||
private int $nano,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Instant representing a number of seconds and an adjustment in nanoseconds.
|
||||
*
|
||||
* This method allows an arbitrary number of nanoseconds to be passed in.
|
||||
* The factory will alter the values of the second and nanosecond in order
|
||||
* to ensure that the stored nanosecond is in the range 0 to 999,999,999.
|
||||
* For example, the following will result in exactly the same instant:
|
||||
*
|
||||
* * Instant::of(3, 1);
|
||||
* * Instant::of(4, -999_999_999);
|
||||
* * Instant::of(2, 1_000_000_001);
|
||||
*
|
||||
* @param int $epochSecond The number of seconds since the UNIX epoch of 1970-01-01T00:00:00Z.
|
||||
* @param int $nanoAdjustment The adjustment to the epoch second in nanoseconds.
|
||||
*/
|
||||
public static function of(int $epochSecond, int $nanoAdjustment = 0): Instant
|
||||
{
|
||||
$nanos = $nanoAdjustment % LocalTime::NANOS_PER_SECOND;
|
||||
$epochSecond += intdiv($nanoAdjustment - $nanos, LocalTime::NANOS_PER_SECOND);
|
||||
|
||||
if ($nanos < 0) {
|
||||
$nanos += LocalTime::NANOS_PER_SECOND;
|
||||
$epochSecond--;
|
||||
}
|
||||
|
||||
return new Instant($epochSecond, $nanos);
|
||||
}
|
||||
|
||||
public static function epoch(): Instant
|
||||
{
|
||||
/** @var Instant|null $epoch */
|
||||
static $epoch = null;
|
||||
|
||||
return $epoch ??= new Instant(0, 0);
|
||||
}
|
||||
|
||||
public static function now(?Clock $clock = null): Instant
|
||||
{
|
||||
if ($clock === null) {
|
||||
$clock = DefaultClock::get();
|
||||
}
|
||||
|
||||
return $clock->getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum supported instant.
|
||||
*
|
||||
* This could be used by an application as a "far past" instant.
|
||||
*/
|
||||
public static function min(): Instant
|
||||
{
|
||||
/** @var Instant|null $min */
|
||||
static $min = null;
|
||||
|
||||
return $min ??= new Instant(PHP_INT_MIN, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum supported instant.
|
||||
*
|
||||
* This could be used by an application as a "far future" instant.
|
||||
*/
|
||||
public static function max(): Instant
|
||||
{
|
||||
/** @var Instant|null $max */
|
||||
static $max = null;
|
||||
|
||||
return $max ??= new Instant(PHP_INT_MAX, 999_999_999);
|
||||
}
|
||||
|
||||
public function plus(Duration $duration): Instant
|
||||
{
|
||||
if ($duration->isZero()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$seconds = $this->epochSecond + $duration->getSeconds();
|
||||
$nanos = $this->nano + $duration->getNanos();
|
||||
|
||||
return Instant::of($seconds, $nanos);
|
||||
}
|
||||
|
||||
public function minus(Duration $duration): Instant
|
||||
{
|
||||
if ($duration->isZero()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->plus($duration->negated());
|
||||
}
|
||||
|
||||
public function plusSeconds(int $seconds): Instant
|
||||
{
|
||||
if ($seconds === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new Instant($this->epochSecond + $seconds, $this->nano);
|
||||
}
|
||||
|
||||
public function minusSeconds(int $seconds): Instant
|
||||
{
|
||||
return $this->plusSeconds(-$seconds);
|
||||
}
|
||||
|
||||
public function plusMinutes(int $minutes): Instant
|
||||
{
|
||||
return $this->plusSeconds($minutes * LocalTime::SECONDS_PER_MINUTE);
|
||||
}
|
||||
|
||||
public function minusMinutes(int $minutes): Instant
|
||||
{
|
||||
return $this->plusMinutes(-$minutes);
|
||||
}
|
||||
|
||||
public function plusHours(int $hours): Instant
|
||||
{
|
||||
return $this->plusSeconds($hours * LocalTime::SECONDS_PER_HOUR);
|
||||
}
|
||||
|
||||
public function minusHours(int $hours): Instant
|
||||
{
|
||||
return $this->plusHours(-$hours);
|
||||
}
|
||||
|
||||
public function plusDays(int $days): Instant
|
||||
{
|
||||
return $this->plusSeconds($days * LocalTime::SECONDS_PER_DAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this Instant with the epoch second altered.
|
||||
*/
|
||||
public function withEpochSecond(int $epochSecond): Instant
|
||||
{
|
||||
if ($epochSecond === $this->epochSecond) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new Instant($epochSecond, $this->nano);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this Instant with the nano-of-second altered.
|
||||
*
|
||||
* @throws DateTimeException If the nano-of-second if not valid.
|
||||
*/
|
||||
public function withNano(int $nano): Instant
|
||||
{
|
||||
if ($nano === $this->nano) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
Field\NanoOfSecond::check($nano);
|
||||
|
||||
return new Instant($this->epochSecond, $nano);
|
||||
}
|
||||
|
||||
public function minusDays(int $days): Instant
|
||||
{
|
||||
return $this->plusDays(-$days);
|
||||
}
|
||||
|
||||
public function getEpochSecond(): int
|
||||
{
|
||||
return $this->epochSecond;
|
||||
}
|
||||
|
||||
public function getNano(): int
|
||||
{
|
||||
return $this->nano;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this instant with another.
|
||||
*
|
||||
* @return -1|0|1 If this instant is before, on, or after the given instant.
|
||||
*/
|
||||
public function compareTo(Instant $that): int
|
||||
{
|
||||
$seconds = $this->getEpochSecond() - $that->getEpochSecond();
|
||||
|
||||
if ($seconds !== 0) {
|
||||
return $seconds > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
$nanos = $this->getNano() - $that->getNano();
|
||||
|
||||
if ($nanos !== 0) {
|
||||
return $nanos > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant equals another.
|
||||
*/
|
||||
public function isEqualTo(Instant $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is after another.
|
||||
*/
|
||||
public function isAfter(Instant $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is after or equal to another.
|
||||
*/
|
||||
public function isAfterOrEqualTo(Instant $that): bool
|
||||
{
|
||||
return $this->compareTo($that) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is before another.
|
||||
*/
|
||||
public function isBefore(Instant $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is before or equal to another.
|
||||
*/
|
||||
public function isBeforeOrEqualTo(Instant $that): bool
|
||||
{
|
||||
return $this->compareTo($that) <= 0;
|
||||
}
|
||||
|
||||
public function isBetweenInclusive(Instant $from, Instant $to): bool
|
||||
{
|
||||
return $this->isAfterOrEqualTo($from) && $this->isBeforeOrEqualTo($to);
|
||||
}
|
||||
|
||||
public function isBetweenExclusive(Instant $from, Instant $to): bool
|
||||
{
|
||||
return $this->isAfter($from) && $this->isBefore($to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is in the future, according to the given clock.
|
||||
*
|
||||
* If no clock is provided, the system clock is used.
|
||||
*/
|
||||
public function isFuture(?Clock $clock = null): bool
|
||||
{
|
||||
return $this->isAfter(Instant::now($clock));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instant is in the past, according to the given clock.
|
||||
*
|
||||
* If no clock is provided, the system clock is used.
|
||||
*/
|
||||
public function isPast(?Clock $clock = null): bool
|
||||
{
|
||||
return $this->isBefore(Instant::now($clock));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ZonedDateTime formed from this instant and the specified time-zone.
|
||||
*/
|
||||
public function atTimeZone(TimeZone $timeZone): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Interval from this Instant (inclusive) to the given one (exclusive).
|
||||
*
|
||||
* @throws DateTimeException If the given Instant is before this Instant.
|
||||
*/
|
||||
public function getIntervalTo(Instant $that): Interval
|
||||
{
|
||||
return Interval::of($this, $that);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a decimal representation of the timestamp represented by this instant.
|
||||
*
|
||||
* The output does not have trailing decimal zeros.
|
||||
*
|
||||
* Examples: `123456789`, `123456789.5`, `123456789.000000001`.
|
||||
*/
|
||||
public function toDecimal(): string
|
||||
{
|
||||
$result = (string) $this->epochSecond;
|
||||
|
||||
if ($this->nano !== 0) {
|
||||
$nano = (string) $this->nano;
|
||||
$nano = str_pad($nano, 9, '0', STR_PAD_LEFT);
|
||||
$nano = rtrim($nano, '0');
|
||||
|
||||
$result .= '.' . $nano;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes as a string using {@see Instant::toISOString()}.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
#[Override]
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ISO 8601 representation of this instant.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function toISOString(): string
|
||||
{
|
||||
return (string) ZonedDateTime::ofInstant($this, TimeZone::utc());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@see Instant::toISOString()}.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
#[Override]
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toISOString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,761 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\DateTime;
|
||||
|
||||
use Brick\DateTime\Parser\DateTimeParseException;
|
||||
use Brick\DateTime\Parser\DateTimeParser;
|
||||
use Brick\DateTime\Parser\DateTimeParseResult;
|
||||
use Brick\DateTime\Parser\IsoParsers;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use JsonSerializable;
|
||||
use Override;
|
||||
use Stringable;
|
||||
|
||||
use function assert;
|
||||
use function intdiv;
|
||||
|
||||
/**
|
||||
* A date-time with a time-zone in the ISO-8601 calendar system.
|
||||
*
|
||||
* A ZonedDateTime can be viewed as a LocalDateTime along with a time zone
|
||||
* and targets a specific point in time.
|
||||
*/
|
||||
final readonly class ZonedDateTime implements JsonSerializable, Stringable
|
||||
{
|
||||
/**
|
||||
* Private constructor. Use a factory method to obtain an instance.
|
||||
*
|
||||
* @param LocalDateTime $localDateTime The local date-time.
|
||||
* @param TimeZoneOffset $timeZoneOffset The time-zone offset from UTC/Greenwich.
|
||||
* @param TimeZone $timeZone The time-zone. It is either a TimeZoneRegion if this ZonedDateTime is
|
||||
* region-based, or the same instance as the offset if this ZonedDateTime
|
||||
* is offset-based.
|
||||
* @param Instant $instant The instant represented by this ZonedDateTime.
|
||||
*/
|
||||
private function __construct(
|
||||
private LocalDateTime $localDateTime,
|
||||
private TimeZoneOffset $timeZoneOffset,
|
||||
private TimeZone $timeZone,
|
||||
private Instant $instant,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ZonedDateTime from a LocalDateTime and a TimeZone.
|
||||
*
|
||||
* This resolves the local date-time to an instant on the time-line.
|
||||
*
|
||||
* When a TimeZoneOffset is used, the local date-time can be converted to an instant without ambiguity.
|
||||
*
|
||||
* When a TimeZoneRegion is used, Daylight Saving Time can make the conversion more complex.
|
||||
* There are 3 cases:
|
||||
*
|
||||
* - Normal: when there is only one valid offset for the date-time. The conversion is then as straightforward
|
||||
* as when using a TimeZoneOffset. This is fortunately the case for the vast majority of the year.
|
||||
* - Gap: when there is no valid offset for the date-time. This happens when the clock jumps forward
|
||||
* typically due to a DST transition from "winter" to "summer". The date-times between the two times
|
||||
* of the transition do not exist.
|
||||
* - Overlap: when there are two valid offsets for the date-time. This happens when the clock is set back
|
||||
* typically due to a DST transition from "summer" to "winter". The date-times between the two times
|
||||
* of the transition can be resolved to two different offsets, representing two different instants
|
||||
* on the time-line.
|
||||
*
|
||||
* The strategy for resolving gaps and overlaps is the following:
|
||||
*
|
||||
* - If the local date-time falls in the middle of a gap, then the resulting date-time will be shifted forward
|
||||
* by the length of the gap, and the later offset, typically "summer" time, will be used.
|
||||
* - If the local date-time falls in the middle of an overlap, then the offset closest to UTC will be used.
|
||||
*/
|
||||
public static function of(LocalDateTime $dateTime, TimeZone $timeZone): ZonedDateTime
|
||||
{
|
||||
$dtz = $timeZone->toNativeDateTimeZone();
|
||||
$dt = new DateTime((string) $dateTime->withNano(0), $dtz);
|
||||
|
||||
$instant = Instant::of($dt->getTimestamp(), $dateTime->getNano());
|
||||
|
||||
if ($timeZone instanceof TimeZoneOffset) {
|
||||
$timeZoneOffset = $timeZone;
|
||||
} else {
|
||||
$timeZoneOffset = TimeZoneOffset::ofTotalSeconds($dt->getOffset());
|
||||
}
|
||||
|
||||
// The time can be affected if the date-time is not valid for the given time-zone due to a DST transition,
|
||||
// so we have to re-compute the local date-time from the DateTime object.
|
||||
// DateTime does not support nanos of seconds, so we just copy the nanos back from the original date-time.
|
||||
$dateTime = LocalDateTime::parse($dt->format('Y-m-d\TH:i:s'))->withNano($dateTime->getNano());
|
||||
|
||||
return new ZonedDateTime($dateTime, $timeZoneOffset, $timeZone, $instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ZonedDateTime from an instant and a time zone.
|
||||
*
|
||||
* This resolves the instant to a date and time without ambiguity.
|
||||
*/
|
||||
public static function ofInstant(Instant $instant, TimeZone $timeZone): ZonedDateTime
|
||||
{
|
||||
$dateTimeZone = $timeZone->toNativeDateTimeZone();
|
||||
|
||||
// We need to pass a DateTimeZone to avoid a PHP warning...
|
||||
$dateTime = new DateTime('@' . $instant->getEpochSecond(), $dateTimeZone);
|
||||
|
||||
// ... but this DateTimeZone is ignored because of the timestamp, so we set it again.
|
||||
$dateTime->setTimezone($dateTimeZone);
|
||||
|
||||
$localDateTime = LocalDateTime::parse($dateTime->format('Y-m-d\TH:i:s'));
|
||||
$localDateTime = $localDateTime->withNano($instant->getNano());
|
||||
|
||||
if ($timeZone instanceof TimeZoneOffset) {
|
||||
$timeZoneOffset = $timeZone;
|
||||
} else {
|
||||
$timeZoneOffset = TimeZoneOffset::ofTotalSeconds($dateTime->getOffset());
|
||||
}
|
||||
|
||||
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current date-time in the given time-zone, according to the given clock.
|
||||
*
|
||||
* If no clock is provided, the system clock is used.
|
||||
*/
|
||||
public static function now(TimeZone $timeZone, ?Clock $clock = null): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant(Instant::now($clock), $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of `ZonedDateTime` from a set of date-time fields.
|
||||
*
|
||||
* This method is only useful to parsers.
|
||||
*
|
||||
* @throws DateTimeException If the zoned date-time is not valid.
|
||||
* @throws DateTimeParseException If required fields are missing from the result.
|
||||
*/
|
||||
public static function from(DateTimeParseResult $result): ZonedDateTime
|
||||
{
|
||||
$localDateTime = LocalDateTime::from($result);
|
||||
|
||||
$timeZoneOffset = TimeZoneOffset::from($result);
|
||||
|
||||
if ($result->hasField(Field\TimeZoneRegion::NAME)) {
|
||||
$timeZone = TimeZoneRegion::from($result);
|
||||
} else {
|
||||
$timeZone = $timeZoneOffset;
|
||||
}
|
||||
|
||||
return ZonedDateTime::of(
|
||||
$localDateTime,
|
||||
$timeZone,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of `ZonedDateTime` from a text string.
|
||||
*
|
||||
* Valid examples:
|
||||
* - `2007-12-03T10:15:30:45Z`
|
||||
* - `2007-12-03T10:15:30+01:00`
|
||||
* - `2007-12-03T10:15:30+01:00[Europe/Paris]`
|
||||
*
|
||||
* @param string $text The text to parse.
|
||||
* @param DateTimeParser|null $parser The parser to use, defaults to the ISO 8601 parser.
|
||||
*
|
||||
* @throws DateTimeException If the date is not valid.
|
||||
* @throws DateTimeParseException If the text string does not follow the expected format.
|
||||
*/
|
||||
public static function parse(string $text, ?DateTimeParser $parser = null): ZonedDateTime
|
||||
{
|
||||
if ($parser === null) {
|
||||
$parser = IsoParsers::zonedDateTime();
|
||||
}
|
||||
|
||||
return ZonedDateTime::from($parser->parse($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ZonedDateTime from a native DateTime or DateTimeImmutable object.
|
||||
*
|
||||
* @throws DateTimeException If the DateTime object has no timezone.
|
||||
*/
|
||||
public static function fromNativeDateTime(DateTimeInterface $dateTime): ZonedDateTime
|
||||
{
|
||||
$localDateTime = LocalDateTime::fromNativeDateTime($dateTime);
|
||||
|
||||
$dateTimeZone = $dateTime->getTimezone();
|
||||
|
||||
if ($dateTimeZone === false) { // @phpstan-ignore identical.alwaysFalse
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new DateTimeException('This DateTime object has no timezone.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$timeZone = TimeZone::fromNativeDateTimeZone($dateTimeZone);
|
||||
|
||||
if ($timeZone instanceof TimeZoneOffset) {
|
||||
$timeZoneOffset = $timeZone;
|
||||
} else {
|
||||
$timeZoneOffset = TimeZoneOffset::ofTotalSeconds($dateTime->getOffset());
|
||||
}
|
||||
|
||||
$instant = Instant::of($dateTime->getTimestamp(), $localDateTime->getNano());
|
||||
|
||||
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `LocalDateTime` part of this `ZonedDateTime`.
|
||||
*/
|
||||
public function getDateTime(): LocalDateTime
|
||||
{
|
||||
return $this->localDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `LocalDate` part of this `ZonedDateTime`.
|
||||
*/
|
||||
public function getDate(): LocalDate
|
||||
{
|
||||
return $this->localDateTime->getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `LocalTime` part of this `ZonedDateTime`.
|
||||
*/
|
||||
public function getTime(): LocalTime
|
||||
{
|
||||
return $this->localDateTime->getTime();
|
||||
}
|
||||
|
||||
public function getYear(): int
|
||||
{
|
||||
return $this->localDateTime->getYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the month-of-year as a Month enum.
|
||||
*/
|
||||
public function getMonth(): Month
|
||||
{
|
||||
return $this->localDateTime->getMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the month-of-year value from 1 to 12.
|
||||
*
|
||||
* @return int<1, 12>
|
||||
*/
|
||||
public function getMonthValue(): int
|
||||
{
|
||||
return $this->localDateTime->getMonthValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<1, 31>
|
||||
*/
|
||||
public function getDayOfMonth(): int
|
||||
{
|
||||
return $this->localDateTime->getDayOfMonth();
|
||||
}
|
||||
|
||||
public function getDayOfWeek(): DayOfWeek
|
||||
{
|
||||
return $this->localDateTime->getDayOfWeek();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<1, 366>
|
||||
*/
|
||||
public function getDayOfYear(): int
|
||||
{
|
||||
return $this->localDateTime->getDayOfYear();
|
||||
}
|
||||
|
||||
public function getHour(): int
|
||||
{
|
||||
return $this->localDateTime->getHour();
|
||||
}
|
||||
|
||||
public function getMinute(): int
|
||||
{
|
||||
return $this->localDateTime->getMinute();
|
||||
}
|
||||
|
||||
public function getSecond(): int
|
||||
{
|
||||
return $this->localDateTime->getSecond();
|
||||
}
|
||||
|
||||
public function getEpochSecond(): int
|
||||
{
|
||||
return $this->instant->getEpochSecond();
|
||||
}
|
||||
|
||||
public function getNano(): int
|
||||
{
|
||||
return $this->instant->getNano();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time-zone, region or offset.
|
||||
*/
|
||||
public function getTimeZone(): TimeZone
|
||||
{
|
||||
return $this->timeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time-zone offset.
|
||||
*/
|
||||
public function getTimeZoneOffset(): TimeZoneOffset
|
||||
{
|
||||
return $this->timeZoneOffset;
|
||||
}
|
||||
|
||||
public function getInstant(): Instant
|
||||
{
|
||||
return $this->instant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with a different date.
|
||||
*/
|
||||
public function withDate(LocalDate $date): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withDate($date), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with a different time.
|
||||
*/
|
||||
public function withTime(LocalTime $time): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withTime($time), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the year altered.
|
||||
*/
|
||||
public function withYear(int $year): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withYear($year), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the month-of-year altered.
|
||||
*/
|
||||
public function withMonth(int|Month $month): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withMonth($month), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the day-of-month altered.
|
||||
*/
|
||||
public function withDay(int $day): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withDay($day), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the hour-of-day altered.
|
||||
*/
|
||||
public function withHour(int $hour): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withHour($hour), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the minute-of-hour altered.
|
||||
*/
|
||||
public function withMinute(int $minute): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withMinute($minute), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the second-of-minute altered.
|
||||
*/
|
||||
public function withSecond(int $second): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withSecond($second), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the nano-of-second altered.
|
||||
*/
|
||||
public function withNano(int $nano): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->withNano($nano), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this `ZonedDateTime` with a different time-zone,
|
||||
* retaining the local date-time if possible.
|
||||
*/
|
||||
public function withTimeZoneSameLocal(TimeZone $timeZone): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this date-time with a different time-zone, retaining the instant.
|
||||
*/
|
||||
public function withTimeZoneSameInstant(TimeZone $timeZone): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this->instant, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this date-time with the time-zone set to the offset.
|
||||
*
|
||||
* This returns a zoned date-time where the time-zone is the same as `getOffset()`.
|
||||
* The local date-time, offset and instant of the result will be the same as in this date-time.
|
||||
*
|
||||
* Setting the date-time to a fixed single offset means that any future
|
||||
* calculations, such as addition or subtraction, have no complex edge cases
|
||||
* due to time-zone rules.
|
||||
* This might also be useful when sending a zoned date-time across a network,
|
||||
* as most protocols, such as ISO-8601, only handle offsets, and not region-based time zones.
|
||||
*/
|
||||
public function withFixedOffsetTimeZone(): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime, $this->timeZoneOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified Period added.
|
||||
*/
|
||||
public function plusPeriod(Period $period): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->plusPeriod($period), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified Duration added.
|
||||
*/
|
||||
public function plusDuration(Duration $duration): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this->instant->plus($duration), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Interval from this ZonedDateTime (inclusive) to the given one (exclusive).
|
||||
*
|
||||
* @throws DateTimeException If the given ZonedDateTime is before this ZonedDateTime.
|
||||
*/
|
||||
public function getIntervalTo(ZonedDateTime $that): Interval
|
||||
{
|
||||
return $this->getInstant()->getIntervalTo($that->getInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Duration representing the time elapsed between this ZonedDateTime and the given one.
|
||||
* This method will return a negative duration if the given ZonedDateTime is before the current one.
|
||||
*/
|
||||
public function getDurationTo(ZonedDateTime $that): Duration
|
||||
{
|
||||
return Duration::between($this->getInstant(), $that->getInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in years added.
|
||||
*/
|
||||
public function plusYears(int $years): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->plusYears($years), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in months added.
|
||||
*/
|
||||
public function plusMonths(int $months): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->plusMonths($months), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in weeks added.
|
||||
*/
|
||||
public function plusWeeks(int $weeks): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->plusWeeks($weeks), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in days added.
|
||||
*/
|
||||
public function plusDays(int $days): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::of($this->localDateTime->plusDays($days), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in hours added.
|
||||
*/
|
||||
public function plusHours(int $hours): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this->instant->plusHours($hours), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in minutes added.
|
||||
*/
|
||||
public function plusMinutes(int $minutes): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this->instant->plusMinutes($minutes), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in seconds added.
|
||||
*/
|
||||
public function plusSeconds(int $seconds): ZonedDateTime
|
||||
{
|
||||
return ZonedDateTime::ofInstant($this->instant->plusSeconds($seconds), $this->timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified Period subtracted.
|
||||
*/
|
||||
public function minusPeriod(Period $period): ZonedDateTime
|
||||
{
|
||||
return $this->plusPeriod($period->negated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified Duration subtracted.
|
||||
*/
|
||||
public function minusDuration(Duration $duration): ZonedDateTime
|
||||
{
|
||||
return $this->plusDuration($duration->negated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in years subtracted.
|
||||
*/
|
||||
public function minusYears(int $years): ZonedDateTime
|
||||
{
|
||||
return $this->plusYears(-$years);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in months subtracted.
|
||||
*/
|
||||
public function minusMonths(int $months): ZonedDateTime
|
||||
{
|
||||
return $this->plusMonths(-$months);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in weeks subtracted.
|
||||
*/
|
||||
public function minusWeeks(int $weeks): ZonedDateTime
|
||||
{
|
||||
return $this->plusWeeks(-$weeks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in days subtracted.
|
||||
*/
|
||||
public function minusDays(int $days): ZonedDateTime
|
||||
{
|
||||
return $this->plusDays(-$days);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in hours subtracted.
|
||||
*/
|
||||
public function minusHours(int $hours): ZonedDateTime
|
||||
{
|
||||
return $this->plusHours(-$hours);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in minutes subtracted.
|
||||
*/
|
||||
public function minusMinutes(int $minutes): ZonedDateTime
|
||||
{
|
||||
return $this->plusMinutes(-$minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ZonedDateTime with the specified period in seconds subtracted.
|
||||
*/
|
||||
public function minusSeconds(int $seconds): ZonedDateTime
|
||||
{
|
||||
return $this->plusSeconds(-$seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this ZonedDateTime with another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*
|
||||
* @return -1|0|1 If this zoned date-time is before, on, or after the given one.
|
||||
*/
|
||||
public function compareTo(ZonedDateTime $that): int
|
||||
{
|
||||
return $this->instant->compareTo($that->instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime equals another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*/
|
||||
public function isEqualTo(ZonedDateTime $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is after another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*/
|
||||
public function isAfter(ZonedDateTime $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is after or equal to another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*/
|
||||
public function isAfterOrEqualTo(ZonedDateTime $that): bool
|
||||
{
|
||||
return $this->compareTo($that) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is before another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*/
|
||||
public function isBefore(ZonedDateTime $that): bool
|
||||
{
|
||||
return $this->compareTo($that) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is before or equal to another.
|
||||
*
|
||||
* The comparison is performed on the instant.
|
||||
*/
|
||||
public function isBeforeOrEqualTo(ZonedDateTime $that): bool
|
||||
{
|
||||
return $this->compareTo($that) <= 0;
|
||||
}
|
||||
|
||||
public function isBetweenInclusive(ZonedDateTime $from, ZonedDateTime $to): bool
|
||||
{
|
||||
return $this->isAfterOrEqualTo($from) && $this->isBeforeOrEqualTo($to);
|
||||
}
|
||||
|
||||
public function isBetweenExclusive(ZonedDateTime $from, ZonedDateTime $to): bool
|
||||
{
|
||||
return $this->isAfter($from) && $this->isBefore($to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is in the future, according to the given clock.
|
||||
*
|
||||
* If no clock is provided, the system clock is used.
|
||||
*/
|
||||
public function isFuture(?Clock $clock = null): bool
|
||||
{
|
||||
return $this->instant->isFuture($clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ZonedDateTime is in the past, according to the given clock.
|
||||
*
|
||||
* If no clock is provided, the system clock is used.
|
||||
*/
|
||||
public function isPast(?Clock $clock = null): bool
|
||||
{
|
||||
return $this->instant->isPast($clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this ZonedDateTime to a native DateTime object.
|
||||
*
|
||||
* Note that the native DateTime object supports a precision up to the microsecond,
|
||||
* so the nanoseconds are rounded down to the nearest microsecond.
|
||||
*/
|
||||
public function toNativeDateTime(): DateTime
|
||||
{
|
||||
$second = $this->localDateTime->getSecond();
|
||||
|
||||
// round down to the microsecond
|
||||
$nano = $this->localDateTime->getNano();
|
||||
$nano = 1000 * intdiv($nano, 1000);
|
||||
|
||||
$dateTime = (string) $this->localDateTime->withNano($nano);
|
||||
$dateTimeZone = $this->timeZone->toNativeDateTimeZone();
|
||||
|
||||
$format = 'Y-m-d\TH:i';
|
||||
|
||||
if ($second !== 0 || $nano !== 0) {
|
||||
$format .= ':s';
|
||||
|
||||
if ($nano !== 0) {
|
||||
$format .= '.u';
|
||||
}
|
||||
}
|
||||
|
||||
$nativeDateTime = DateTime::createFromFormat($format, $dateTime, $dateTimeZone);
|
||||
|
||||
assert($nativeDateTime !== false);
|
||||
|
||||
return $nativeDateTime;
|
||||
}
|
||||
|
||||
public function toNativeDateTimeImmutable(): DateTimeImmutable
|
||||
{
|
||||
return DateTimeImmutable::createFromMutable($this->toNativeDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes as a string using {@see ZonedDateTime::toISOString()}.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
#[Override]
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ISO 8601 representation of this zoned date time.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function toISOString(): string
|
||||
{
|
||||
$string = $this->localDateTime . $this->timeZoneOffset;
|
||||
|
||||
if ($this->timeZone instanceof TimeZoneRegion) {
|
||||
$string .= '[' . $this->timeZone . ']';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@see ZonedDateTime::toISOString()}.
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
#[Override]
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toISOString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\DateTime\Tests;
|
||||
|
||||
use Brick\DateTime\Clock\FixedClock;
|
||||
use Brick\DateTime\DateTimeException;
|
||||
use Brick\DateTime\Duration;
|
||||
use Brick\DateTime\Instant;
|
||||
use Brick\DateTime\TimeZone;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
use function json_encode;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const PHP_INT_MAX;
|
||||
use const PHP_INT_MIN;
|
||||
|
||||
/**
|
||||
* Unit tests for class Instant.
|
||||
*/
|
||||
class InstantTest extends AbstractTestCase
|
||||
{
|
||||
/**
|
||||
* @param int $seconds The duration in seconds.
|
||||
* @param int $nanoAdjustment The nanoseconds adjustment to the duration.
|
||||
* @param int $expectedSeconds The expected adjusted duration seconds.
|
||||
* @param int $expectedNanos The expected adjusted duration nanoseconds.
|
||||
*/
|
||||
#[DataProvider('providerOf')]
|
||||
public function testOf(int $seconds, int $nanoAdjustment, int $expectedSeconds, int $expectedNanos): void
|
||||
{
|
||||
$duration = Instant::of($seconds, $nanoAdjustment);
|
||||
|
||||
self::assertSame($expectedSeconds, $duration->getEpochSecond());
|
||||
self::assertSame($expectedNanos, $duration->getNano());
|
||||
}
|
||||
|
||||
public static function providerOf(): array
|
||||
{
|
||||
return [
|
||||
[3, 1, 3, 1],
|
||||
[4, -999999999, 3, 1],
|
||||
[2, 1000000001, 3, 1],
|
||||
[-3, 1, -3, 1],
|
||||
[-4, 1000000001, -3, 1],
|
||||
[-2, -999999999, -3, 1],
|
||||
[1, -1000000001, -1, 999999999],
|
||||
[-1, -1000000001, -3, 999999999],
|
||||
];
|
||||
}
|
||||
|
||||
public function testEpoch(): void
|
||||
{
|
||||
$epoch = Instant::epoch();
|
||||
|
||||
self::assertInstantIs(0, 0, $epoch);
|
||||
self::assertSame($epoch, Instant::epoch());
|
||||
}
|
||||
|
||||
public function testNow(): void
|
||||
{
|
||||
$clock = new FixedClock(Instant::of(123456789, 987654321));
|
||||
self::assertInstantIs(123456789, 987654321, Instant::now($clock));
|
||||
}
|
||||
|
||||
public function testMin(): void
|
||||
{
|
||||
$min = Instant::min();
|
||||
|
||||
self::assertInstantIs(PHP_INT_MIN, 0, $min);
|
||||
self::assertSame($min, Instant::min());
|
||||
}
|
||||
|
||||
public function testMax(): void
|
||||
{
|
||||
$max = Instant::max();
|
||||
|
||||
self::assertInstantIs(PHP_INT_MAX, 999999999, $max);
|
||||
self::assertSame($max, Instant::max());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusSeconds The seconds of the duration to add.
|
||||
* @param int $plusNanos The nanos of the duration to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
* @param int $expectedNano The expected nano of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlus')]
|
||||
public function testPlus(int $second, int $nano, int $plusSeconds, int $plusNanos, int $expectedSecond, int $expectedNano): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->plus(Duration::ofSeconds($plusSeconds, $plusNanos));
|
||||
self::assertInstantIs($expectedSecond, $expectedNano, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusSeconds The seconds of the duration to add.
|
||||
* @param int $plusNanos The nanos of the duration to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
* @param int $expectedNano The expected nano of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlus')]
|
||||
public function testMinus(int $second, int $nano, int $plusSeconds, int $plusNanos, int $expectedSecond, int $expectedNano): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->minus(Duration::ofSeconds(-$plusSeconds, -$plusNanos));
|
||||
self::assertInstantIs($expectedSecond, $expectedNano, $result);
|
||||
}
|
||||
|
||||
public static function providerPlus(): array
|
||||
{
|
||||
return [
|
||||
[123456, 789, 0, 0, 123456, 789],
|
||||
[123456, 789, 123, 456789, 123579, 457578],
|
||||
[123456, 789, -123, -456789, 123332, 999544000],
|
||||
[123456789, 999999999, 1, 1, 123456791, 0],
|
||||
[123456789, 0, -1, -1, 123456787, 999999999],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusSeconds The number of seconds to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusSeconds')]
|
||||
public function testPlusSeconds(int $second, int $nano, int $plusSeconds, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->plusSeconds($plusSeconds);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusSeconds The number of seconds to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusSeconds')]
|
||||
public function testMinusSeconds(int $second, int $nano, int $plusSeconds, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->minusSeconds(-$plusSeconds);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
public static function providerPlusSeconds(): array
|
||||
{
|
||||
return [
|
||||
[123456, 789, 0, 123456, 789],
|
||||
[123456, 789, 1000, 124456],
|
||||
[123456, 789, -1000, 122456],
|
||||
[123456, 123456789, 456, 123912],
|
||||
[123456, 123456789, -456, 123000],
|
||||
[123456, 987, 1000000, 1123456],
|
||||
[123456, 987, -1000000, -876544],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusMinutes The number of minutes to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusMinutes')]
|
||||
public function testPlusMinutes(int $second, int $nano, int $plusMinutes, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->plusMinutes($plusMinutes);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusMinutes The number of minutes to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusMinutes')]
|
||||
public function testMinusMinutes(int $second, int $nano, int $plusMinutes, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->minusMinutes(-$plusMinutes);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
public static function providerPlusMinutes(): array
|
||||
{
|
||||
return [
|
||||
[123456, 789, 0, 123456, 789],
|
||||
[123456, 789, 1000, 183456],
|
||||
[123456, 789, -1000, 63456],
|
||||
[123456, 123456789, 456, 150816],
|
||||
[123456, 123456789, -456, 96096],
|
||||
[123456, 987, 1000000, 60123456],
|
||||
[123456, 987, -1000000, -59876544],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusHours The number of hours to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusHours')]
|
||||
public function testPlusHours(int $second, int $nano, int $plusHours, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->plusHours($plusHours);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusHours The number of hours to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusHours')]
|
||||
public function testMinusHours(int $second, int $nano, int $plusHours, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->minusHours(-$plusHours);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
public static function providerPlusHours(): array
|
||||
{
|
||||
return [
|
||||
[123456, 789, 0, 123456, 789],
|
||||
[123456, 789, 1000, 3723456],
|
||||
[123456, 789, -1000, -3476544],
|
||||
[123456, 123456789, 456, 1765056],
|
||||
[123456, 123456789, -456, -1518144],
|
||||
[123456, 987, 1, 127056],
|
||||
[123456, 987, -1, 119856],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusDays The number of days to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusDays')]
|
||||
public function testPlusDays(int $second, int $nano, int $plusDays, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->plusDays($plusDays);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The base second.
|
||||
* @param int $nano The base nano-of-second.
|
||||
* @param int $plusDays The number of days to add.
|
||||
* @param int $expectedSecond The expected second of the result.
|
||||
*/
|
||||
#[DataProvider('providerPlusDays')]
|
||||
public function testMinusDays(int $second, int $nano, int $plusDays, int $expectedSecond): void
|
||||
{
|
||||
$result = Instant::of($second, $nano)->minusDays(-$plusDays);
|
||||
self::assertInstantIs($expectedSecond, $nano, $result);
|
||||
}
|
||||
|
||||
public static function providerPlusDays(): array
|
||||
{
|
||||
return [
|
||||
[123456, 789, 0, 123456, 789],
|
||||
[123456, 789, 1000, 86523456],
|
||||
[123456, 789, -1000, -86276544],
|
||||
[123456, 123456789, 456, 39521856],
|
||||
[123456, 123456789, -456, -39274944],
|
||||
[123456, 987, 1, 209856],
|
||||
[123456, 987, -1, 37056],
|
||||
];
|
||||
}
|
||||
|
||||
public function testWithEpochSecond(): void
|
||||
{
|
||||
$instant = Instant::of(1234567890, 987654321);
|
||||
self::assertInstantIs(2345678901, 987654321, $instant->withEpochSecond(2345678901));
|
||||
}
|
||||
|
||||
public function testWithNano(): void
|
||||
{
|
||||
$instant = Instant::of(1234567890, 987654321);
|
||||
self::assertInstantIs(1234567890, 123456789, $instant->withNano(123456789));
|
||||
}
|
||||
|
||||
#[DataProvider('providerWithInvalidNanoThrowsException')]
|
||||
public function testWithInvalidNanoThrowsException(int $nano): void
|
||||
{
|
||||
$instant = Instant::of(1234567890, 987654321);
|
||||
|
||||
$this->expectException(DateTimeException::class);
|
||||
$instant->withNano($nano);
|
||||
}
|
||||
|
||||
public static function providerWithInvalidNanoThrowsException(): array
|
||||
{
|
||||
return [
|
||||
[-1],
|
||||
[1000000000],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The expected comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testCompareTo(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp, Instant::of($s1, $n1)->compareTo(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsEqualTo(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp === 0, Instant::of($s1, $n1)->isEqualTo(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsAfter(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp === 1, Instant::of($s1, $n1)->isAfter(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsAfterOrEqualTo(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp >= 0, Instant::of($s1, $n1)->isAfterOrEqualTo(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsBefore(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp === -1, Instant::of($s1, $n1)->isBefore(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $s1 The epoch second of the 1st instant.
|
||||
* @param int $n1 The nanosecond adjustment of the 1st instant.
|
||||
* @param int $s2 The epoch second of the 2nd instant.
|
||||
* @param int $n2 The nanosecond adjustment of the 2nd instant.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsBeforeOrEqualTo(int $s1, int $n1, int $s2, int $n2, int $cmp): void
|
||||
{
|
||||
self::assertSame($cmp <= 0, Instant::of($s1, $n1)->isBeforeOrEqualTo(Instant::of($s2, $n2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $testSecond The second of the test instant.
|
||||
* @param int $testNano The nanosecond adjustment to the test instant.
|
||||
* @param int $nowSecond The second of the current time.
|
||||
* @param int $nowNano The nanosecond adjustment to the current time.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsFuture(int $testSecond, int $testNano, int $nowSecond, int $nowNano, int $cmp): void
|
||||
{
|
||||
$clock = new FixedClock(Instant::of($nowSecond, $nowNano));
|
||||
self::assertSame($cmp === 1, Instant::of($testSecond, $testNano)->isFuture($clock));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $testSecond The second of the test instant.
|
||||
* @param int $testNano The nanosecond adjustment to the test instant.
|
||||
* @param int $nowSecond The second of the current time.
|
||||
* @param int $nowNano The nanosecond adjustment to the current time.
|
||||
* @param int $cmp The comparison value.
|
||||
*/
|
||||
#[DataProvider('providerCompareTo')]
|
||||
public function testIsPast(int $testSecond, int $testNano, int $nowSecond, int $nowNano, int $cmp): void
|
||||
{
|
||||
$clock = new FixedClock(Instant::of($nowSecond, $nowNano));
|
||||
self::assertSame($cmp === -1, Instant::of($testSecond, $testNano)->isPast($clock));
|
||||
}
|
||||
|
||||
public static function providerCompareTo(): array
|
||||
{
|
||||
return [
|
||||
[-1, -1, -1, -1, 0],
|
||||
[-1, -1, -1, 0, -1],
|
||||
[-1, -1, -1, 1, -1],
|
||||
[-1, -1, 0, -1, -1],
|
||||
[-1, -1, 0, 0, -1],
|
||||
[-1, -1, 0, 1, -1],
|
||||
[-1, -1, 1, -1, -1],
|
||||
[-1, -1, 1, 0, -1],
|
||||
[-1, -1, 1, 1, -1],
|
||||
[-1, 0, -1, -1, 1],
|
||||
[-1, 0, -1, 0, 0],
|
||||
[-1, 0, -1, 1, -1],
|
||||
[-1, 0, 0, -1, -1],
|
||||
[-1, 0, 0, 0, -1],
|
||||
[-1, 0, 0, 1, -1],
|
||||
[-1, 0, 1, -1, -1],
|
||||
[-1, 0, 1, 0, -1],
|
||||
[-1, 0, 1, 1, -1],
|
||||
[-1, 1, -1, -1, 1],
|
||||
[-1, 1, -1, 0, 1],
|
||||
[-1, 1, -1, 1, 0],
|
||||
[-1, 1, 0, -1, -1],
|
||||
[-1, 1, 0, 0, -1],
|
||||
[-1, 1, 0, 1, -1],
|
||||
[-1, 1, 1, -1, -1],
|
||||
[-1, 1, 1, 0, -1],
|
||||
[-1, 1, 1, 1, -1],
|
||||
[0, -1, -1, -1, 1],
|
||||
[0, -1, -1, 0, 1],
|
||||
[0, -1, -1, 1, 1],
|
||||
[0, -1, 0, -1, 0],
|
||||
[0, -1, 0, 0, -1],
|
||||
[0, -1, 0, 1, -1],
|
||||
[0, -1, 1, -1, -1],
|
||||
[0, -1, 1, 0, -1],
|
||||
[0, -1, 1, 1, -1],
|
||||
[0, 0, -1, -1, 1],
|
||||
[0, 0, -1, 0, 1],
|
||||
[0, 0, -1, 1, 1],
|
||||
[0, 0, 0, -1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, -1],
|
||||
[0, 0, 1, -1, -1],
|
||||
[0, 0, 1, 0, -1],
|
||||
[0, 0, 1, 1, -1],
|
||||
[0, 1, -1, -1, 1],
|
||||
[0, 1, -1, 0, 1],
|
||||
[0, 1, -1, 1, 1],
|
||||
[0, 1, 0, -1, 1],
|
||||
[0, 1, 0, 0, 1],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 1, -1, -1],
|
||||
[0, 1, 1, 0, -1],
|
||||
[0, 1, 1, 1, -1],
|
||||
[1, -1, -1, -1, 1],
|
||||
[1, -1, -1, 0, 1],
|
||||
[1, -1, -1, 1, 1],
|
||||
[1, -1, 0, -1, 1],
|
||||
[1, -1, 0, 0, 1],
|
||||
[1, -1, 0, 1, 1],
|
||||
[1, -1, 1, -1, 0],
|
||||
[1, -1, 1, 0, -1],
|
||||
[1, -1, 1, 1, -1],
|
||||
[1, 0, -1, -1, 1],
|
||||
[1, 0, -1, 0, 1],
|
||||
[1, 0, -1, 1, 1],
|
||||
[1, 0, 0, -1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 1, 1],
|
||||
[1, 0, 1, -1, 1],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 1, 1, -1],
|
||||
[1, 1, -1, -1, 1],
|
||||
[1, 1, -1, 0, 1],
|
||||
[1, 1, -1, 1, 1],
|
||||
[1, 1, 0, -1, 1],
|
||||
[1, 1, 0, 0, 1],
|
||||
[1, 1, 0, 1, 1],
|
||||
[1, 1, 1, -1, 1],
|
||||
[1, 1, 1, 0, 1],
|
||||
[1, 1, 1, 1, 0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $seconds The seconds value.
|
||||
* @param int $nanos The nano seconds value.
|
||||
* @param bool $isBetween Check the secs and nanos are between.
|
||||
*/
|
||||
#[DataProvider('providerIsBetweenInclusive')]
|
||||
public function testIsBetweenInclusive(int $seconds, int $nanos, bool $isBetween): void
|
||||
{
|
||||
self::assertSame($isBetween, Instant::of($seconds, $nanos)->isBetweenInclusive(
|
||||
Instant::of(-1, -1),
|
||||
Instant::of(1, 1),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $seconds The seconds value.
|
||||
* @param int $nanos The nano seconds value.
|
||||
* @param bool $isBetween Check the secs and nanos are between.
|
||||
*/
|
||||
#[DataProvider('providerIsBetweenExclusive')]
|
||||
public function testIsBetweenExclusive(int $seconds, int $nanos, bool $isBetween): void
|
||||
{
|
||||
self::assertSame($isBetween, Instant::of($seconds, $nanos)->isBetweenExclusive(
|
||||
Instant::of(-1, -1),
|
||||
Instant::of(1, 1),
|
||||
));
|
||||
}
|
||||
|
||||
public static function providerIsBetweenExclusive(): array
|
||||
{
|
||||
return [
|
||||
[-1, -2, false],
|
||||
[-1, -1, false],
|
||||
[-1, 0, true],
|
||||
[-1, 1, true],
|
||||
[0, -1, true],
|
||||
[0, 0, true],
|
||||
[0, 1, true],
|
||||
[1, -1, true],
|
||||
[1, 0, true],
|
||||
[1, 1, false],
|
||||
[1, 2, false],
|
||||
];
|
||||
}
|
||||
|
||||
public static function providerIsBetweenInclusive(): array
|
||||
{
|
||||
return [
|
||||
[-1, -2, false],
|
||||
[-1, -1, true],
|
||||
[-1, 0, true],
|
||||
[-1, 1, true],
|
||||
[0, -1, true],
|
||||
[0, 0, true],
|
||||
[0, 1, true],
|
||||
[1, -1, true],
|
||||
[1, 0, true],
|
||||
[1, 1, true],
|
||||
[1, 2, false],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('providerGetIntervalTo')]
|
||||
public function testGetIntervalTo(int $second1, int $nano1, int $second2, int $nano2, string $expectedInterval): void
|
||||
{
|
||||
$actualResult = Instant::of($second1, $nano1)->getIntervalTo(Instant::of($second2, $nano2));
|
||||
|
||||
self::assertSame($expectedInterval, (string) $actualResult);
|
||||
}
|
||||
|
||||
public static function providerGetIntervalTo(): array
|
||||
{
|
||||
return [
|
||||
[1672567200, 0, 1672567200, 0, '2023-01-01T10:00Z/2023-01-01T10:00Z'],
|
||||
[1672567200, 0, 1672567210, 0, '2023-01-01T10:00Z/2023-01-01T10:00:10Z'],
|
||||
[1672567200, 1000000, 1672567210, 2000000, '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
||||
[1672567200, 1, 1672567200, 9, '2023-01-01T10:00:00.000000001Z/2023-01-01T10:00:00.000000009Z'],
|
||||
[1672567200, 0, 1672653600, 0, '2023-01-01T10:00Z/2023-01-02T10:00Z'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $second The epoch second.
|
||||
* @param int $nano The nano adjustment.
|
||||
* @param string $expected The expected decimal output.
|
||||
*/
|
||||
#[DataProvider('providerToDecimal')]
|
||||
public function testToDecimal(int $second, int $nano, string $expected): void
|
||||
{
|
||||
self::assertSame($expected, Instant::of($second, $nano)->toDecimal());
|
||||
}
|
||||
|
||||
public static function providerToDecimal(): array
|
||||
{
|
||||
return [
|
||||
[123456789, 0, '123456789'],
|
||||
[123456789, 1, '123456789.000000001'],
|
||||
[123456789, 10, '123456789.00000001'],
|
||||
[123456789, 100, '123456789.0000001'],
|
||||
[123456789, 1000, '123456789.000001'],
|
||||
[123456789, 10000, '123456789.00001'],
|
||||
[123456789, 100000, '123456789.0001'],
|
||||
[123456789, 1000000, '123456789.001'],
|
||||
[123456789, 10000000, '123456789.01'],
|
||||
[123456789, 100000000, '123456789.1'],
|
||||
[123456789, 550000000, '123456789.55'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $epochSecond The epoch second to test.
|
||||
* @param int $nano The nano adjustment to the epoch second.
|
||||
* @param string $expectedString The expected string output.
|
||||
*/
|
||||
#[DataProvider('providerToString')]
|
||||
public function testJsonSerialize(int $epochSecond, int $nano, string $expectedString): void
|
||||
{
|
||||
self::assertSame(json_encode($expectedString, JSON_THROW_ON_ERROR), json_encode(Instant::of($epochSecond, $nano), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $epochSecond The epoch second to test.
|
||||
* @param int $nano The nano adjustment to the epoch second.
|
||||
* @param string $expectedString The expected string output.
|
||||
*/
|
||||
#[DataProvider('providerToString')]
|
||||
public function testToISOString(int $epochSecond, int $nano, string $expectedString): void
|
||||
{
|
||||
self::assertSame($expectedString, Instant::of($epochSecond, $nano)->toISOString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $epochSecond The epoch second to test.
|
||||
* @param int $nano The nano adjustment to the epoch second.
|
||||
* @param string $expectedString The expected string output.
|
||||
*/
|
||||
#[DataProvider('providerToString')]
|
||||
public function testToString(int $epochSecond, int $nano, string $expectedString): void
|
||||
{
|
||||
self::assertSame($expectedString, (string) Instant::of($epochSecond, $nano));
|
||||
}
|
||||
|
||||
public static function providerToString(): array
|
||||
{
|
||||
return [
|
||||
[-2000000000, 0, '1906-08-16T20:26:40Z'],
|
||||
[-1, 0, '1969-12-31T23:59:59Z'],
|
||||
[-1, 123, '1969-12-31T23:59:59.000000123Z'],
|
||||
[0, 0, '1970-01-01T00:00Z'],
|
||||
[0, 123456, '1970-01-01T00:00:00.000123456Z'],
|
||||
[1, 0, '1970-01-01T00:00:01Z'],
|
||||
[1, 123456789, '1970-01-01T00:00:01.123456789Z'],
|
||||
[2000000000, 0, '2033-05-18T03:33:20Z'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testAtTimeZone(): void
|
||||
{
|
||||
$timeZone = TimeZone::utc();
|
||||
$instant = Instant::of(1000000000);
|
||||
$result = $instant->atTimeZone($timeZone);
|
||||
self::assertSame(1000000000, $result->getInstant()->getEpochSecond());
|
||||
self::assertSame('2001-09-09T01:46:40', (string) $result->getDateTime());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\DateTime\Tests;
|
||||
|
||||
use Brick\DateTime\DateTimeException;
|
||||
use Brick\DateTime\Instant;
|
||||
use Brick\DateTime\Interval;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
|
||||
use function json_encode;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
/**
|
||||
* Unit tests for class Interval.
|
||||
*/
|
||||
class IntervalTest extends AbstractTestCase
|
||||
{
|
||||
public function testEndInstantIsNotBeforeStartInstant(): void
|
||||
{
|
||||
$start = Instant::of(2000000000, 987654321);
|
||||
$end = Instant::of(2000000009, 123456789);
|
||||
|
||||
$this->expectException(DateTimeException::class);
|
||||
$this->expectExceptionMessage('The end instant must not be before the start instant.');
|
||||
|
||||
Interval::of($end, $start);
|
||||
}
|
||||
|
||||
public function testGetStartEnd(): void
|
||||
{
|
||||
$start = Instant::of(2000000000, 987654321);
|
||||
$end = Instant::of(2000000009, 123456789);
|
||||
|
||||
$interval = Interval::of($start, $end);
|
||||
|
||||
self::assertInstantIs(2000000000, 987654321, $interval->getStart());
|
||||
self::assertInstantIs(2000000009, 123456789, $interval->getEnd());
|
||||
}
|
||||
|
||||
#[Depends('testGetStartEnd')]
|
||||
public function testWithStart(): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of(2000000000),
|
||||
Instant::of(2000000001),
|
||||
);
|
||||
|
||||
$newInterval = $interval->withStart(Instant::of(1999999999, 999999999));
|
||||
|
||||
self::assertNotSame($newInterval, $interval);
|
||||
|
||||
// ensure that the original isn't changed
|
||||
self::assertInstantIs(2000000000, 0, $interval->getStart());
|
||||
self::assertInstantIs(2000000001, 0, $interval->getEnd());
|
||||
|
||||
// test the new instance
|
||||
self::assertInstantIs(1999999999, 999999999, $newInterval->getStart());
|
||||
self::assertInstantIs(2000000001, 0, $newInterval->getEnd());
|
||||
}
|
||||
|
||||
#[Depends('testGetStartEnd')]
|
||||
public function testWithEnd(): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of(2000000000),
|
||||
Instant::of(2000000001),
|
||||
);
|
||||
|
||||
$newInterval = $interval->withEnd(Instant::of(2000000002, 222222222));
|
||||
|
||||
self::assertNotSame($newInterval, $interval);
|
||||
|
||||
// ensure that the original isn't changed
|
||||
self::assertInstantIs(2000000000, 0, $interval->getStart());
|
||||
self::assertInstantIs(2000000001, 0, $interval->getEnd());
|
||||
|
||||
// test the new instance
|
||||
self::assertInstantIs(2000000000, 0, $newInterval->getStart());
|
||||
self::assertInstantIs(2000000002, 222222222, $newInterval->getEnd());
|
||||
}
|
||||
|
||||
public function testGetDuration(): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of(1999999999, 555555),
|
||||
Instant::of(2000000001, 111),
|
||||
);
|
||||
|
||||
$duration = $interval->getDuration();
|
||||
|
||||
self::assertDurationIs(1, 999444556, $duration);
|
||||
}
|
||||
|
||||
#[DataProvider('providerContains')]
|
||||
public function testContains(int $start, int $end, int $now, bool $expected, string $errorMessage): void
|
||||
{
|
||||
$interval = Interval::of(Instant::of($start), Instant::of($end));
|
||||
|
||||
self::assertSame($expected, $interval->contains(Instant::of($now)), $errorMessage);
|
||||
}
|
||||
|
||||
public static function providerContains(): array
|
||||
{
|
||||
return [
|
||||
'at the start' => [
|
||||
1000000000,
|
||||
2000000000,
|
||||
1000000000,
|
||||
true,
|
||||
'an Interval must contain its start',
|
||||
],
|
||||
'at the end' => [
|
||||
1000000000,
|
||||
2000000000,
|
||||
2000000000,
|
||||
false,
|
||||
'an Interval must not contain its end',
|
||||
],
|
||||
'in the middle' => [
|
||||
1000000001,
|
||||
1000000003,
|
||||
1000000002,
|
||||
true,
|
||||
'an Interval must contain its intermediate values',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('providerIntersectsWith')]
|
||||
public function testIntersectsWith(int $start1, int $end1, int $start2, int $end2, bool $expected): void
|
||||
{
|
||||
$interval1 = Interval::of(Instant::of($start1), Instant::of($end1));
|
||||
$interval2 = Interval::of(Instant::of($start2), Instant::of($end2));
|
||||
self::assertSame($expected, $interval1->intersectsWith($interval2));
|
||||
}
|
||||
|
||||
public static function providerIntersectsWith(): array
|
||||
{
|
||||
return [
|
||||
'second is after first' => [
|
||||
100000, 200000,
|
||||
400000, 500000,
|
||||
false,
|
||||
],
|
||||
'second is before first' => [
|
||||
400000, 500000,
|
||||
100000, 200000,
|
||||
false,
|
||||
],
|
||||
'end of the first is start of the second' => [
|
||||
100000, 200000,
|
||||
200000, 300000,
|
||||
false,
|
||||
],
|
||||
'start of the first is end of the second' => [
|
||||
200000, 300000,
|
||||
100000, 200000,
|
||||
false,
|
||||
],
|
||||
'intersection' => [
|
||||
100000, 200000,
|
||||
150000, 250000,
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('providerGetIntersectionWith')]
|
||||
public function testGetIntersectionWith(
|
||||
int $start1,
|
||||
int $end1,
|
||||
int $start2,
|
||||
int $end2,
|
||||
int $expectedStart,
|
||||
int $expectedEnd,
|
||||
): void {
|
||||
$interval1 = Interval::of(Instant::of($start1), Instant::of($end1));
|
||||
$interval2 = Interval::of(Instant::of($start2), Instant::of($end2));
|
||||
$expected = Interval::of(Instant::of($expectedStart), Instant::of($expectedEnd));
|
||||
|
||||
self::assertTrue($expected->isEqualTo($interval1->getIntersectionWith($interval2)));
|
||||
}
|
||||
|
||||
public static function providerGetIntersectionWith(): array
|
||||
{
|
||||
return [
|
||||
'first before second' => [
|
||||
100000, 200000,
|
||||
150000, 250000,
|
||||
150000, 200000,
|
||||
],
|
||||
'first after second' => [
|
||||
150000, 250000,
|
||||
100000, 200000,
|
||||
150000, 200000,
|
||||
],
|
||||
'first inside second' => [
|
||||
200000, 300000,
|
||||
100000, 400000,
|
||||
200000, 300000,
|
||||
],
|
||||
'second inside first' => [
|
||||
100000, 400000,
|
||||
200000, 300000,
|
||||
200000, 300000,
|
||||
],
|
||||
'first = second' => [
|
||||
5000, 6000,
|
||||
5000, 6000,
|
||||
5000, 6000,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetIntersectionWithInvalidParams(): void
|
||||
{
|
||||
$interval1 = Interval::of(Instant::of(100000), Instant::of(200000));
|
||||
$interval2 = Interval::of(Instant::of(300000), Instant::of(400000));
|
||||
|
||||
$this->expectException(DateTimeException::class);
|
||||
$this->expectExceptionMessage('Intervals "1970-01-02T03:46:40Z/1970-01-03T07:33:20Z" and "1970-01-04T11:20Z/1970-01-05T15:06:40Z" do not intersect.');
|
||||
|
||||
$interval1->getIntersectionWith($interval2);
|
||||
}
|
||||
|
||||
#[DataProvider('providerIsEqualTo')]
|
||||
public function testIsEqualTo(Interval $a, Interval $b, bool $expectedResult): void
|
||||
{
|
||||
self::assertSame($expectedResult, $a->isEqualTo($b));
|
||||
self::assertSame($expectedResult, $b->isEqualTo($a));
|
||||
}
|
||||
|
||||
public static function providerIsEqualTo(): array
|
||||
{
|
||||
return [
|
||||
'start is not equal' => [
|
||||
Interval::of(Instant::of(100000), Instant::of(200000)),
|
||||
Interval::of(Instant::of(150000), Instant::of(200000)),
|
||||
false,
|
||||
],
|
||||
'end is not equal' => [
|
||||
Interval::of(Instant::of(100000), Instant::of(200000)),
|
||||
Interval::of(Instant::of(100000), Instant::of(250000)),
|
||||
false,
|
||||
],
|
||||
'both start and end are not equal' => [
|
||||
Interval::of(Instant::of(100000), Instant::of(200000)),
|
||||
Interval::of(Instant::of(150000), Instant::of(250000)),
|
||||
false,
|
||||
],
|
||||
'intervals are equal' => [
|
||||
Interval::of(Instant::of(100000), Instant::of(200000)),
|
||||
Interval::of(Instant::of(100000), Instant::of(200000)),
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('providerToString')]
|
||||
public function testJsonSerialize(int $epochSecondStart, int $epochSecondEnd, string $expectedString): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of($epochSecondStart),
|
||||
Instant::of($epochSecondEnd),
|
||||
);
|
||||
|
||||
self::assertSame(json_encode($expectedString, JSON_THROW_ON_ERROR), json_encode($interval, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
#[DataProvider('providerToString')]
|
||||
public function testToISOString(int $epochSecondStart, int $epochSecondEnd, string $expectedString): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of($epochSecondStart),
|
||||
Instant::of($epochSecondEnd),
|
||||
);
|
||||
|
||||
self::assertSame($expectedString, $interval->toISOString());
|
||||
}
|
||||
|
||||
#[DataProvider('providerToString')]
|
||||
public function testToString(int $epochSecondStart, int $epochSecondEnd, string $expectedString): void
|
||||
{
|
||||
$interval = Interval::of(
|
||||
Instant::of($epochSecondStart),
|
||||
Instant::of($epochSecondEnd),
|
||||
);
|
||||
|
||||
self::assertSame($expectedString, (string) $interval);
|
||||
}
|
||||
|
||||
public static function providerToString(): array
|
||||
{
|
||||
return [
|
||||
[1000000000, 1000000000, '2001-09-09T01:46:40Z/2001-09-09T01:46:40Z'],
|
||||
[1000000000, 2000000000, '2001-09-09T01:46:40Z/2033-05-18T03:33:20Z'],
|
||||
];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user