vendor/league/period/src/Period.php line 34

Open in your IDE?
  1. <?php
  2. /**
  3.  * League.Period (https://period.thephpleague.com)
  4.  *
  5.  * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace League\Period;
  12. use DateInterval;
  13. use DatePeriod;
  14. use DateTimeImmutable;
  15. use DateTimeInterface;
  16. use DateTimeZone;
  17. use JsonSerializable;
  18. use function array_filter;
  19. use function array_keys;
  20. use function implode;
  21. use function sprintf;
  22. /**
  23.  * A immutable value object class to manipulate Time interval.
  24.  *
  25.  * @package League.period
  26.  * @author  Ignace Nyamagana Butera <nyamsprod@gmail.com>
  27.  * @since   1.0.0
  28.  */
  29. final class Period implements JsonSerializable
  30. {
  31.     private const ISO8601_FORMAT 'Y-m-d\TH:i:s.u\Z';
  32.     private const BOUNDARY_TYPE = [
  33.         self::INCLUDE_START_EXCLUDE_END => 1,
  34.         self::INCLUDE_ALL => 1,
  35.         self::EXCLUDE_START_INCLUDE_END => 1,
  36.         self::EXCLUDE_ALL => 1,
  37.     ];
  38.     public const INCLUDE_START_EXCLUDE_END '[)';
  39.     public const EXCLUDE_START_INCLUDE_END '(]';
  40.     public const EXCLUDE_ALL '()';
  41.     public const INCLUDE_ALL '[]';
  42.     /** @var DateTimeImmutable */
  43.     private $startDate;
  44.     /** @var DateTimeImmutable */
  45.     private $endDate;
  46.     /** @var string */
  47.     private $boundaryType;
  48.     /**
  49.      * Creates a new instance.
  50.      *
  51.      * @param Datepoint|DateTimeInterface|int|string $startDate the starting datepoint
  52.      * @param Datepoint|DateTimeInterface|int|string $endDate   the ending datepoint
  53.      *
  54.      * @throws Exception If $startDate is greater than $endDate
  55.      */
  56.     public function __construct($startDate$endDatestring $boundaryType self::INCLUDE_START_EXCLUDE_END)
  57.     {
  58.         $startDate self::filterDatepoint($startDate);
  59.         $endDate self::filterDatepoint($endDate);
  60.         if ($startDate $endDate) {
  61.             throw new Exception('The ending datepoint must be greater or equal to the starting datepoint');
  62.         }
  63.         if (!isset(self::BOUNDARY_TYPE[$boundaryType])) {
  64.             throw new Exception(sprintf(
  65.                 'The boundary type `%s` is invalid. The only valid values are %s',
  66.                 $boundaryType,
  67.                 '`'.implode('`, `'array_keys(self::BOUNDARY_TYPE)).'`'
  68.             ));
  69.         }
  70.         $this->startDate $startDate;
  71.         $this->endDate $endDate;
  72.         $this->boundaryType $boundaryType;
  73.     }
  74.     /**
  75.      * Returns a DateTimeImmutable instance.
  76.      *
  77.      * @param Datepoint|DateTimeInterface|int|string $datepoint a Datepoint
  78.      */
  79.     private static function filterDatepoint($datepoint): DateTimeImmutable
  80.     {
  81.         if ($datepoint instanceof DateTimeImmutable) {
  82.             return $datepoint;
  83.         }
  84.         if ($datepoint instanceof DateTimeInterface) {
  85.             return new DateTimeImmutable($datepoint->format('Y-m-d H:i:s.u'), $datepoint->getTimezone());
  86.         }
  87.         $timestamp $datepoint;
  88.         if (is_int($timestamp) || false !== ($timestamp filter_var($datepointFILTER_VALIDATE_INT))) {
  89.             return new DateTimeImmutable('@'.$timestamp);
  90.         }
  91.         return new DateTimeImmutable($datepoint);
  92.     }
  93.     /**
  94.      * Returns a DateInterval instance.
  95.      *
  96.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  97.      */
  98.     private static function filterDuration($duration): DateInterval
  99.     {
  100.         if ($duration instanceof DateInterval) {
  101.             return $duration;
  102.         }
  103.         if ($duration instanceof self) {
  104.             return $duration->dateInterval();
  105.         }
  106.         return Duration::create($duration);
  107.     }
  108.     /**************************************************
  109.      * Named constructors
  110.      **************************************************/
  111.     /**
  112.      * @inheritDoc
  113.      */
  114.     public static function __set_state(array $interval)
  115.     {
  116.         return new self(
  117.             $interval['startDate'],
  118.             $interval['endDate'],
  119.             $interval['boundaryType'] ?? self::INCLUDE_START_EXCLUDE_END
  120.         );
  121.     }
  122.     /**
  123.      * Creates new instance from a starting date endpoint and a duration.
  124.      *
  125.      * @param Datepoint|DateTimeInterface|int|string $startDate the starting date endpoint
  126.      * @param DateInterval|Duration|string|int       $duration  a Duration
  127.      */
  128.     public static function after($startDate$durationstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  129.     {
  130.         $startDate self::filterDatepoint($startDate);
  131.         return new self($startDate$startDate->add(self::filterDuration($duration)), $boundaryType);
  132.     }
  133.     /**
  134.      * Creates new instance from a ending date endpoint and a duration.
  135.      *
  136.      * @param Datepoint|DateTimeInterface|int|string $endDate  the ending date endpoint
  137.      * @param DateInterval|Duration|string|int       $duration a Duration
  138.      */
  139.     public static function before($endDate$durationstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  140.     {
  141.         $endDate self::filterDatepoint($endDate);
  142.         return new self($endDate->sub(self::filterDuration($duration)), $endDate$boundaryType);
  143.     }
  144.     /**
  145.      * Creates new instance where the given duration is simultaneously
  146.      * subtracted from and added to the date endpoint.
  147.      *
  148.      * @param Datepoint|DateTimeInterface|int|string $datepoint a Date endpoint
  149.      * @param DateInterval|Duration|string|int       $duration  a Duration
  150.      */
  151.     public static function around($datepoint$durationstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  152.     {
  153.         $datepoint self::filterDatepoint($datepoint);
  154.         $duration self::filterDuration($duration);
  155.         return new self($datepoint->sub($duration), $datepoint->add($duration), $boundaryType);
  156.     }
  157.     /**
  158.      * Creates new instance from a DatePeriod.
  159.      */
  160.     public static function fromDatePeriod(DatePeriod $datePeriodstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  161.     {
  162.         return new self($datePeriod->getStartDate(), $datePeriod->getEndDate(), $boundaryType);
  163.     }
  164.     /**
  165.      * Creates new instance for a specific year.
  166.      */
  167.     public static function fromYear(int $yearstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  168.     {
  169.         $startDate = (new DateTimeImmutable())->setDate($year11)->setTime(00);
  170.         return new self($startDate$startDate->add(new DateInterval('P1Y')), $boundaryType);
  171.     }
  172.     /**
  173.      * Creates new instance for a specific ISO year.
  174.      */
  175.     public static function fromIsoYear(int $yearstring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  176.     {
  177.         return new self(
  178.             (new DateTimeImmutable())->setISODate($year1)->setTime(00),
  179.             (new DateTimeImmutable())->setISODate(++$year1)->setTime(00),
  180.             $boundaryType
  181.         );
  182.     }
  183.     /**
  184.      * Creates new instance for a specific year and semester.
  185.      */
  186.     public static function fromSemester(int $yearint $semester 1string $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  187.     {
  188.         $month = (($semester 1) * 6) + 1;
  189.         $startDate = (new DateTimeImmutable())->setDate($year$month1)->setTime(00);
  190.         return new self($startDate$startDate->add(new DateInterval('P6M')), $boundaryType);
  191.     }
  192.     /**
  193.      * Creates new instance for a specific year and quarter.
  194.      */
  195.     public static function fromQuarter(int $yearint $quarter 1string $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  196.     {
  197.         $month = (($quarter 1) * 3) + 1;
  198.         $startDate = (new DateTimeImmutable())->setDate($year$month1)->setTime(00);
  199.         return new self($startDate$startDate->add(new DateInterval('P3M')), $boundaryType);
  200.     }
  201.     /**
  202.      * Creates new instance for a specific year and month.
  203.      */
  204.     public static function fromMonth(int $yearint $month 1string $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  205.     {
  206.         $startDate = (new DateTimeImmutable())->setDate($year$month1)->setTime(00);
  207.         return new self($startDate$startDate->add(new DateInterval('P1M')), $boundaryType);
  208.     }
  209.     /**
  210.      * Creates new instance for a specific ISO8601 week.
  211.      */
  212.     public static function fromIsoWeek(int $yearint $week 1string $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  213.     {
  214.         $startDate = (new DateTimeImmutable())->setISODate($year$week1)->setTime(00);
  215.         return new self($startDate$startDate->add(new DateInterval('P7D')), $boundaryType);
  216.     }
  217.     /**
  218.      * Creates new instance for a specific year, month and day.
  219.      */
  220.     public static function fromDay(int $yearint $month 1int $day 1string $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  221.     {
  222.         $startDate = (new DateTimeImmutable())->setDate($year$month$day)->setTime(00);
  223.         return new self($startDate$startDate->add(new DateInterval('P1D')), $boundaryType);
  224.     }
  225.     /**
  226.      * Creates new instance for Datepoint.
  227.      */
  228.     public static function fromDatepoint(DateTimeInterface $startDateDateTimeInterface $endDatestring $boundaryType self::INCLUDE_START_EXCLUDE_END): self
  229.     {
  230.         return new self($startDate$endDate$boundaryType);
  231.     }
  232.     /**************************************************
  233.      * Basic getters
  234.      **************************************************/
  235.     /**
  236.      * Returns the starting date endpoint.
  237.      */
  238.     public function getStartDate(): DateTimeImmutable
  239.     {
  240.         return $this->startDate;
  241.     }
  242.     /**
  243.      * Returns the ending date endpoint.
  244.      */
  245.     public function getEndDate(): DateTimeImmutable
  246.     {
  247.         return $this->endDate;
  248.     }
  249.     /**
  250.      * Returns the instance boundary type.
  251.      */
  252.     public function getBoundaryType(): string
  253.     {
  254.         return $this->boundaryType;
  255.     }
  256.     /**
  257.      * Returns the instance duration as expressed in seconds.
  258.      */
  259.     public function timeDuration(): float
  260.     {
  261.         return $this->endDate->getTimestamp() - $this->startDate->getTimestamp();
  262.     }
  263.     /**
  264.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  265.      *
  266.      * @deprecated 4.12.0 This method will be removed in the next major point release
  267.      * @see Period::timeDuration()
  268.      *
  269.      * Returns the instance duration as expressed in seconds.
  270.      */
  271.     public function getTimestampInterval(): float
  272.     {
  273.         return $this->timeDuration();
  274.     }
  275.     /**
  276.      * Returns the instance duration as a DateInterval object.
  277.      */
  278.     public function dateInterval(): DateInterval
  279.     {
  280.         return $this->startDate->diff($this->endDate);
  281.     }
  282.     /**
  283.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  284.      *
  285.      * @deprecated 4.12.0 This method will be removed in the next major point release
  286.      * @see Period::dateInterval()
  287.      *
  288.      * Returns the instance duration as a DateInterval object.
  289.      */
  290.     public function getDateInterval(): DateInterval
  291.     {
  292.         return $this->dateInterval();
  293.     }
  294.     /**************************************************
  295.      * String representation
  296.      **************************************************/
  297.     /**
  298.      * Returns the string representation as a ISO8601 interval format.
  299.      *
  300.      * @deprecated 4.10.0 This method will be removed in the next major point release
  301.      * @see ::toIso8601()
  302.      */
  303.     public function __toString()
  304.     {
  305.         return $this->toIso8601();
  306.     }
  307.     /**
  308.      * Returns the string representation as a ISO8601 interval format.
  309.      *
  310.      * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
  311.      * @param ?string $format
  312.      */
  313.     public function toIso8601(?string $format null): string
  314.     {
  315.         $utc = new DateTimeZone('UTC');
  316.         $format $format ?? self::ISO8601_FORMAT;
  317.         $startDate $this->startDate->setTimezone($utc)->format($format);
  318.         $endDate $this->endDate->setTimezone($utc)->format($format);
  319.         return $startDate.'/'.$endDate;
  320.     }
  321.     /**
  322.      * Returns the JSON representation of an instance.
  323.      *
  324.      * Based on the JSON representation of dates as
  325.      * returned by Javascript Date.toJSON() method.
  326.      *
  327.      * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON
  328.      * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
  329.      *
  330.      * @return array<string>
  331.      */
  332.     public function jsonSerialize(): array
  333.     {
  334.         [$startDate$endDate] = explode('/'$this->toIso8601(), 2);
  335.         return ['startDate' => $startDate'endDate' => $endDate];
  336.     }
  337.     /**
  338.      * Returns the mathematical representation of an instance as a left close, right open interval.
  339.      *
  340.      * @see https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals
  341.      * @see https://php.net/manual/en/function.date.php
  342.      * @see https://www.postgresql.org/docs/9.3/static/rangetypes.html
  343.      *
  344.      * @param string $format the format of the outputted date string
  345.      */
  346.     public function toIso80000(string $format): string
  347.     {
  348.         return $this->boundaryType[0]
  349.             .$this->startDate->format($format)
  350.             .', '
  351.             .$this->endDate->format($format)
  352.             .$this->boundaryType[1];
  353.     }
  354.     /**
  355.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  356.      *
  357.      * @deprecated 4.12.0 This method will be removed in the next major point release
  358.      * @see Period::toIso80000()
  359.      *
  360.      * @param string $format the format of the outputted date string
  361.      *
  362.      * Returns the mathematical representation of an instance as a left close, right open interval.
  363.      *
  364.      * @see https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals
  365.      * @see https://php.net/manual/en/function.date.php
  366.      * @see https://www.postgresql.org/docs/9.3/static/rangetypes.html
  367.      */
  368.     public function format(string $format): string
  369.     {
  370.         return $this->toIso80000($format);
  371.     }
  372.     /**************************************************
  373.      * Boundary related methods
  374.      **************************************************/
  375.     /**
  376.      * Tells whether the start datepoint is included in the boundary.
  377.      */
  378.     public function isStartIncluded(): bool
  379.     {
  380.         return '[' === $this->boundaryType[0];
  381.     }
  382.     /**
  383.      * Tells whether the start datepoint is excluded from the boundary.
  384.      */
  385.     public function isStartExcluded(): bool
  386.     {
  387.         return '(' === $this->boundaryType[0];
  388.     }
  389.     /**
  390.      * Tells whether the end datepoint is included in the boundary.
  391.      */
  392.     public function isEndIncluded(): bool
  393.     {
  394.         return ']' === $this->boundaryType[1];
  395.     }
  396.     /**
  397.      * Tells whether the end datepoint is excluded from the boundary.
  398.      */
  399.     public function isEndExcluded(): bool
  400.     {
  401.         return ')' === $this->boundaryType[1];
  402.     }
  403.     /**************************************************
  404.      * Duration comparison methods
  405.      **************************************************/
  406.     /**
  407.      * Compares two instances according to their duration.
  408.      *
  409.      * Returns:
  410.      * <ul>
  411.      * <li> -1 if the current Interval is lesser than the submitted Interval object</li>
  412.      * <li>  1 if the current Interval is greater than the submitted Interval object</li>
  413.      * <li>  0 if both Interval objects have the same duration</li>
  414.      * </ul>
  415.      */
  416.     public function durationCompare(self $interval): int
  417.     {
  418.         return $this->startDate->add($this->dateInterval())
  419.             <=> $this->startDate->add($interval->dateInterval());
  420.     }
  421.     /**
  422.      * Tells whether the current instance duration is equal to the submitted one.
  423.      */
  424.     public function durationEquals(self $interval): bool
  425.     {
  426.         return === $this->durationCompare($interval);
  427.     }
  428.     /**
  429.      * Tells whether the current instance duration is greater than the submitted one.
  430.      */
  431.     public function durationGreaterThan(self $interval): bool
  432.     {
  433.         return === $this->durationCompare($interval);
  434.     }
  435.     /**
  436.      * Tells whether the current instance duration is less than the submitted one.
  437.      */
  438.     public function durationLessThan(self $interval): bool
  439.     {
  440.         return -=== $this->durationCompare($interval);
  441.     }
  442.     /**************************************************
  443.      * Relation methods
  444.      **************************************************/
  445.     /**
  446.      * Tells whether an instance is entirely before the specified index.
  447.      *
  448.      * The index can be a DateTimeInterface object or another Period object.
  449.      *
  450.      * [--------------------)
  451.      *                          [--------------------)
  452.      *
  453.      * @param Period|Datepoint|\DateTimeInterface|int|string $index a datepoint or a Period object
  454.      */
  455.     public function isBefore($index): bool
  456.     {
  457.         if ($index instanceof self) {
  458.             return $this->endDate $index->startDate
  459.                 || ($this->endDate == $index->startDate && $this->boundaryType[1] !== $index->boundaryType[0]);
  460.         }
  461.         $datepoint self::filterDatepoint($index);
  462.         return $this->endDate $datepoint
  463.             || ($this->endDate == $datepoint && ')' === $this->boundaryType[1]);
  464.     }
  465.     /**
  466.      * Tells whether the current instance end date meets the interval start date.
  467.      *
  468.      * [--------------------)
  469.      *                      [--------------------)
  470.      */
  471.     public function bordersOnStart(self $interval): bool
  472.     {
  473.         return $this->endDate == $interval->startDate
  474.             && '][' !== $this->boundaryType[1].$interval->boundaryType[0];
  475.     }
  476.     /**
  477.      * Tells whether two intervals share the same start datepoint
  478.      * and the same starting boundary type.
  479.      *
  480.      *    [----------)
  481.      *    [--------------------)
  482.      *
  483.      * or
  484.      *
  485.      *    [--------------------)
  486.      *    [---------)
  487.      *
  488.      * @param Period|Datepoint|\DateTimeInterface|int|string $index a datepoint or a Period object
  489.      */
  490.     public function isStartedBy($index): bool
  491.     {
  492.         if ($index instanceof self) {
  493.             return $this->startDate == $index->startDate
  494.                 && $this->boundaryType[0] === $index->boundaryType[0];
  495.         }
  496.         $index self::filterDatepoint($index);
  497.         return $index == $this->startDate && '[' === $this->boundaryType[0];
  498.     }
  499.     /**
  500.      * Tells whether an instance is fully contained in the specified interval.
  501.      *
  502.      *     [----------)
  503.      * [--------------------)
  504.      */
  505.     public function isDuring(self $interval): bool
  506.     {
  507.         return $interval->containsInterval($this);
  508.     }
  509.     /**
  510.      * Tells whether an instance fully contains the specified index.
  511.      *
  512.      * The index can be a DateTimeInterface object or another Period object.
  513.      *
  514.      * @param Period|Datepoint|\DateTimeInterface|int|string $index a datepoint or a Period object
  515.      */
  516.     public function contains($index): bool
  517.     {
  518.         if ($index instanceof self) {
  519.             return $this->containsInterval($index);
  520.         }
  521.         return $this->containsDatepoint(self::filterDatepoint($index), $this->boundaryType);
  522.     }
  523.     /**
  524.      * Tells whether an instance fully contains another instance.
  525.      *
  526.      * [--------------------)
  527.      *     [----------)
  528.      */
  529.     private function containsInterval(self $interval): bool
  530.     {
  531.         if ($this->startDate $interval->startDate && $this->endDate $interval->endDate) {
  532.             return true;
  533.         }
  534.         if ($this->startDate == $interval->startDate && $this->endDate == $interval->endDate) {
  535.             return $this->boundaryType === $interval->boundaryType || '[]' === $this->boundaryType;
  536.         }
  537.         if ($this->startDate == $interval->startDate) {
  538.             return ($this->boundaryType[0] === $interval->boundaryType[0] || '[' === $this->boundaryType[0])
  539.                 && $this->containsDatepoint($this->startDate->add($interval->getDateInterval()), $this->boundaryType);
  540.         }
  541.         if ($this->endDate == $interval->endDate) {
  542.             return ($this->boundaryType[1] === $interval->boundaryType[1] || ']' === $this->boundaryType[1])
  543.                 && $this->containsDatepoint($this->endDate->sub($interval->getDateInterval()), $this->boundaryType);
  544.         }
  545.         return false;
  546.     }
  547.     /**
  548.      * Tells whether an instance contains a date endpoint.
  549.      *
  550.      * [------|------------)
  551.      */
  552.     private function containsDatepoint(DateTimeInterface $datepointstring $boundaryType): bool
  553.     {
  554.         switch ($boundaryType) {
  555.             case self::EXCLUDE_ALL:
  556.                 return $datepoint $this->startDate && $datepoint $this->endDate;
  557.             case self::INCLUDE_ALL:
  558.                 return $datepoint >= $this->startDate && $datepoint <= $this->endDate;
  559.             case self::EXCLUDE_START_INCLUDE_END:
  560.                 return $datepoint $this->startDate && $datepoint <= $this->endDate;
  561.             case self::INCLUDE_START_EXCLUDE_END:
  562.             default:
  563.                 return $datepoint >= $this->startDate && $datepoint $this->endDate;
  564.         }
  565.     }
  566.     /**
  567.      * Tells whether two intervals share the same datepoints.
  568.      *
  569.      * [--------------------)
  570.      * [--------------------)
  571.      */
  572.     public function equals(self $interval): bool
  573.     {
  574.         return $this->startDate == $interval->startDate
  575.             && $this->endDate == $interval->endDate
  576.             && $this->boundaryType === $interval->boundaryType;
  577.     }
  578.     /**
  579.      * Tells whether two intervals share the same end datepoint
  580.      * and the same ending boundary type.
  581.      *
  582.      *              [----------)
  583.      *    [--------------------)
  584.      *
  585.      * or
  586.      *
  587.      *    [--------------------)
  588.      *               [---------)
  589.      *
  590.      * @param Period|Datepoint|\DateTimeInterface|int|string $index a datepoint or a Period object
  591.      */
  592.     public function isEndedBy($index): bool
  593.     {
  594.         if ($index instanceof self) {
  595.             return $this->endDate == $index->endDate
  596.                 && $this->boundaryType[1] === $index->boundaryType[1];
  597.         }
  598.         $index self::filterDatepoint($index);
  599.         return $index == $this->endDate && ']' === $this->boundaryType[1];
  600.     }
  601.     /**
  602.      * Tells whether the current instance start date meets the interval end date.
  603.      *
  604.      *                      [--------------------)
  605.      * [--------------------)
  606.      */
  607.     public function bordersOnEnd(self $interval): bool
  608.     {
  609.         return $interval->bordersOnStart($this);
  610.     }
  611.     /**
  612.      * Tells whether an interval is entirely after the specified index.
  613.      * The index can be a DateTimeInterface object or another Period object.
  614.      *
  615.      *                          [--------------------)
  616.      * [--------------------)
  617.      *
  618.      * @param Period|Datepoint|\DateTimeInterface|int|string $index a datepoint or a Period object
  619.      */
  620.     public function isAfter($index): bool
  621.     {
  622.         if ($index instanceof self) {
  623.             return $index->isBefore($this);
  624.         }
  625.         $datepoint self::filterDatepoint($index);
  626.         return $this->startDate $datepoint
  627.             || ($this->startDate == $datepoint && '(' === $this->boundaryType[0]);
  628.     }
  629.     /**
  630.      * Tells whether two intervals abuts.
  631.      *
  632.      * [--------------------)
  633.      *                      [--------------------)
  634.      * or
  635.      *                      [--------------------)
  636.      * [--------------------)
  637.      */
  638.     public function abuts(self $interval): bool
  639.     {
  640.         return $this->bordersOnStart($interval) || $this->bordersOnEnd($interval);
  641.     }
  642.     /**
  643.      * Tells whether two intervals overlaps.
  644.      *
  645.      * [--------------------)
  646.      *          [--------------------)
  647.      */
  648.     public function overlaps(self $interval): bool
  649.     {
  650.         return !$this->abuts($interval)
  651.             && $this->startDate $interval->endDate
  652.             && $this->endDate $interval->startDate;
  653.     }
  654.     /**************************************************
  655.      * Manipulating instance duration
  656.      **************************************************/
  657.     /**
  658.      * Returns the difference between two instances expressed in seconds.
  659.      */
  660.     public function timeDurationDiff(self $interval): float
  661.     {
  662.         return $this->timeDuration() - $interval->timeDuration();
  663.     }
  664.     /**
  665.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  666.      *
  667.      * @deprecated 4.12.0 This method will be removed in the next major point release
  668.      * @see Period::timeDurationDiff()
  669.      *
  670.      * Returns the difference between two instances expressed in seconds.
  671.      */
  672.     public function timestampIntervalDiff(self $interval): float
  673.     {
  674.         return $this->timeDurationDiff($interval);
  675.     }
  676.     /**
  677.      * Returns the difference between two instances expressed with a DateInterval object.
  678.      */
  679.     public function dateIntervalDiff(self $interval): DateInterval
  680.     {
  681.         return $this->endDate->diff($this->startDate->add($interval->dateInterval()));
  682.     }
  683.     /**
  684.      * Allows iteration over a set of dates and times,
  685.      * recurring at regular intervals, over the instance.
  686.      *
  687.      * @see http://php.net/manual/en/dateperiod.construct.php
  688.      *
  689.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  690.      */
  691.     public function dateRangeForward($durationint $option 0): DatePeriod
  692.     {
  693.         return new DatePeriod($this->startDateself::filterDuration($duration), $this->endDate$option);
  694.     }
  695.     /**
  696.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  697.      *
  698.      * @deprecated 4.12.0 This method will be removed in the next major point release
  699.      * @see Period::dateRangeForward()
  700.      *
  701.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  702.      *
  703.      * Allows iteration over a set of dates and times,
  704.      * recurring at regular intervals, over the instance.
  705.      *
  706.      * @see http://php.net/manual/en/dateperiod.construct.php
  707.      */
  708.     public function getDatePeriod($durationint $option 0): DatePeriod
  709.     {
  710.         return $this->dateRangeForward($duration$option);
  711.     }
  712.     /**
  713.      * Allows iteration over a set of dates and times,
  714.      * recurring at regular intervals, over the instance backwards starting from
  715.      * the instance ending date endpoint.
  716.      *
  717.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  718.      */
  719.     public function dateRangeBackwards($durationint $option 0): iterable
  720.     {
  721.         $duration self::filterDuration($duration);
  722.         $date $this->endDate;
  723.         if ((bool) ($option DatePeriod::EXCLUDE_START_DATE)) {
  724.             $date $this->endDate->sub($duration);
  725.         }
  726.         while ($date $this->startDate) {
  727.             yield $date;
  728.             $date $date->sub($duration);
  729.         }
  730.     }
  731.     /**
  732.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  733.      *
  734.      * @deprecated 4.12.0 This method will be removed in the next major point release
  735.      * @see Period::dateRangeBackwards()
  736.      *
  737.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  738.      *
  739.      * Allows iteration over a set of dates and times,
  740.      * recurring at regular intervals, over the instance backwards starting from
  741.      * the instance ending date endpoint.
  742.      */
  743.     public function getDatePeriodBackwards($durationint $option 0): iterable
  744.     {
  745.         return $this->dateRangeBackwards($duration$option);
  746.     }
  747.     /**
  748.      * Allows splitting an instance in smaller Period objects according to a given interval.
  749.      *
  750.      * The returned iterable Interval set is ordered so that:
  751.      * <ul>
  752.      * <li>The first returned object MUST share the starting datepoint of the parent object.</li>
  753.      * <li>The last returned object MUST share the ending datepoint of the parent object.</li>
  754.      * <li>The last returned object MUST have a duration equal or lesser than the submitted interval.</li>
  755.      * <li>All returned objects except for the first one MUST start immediately after the previously returned object</li>
  756.      * </ul>
  757.      *
  758.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  759.      *
  760.      * @return iterable<Period>
  761.      */
  762.     public function splitForward($duration): iterable
  763.     {
  764.         $duration self::filterDuration($duration);
  765.         /** @var DateTimeImmutable $startDate */
  766.         foreach ($this->dateRangeForward($duration) as $startDate) {
  767.             $endDate $startDate->add($duration);
  768.             if ($endDate $this->endDate) {
  769.                 $endDate $this->endDate;
  770.             }
  771.             yield new self($startDate$endDate$this->boundaryType);
  772.         }
  773.     }
  774.     /**
  775.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  776.      *
  777.      * @see Period::splitForward()
  778.      *
  779.      * Allows splitting an instance in smaller Period objects according to a given interval.
  780.      *
  781.      * The returned iterable Interval set is ordered so that:
  782.      * <ul>
  783.      * <li>The first returned object MUST share the starting datepoint of the parent object.</li>
  784.      * <li>The last returned object MUST share the ending datepoint of the parent object.</li>
  785.      * <li>The last returned object MUST have a duration equal or lesser than the submitted interval.</li>
  786.      * <li>All returned objects except for the first one MUST start immediately after the previously returned object</li>
  787.      * </ul>
  788.      *
  789.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  790.      *
  791.      * @return iterable<Period>
  792.      */
  793.     public function split($duration): iterable
  794.     {
  795.         return $this->splitForward($duration);
  796.     }
  797.     /**
  798.      * Allows splitting an instance in smaller Period objects according to a given interval.
  799.      *
  800.      * The returned iterable Period set is ordered so that:
  801.      * <ul>
  802.      * <li>The first returned object MUST share the ending datepoint of the parent object.</li>
  803.      * <li>The last returned object MUST share the starting datepoint of the parent object.</li>
  804.      * <li>The last returned object MUST have a duration equal or lesser than the submitted interval.</li>
  805.      * <li>All returned objects except for the first one MUST end immediately before the previously returned object</li>
  806.      * </ul>
  807.      *
  808.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  809.      *
  810.      * @return iterable<Period>
  811.      */
  812.     public function splitBackwards($duration): iterable
  813.     {
  814.         $endDate $this->endDate;
  815.         $duration self::filterDuration($duration);
  816.         do {
  817.             $startDate $endDate->sub($duration);
  818.             if ($startDate $this->startDate) {
  819.                 $startDate $this->startDate;
  820.             }
  821.             yield new self($startDate$endDate$this->boundaryType);
  822.             $endDate $startDate;
  823.         } while ($endDate $this->startDate);
  824.     }
  825.     /**************************************************
  826.      * Manipulation instance endpoints and bounds
  827.      **************************************************/
  828.     /**
  829.      * Returns the computed intersection between two instances as a new instance.
  830.      *
  831.      * [--------------------)
  832.      *          âˆ©
  833.      *                 [----------)
  834.      *          =
  835.      *                 [----)
  836.      *
  837.      * @throws Exception If both objects do not overlaps
  838.      */
  839.     public function intersect(self $interval): self
  840.     {
  841.         if (!$this->overlaps($interval)) {
  842.             throw new Exception('Both '.self::class.' objects should overlaps');
  843.         }
  844.         $startDate $this->startDate;
  845.         $endDate $this->endDate;
  846.         $boundaryType $this->boundaryType;
  847.         if ($interval->startDate $this->startDate) {
  848.             $boundaryType[0] = $interval->boundaryType[0];
  849.             $startDate $interval->startDate;
  850.         }
  851.         if ($interval->endDate $this->endDate) {
  852.             $boundaryType[1] = $interval->boundaryType[1];
  853.             $endDate $interval->endDate;
  854.         }
  855.         $intersect = new self($startDate$endDate$boundaryType);
  856.         if ($intersect->equals($this)) {
  857.             return $this;
  858.         }
  859.         return $intersect;
  860.     }
  861.     /**
  862.      * Returns the computed difference between two overlapping instances as
  863.      * an array containing Period objects or the null value.
  864.      *
  865.      * The array will always contains 2 elements:
  866.      *
  867.      * <ul>
  868.      * <li>an NULL filled array if both objects have the same datepoints</li>
  869.      * <li>one Period object and NULL if both objects share one datepoint</li>
  870.      * <li>two Period objects if both objects share no datepoint</li>
  871.      * </ul>
  872.      *
  873.      * [--------------------)
  874.      *          \
  875.      *                [-----------)
  876.      *          =
  877.      * [--------------)  +  [-----)
  878.      *
  879.      * @return array<null|Period>
  880.      */
  881.     public function diff(self $interval): array
  882.     {
  883.         if ($interval->equals($this)) {
  884.             return [nullnull];
  885.         }
  886.         $intersect $this->intersect($interval);
  887.         $merge $this->merge($interval);
  888.         if ($merge->startDate == $intersect->startDate) {
  889.             $first ')' === $intersect->boundaryType[1] ? '[' '(';
  890.             $boundary $first.$merge->boundaryType[1];
  891.             return [$merge->startingOn($intersect->endDate)->boundedBy($boundary), null];
  892.         }
  893.         if ($merge->endDate == $intersect->endDate) {
  894.             $last '(' === $intersect->boundaryType[0] ? ']' ')';
  895.             $boundary $merge->boundaryType[0].$last;
  896.             return [$merge->endingOn($intersect->startDate)->boundedBy($boundary), null];
  897.         }
  898.         $last '(' === $intersect->boundaryType[0] ? ']' ')';
  899.         $lastBoundary $merge->boundaryType[0].$last;
  900.         $first ')' === $intersect->boundaryType[1] ? '[' '(';
  901.         $firstBoundary $first.$merge->boundaryType[1];
  902.         return [
  903.             $merge->endingOn($intersect->startDate)->boundedBy($lastBoundary),
  904.             $merge->startingOn($intersect->endDate)->boundedBy($firstBoundary),
  905.         ];
  906.     }
  907.     /**
  908.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  909.      *
  910.      * @deprecated 4.9.0 This method will be removed in the next major point release
  911.      * @see Period::subtract
  912.      */
  913.     public function substract(self $interval): Sequence
  914.     {
  915.         return $this->subtract($interval);
  916.     }
  917.     /**
  918.      * Returns the difference set operation between two intervals as a Sequence.
  919.      * The Sequence can contain from 0 to 2 Periods depending on the result of
  920.      * the operation.
  921.      *
  922.      * [--------------------)
  923.      *          -
  924.      *                [-----------)
  925.      *          =
  926.      * [--------------)
  927.      */
  928.     public function subtract(self $interval): Sequence
  929.     {
  930.         if (!$this->overlaps($interval)) {
  931.             return new Sequence($this);
  932.         }
  933.         $filter = function ($item): bool {
  934.             return null !== $item && $this->overlaps($item);
  935.         };
  936.         return new Sequence(...array_filter($this->diff($interval), $filter));
  937.     }
  938.     /**
  939.      * Returns the computed gap between two instances as a new instance.
  940.      *
  941.      * [--------------------)
  942.      *          +
  943.      *                          [----------)
  944.      *          =
  945.      *                      [---)
  946.      *
  947.      * @throws Exception If both instance overlaps
  948.      */
  949.     public function gap(self $interval): self
  950.     {
  951.         if ($this->overlaps($interval)) {
  952.             throw new Exception('Both '.self::class.' objects must not overlaps');
  953.         }
  954.         $bounds $this->isEndIncluded() ? '(' '[';
  955.         $bounds .= $interval->isStartIncluded() ? ')' ']';
  956.         if ($interval->startDate $this->startDate) {
  957.             return new self($this->endDate$interval->startDate$bounds);
  958.         }
  959.         return new self($interval->endDate$this->startDate$this->boundaryType);
  960.     }
  961.     /**
  962.      * Merges one or more instances to return a new instance.
  963.      * The resulting instance represents the largest duration possible.
  964.      *
  965.      * This method MUST retain the state of the current instance, and return
  966.      * an instance that contains the specified new datepoints.
  967.      *
  968.      * [--------------------)
  969.      *          +
  970.      *                 [----------)
  971.      *          =
  972.      * [--------------------------)
  973.      *
  974.      *
  975.      * @param Period ...$intervals
  976.      */
  977.     public function merge(self ...$intervals): self
  978.     {
  979.         $carry $this;
  980.         foreach ($intervals as $period) {
  981.             if ($carry->startDate $period->startDate) {
  982.                 $carry = new self(
  983.                     $period->startDate,
  984.                     $carry->endDate,
  985.                     $period->boundaryType[0].$carry->boundaryType[1]
  986.                 );
  987.             }
  988.             if ($carry->endDate $period->endDate) {
  989.                 $carry = new self(
  990.                     $carry->startDate,
  991.                     $period->endDate,
  992.                     $carry->boundaryType[0].$period->boundaryType[1]
  993.                 );
  994.             }
  995.         }
  996.         return $carry;
  997.     }
  998.     /**************************************************
  999.      * Mutation methods
  1000.      **************************************************/
  1001.     /**
  1002.      * Returns an instance with the specified starting date endpoint.
  1003.      *
  1004.      * This method MUST retain the state of the current instance, and return
  1005.      * an instance that contains the specified starting date endpoint.
  1006.      *
  1007.      * @param Datepoint|DateTimeInterface|int|string $startDate the new starting date endpoint
  1008.      */
  1009.     public function startingOn($startDate): self
  1010.     {
  1011.         $startDate self::filterDatepoint($startDate);
  1012.         if ($startDate == $this->startDate) {
  1013.             return $this;
  1014.         }
  1015.         return new self($startDate$this->endDate$this->boundaryType);
  1016.     }
  1017.     /**
  1018.      * Returns an instance with the specified ending date endpoint.
  1019.      *
  1020.      * This method MUST retain the state of the current instance, and return
  1021.      * an instance that contains the specified ending date endpoint.
  1022.      *
  1023.      * @param Datepoint|DateTimeInterface|int|string $endDate the new ending date endpoint
  1024.      */
  1025.     public function endingOn($endDate): self
  1026.     {
  1027.         $endDate self::filterDatepoint($endDate);
  1028.         if ($endDate == $this->endDate) {
  1029.             return $this;
  1030.         }
  1031.         return new self($this->startDate$endDate$this->boundaryType);
  1032.     }
  1033.     /**
  1034.      * Returns an instance with the specified boundary type.
  1035.      *
  1036.      * This method MUST retain the state of the current instance, and return
  1037.      * an instance with the specified range type.
  1038.      */
  1039.     public function boundedBy(string $bounds): self
  1040.     {
  1041.         if ($bounds === $this->boundaryType) {
  1042.             return $this;
  1043.         }
  1044.         return new self($this->startDate$this->endDate$bounds);
  1045.     }
  1046.     /**
  1047.      * DEPRECATION WARNING! This method will be removed in the next major point release.
  1048.      *
  1049.      * @deprecated 4.12.0 This method will be removed in the next major point release
  1050.      * @see Period::boundedBy()
  1051.      *
  1052.      * Returns an instance with the specified boundary type.
  1053.      *
  1054.      * This method MUST retain the state of the current instance, and return
  1055.      * an instance with the specified range type.
  1056.      */
  1057.     public function withBoundaryType(string $boundaryType): self
  1058.     {
  1059.         return $this->boundedBy($boundaryType);
  1060.     }
  1061.     /**
  1062.      * Returns a new instance with a new ending date endpoint.
  1063.      *
  1064.      * This method MUST retain the state of the current instance, and return
  1065.      * an instance that contains the specified ending date endpoint.
  1066.      *
  1067.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1068.      */
  1069.     public function withDurationAfterStart($duration): self
  1070.     {
  1071.         return $this->endingOn($this->startDate->add(self::filterDuration($duration)));
  1072.     }
  1073.     /**
  1074.      * Returns a new instance with a new starting date endpoint.
  1075.      *
  1076.      * This method MUST retain the state of the current instance, and return
  1077.      * an instance that contains the specified starting date endpoint.
  1078.      *
  1079.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1080.      */
  1081.     public function withDurationBeforeEnd($duration): self
  1082.     {
  1083.         return $this->startingOn($this->endDate->sub(self::filterDuration($duration)));
  1084.     }
  1085.     /**
  1086.      * Returns a new instance with a new starting datepoint
  1087.      * moved forward or backward by the given interval.
  1088.      *
  1089.      * This method MUST retain the state of the current instance, and return
  1090.      * an instance that contains the specified starting date endpoint.
  1091.      *
  1092.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1093.      */
  1094.     public function moveStartDate($duration): self
  1095.     {
  1096.         return $this->startingOn($this->startDate->add(self::filterDuration($duration)));
  1097.     }
  1098.     /**
  1099.      * Returns a new instance with a new ending datepoint
  1100.      * moved forward or backward by the given interval.
  1101.      *
  1102.      * This method MUST retain the state of the current instance, and return
  1103.      * an instance that contains the specified ending date endpoint.
  1104.      *
  1105.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1106.      */
  1107.     public function moveEndDate($duration): self
  1108.     {
  1109.         return $this->endingOn($this->endDate->add(self::filterDuration($duration)));
  1110.     }
  1111.     /**
  1112.      * Returns a new instance where the date endpoints
  1113.      * are moved forwards or backward simultaneously by the given DateInterval.
  1114.      *
  1115.      * This method MUST retain the state of the current instance, and return
  1116.      * an instance that contains the specified new date endpoints.
  1117.      *
  1118.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1119.      */
  1120.     public function move($duration): self
  1121.     {
  1122.         $duration self::filterDuration($duration);
  1123.         $interval = new self($this->startDate->add($duration), $this->endDate->add($duration), $this->boundaryType);
  1124.         if ($this->equals($interval)) {
  1125.             return $this;
  1126.         }
  1127.         return $interval;
  1128.     }
  1129.     /**
  1130.      * Returns an instance where the given DateInterval is simultaneously
  1131.      * subtracted from the starting date endpoint and added to the ending date endpoint.
  1132.      *
  1133.      * Depending on the duration value, the resulting instance duration will be expanded or shrinked.
  1134.      *
  1135.      * This method MUST retain the state of the current instance, and return
  1136.      * an instance that contains the specified new date endpoints.
  1137.      *
  1138.      * @param Period|DateInterval|Duration|string|int $duration a Duration
  1139.      */
  1140.     public function expand($duration): self
  1141.     {
  1142.         $duration self::filterDuration($duration);
  1143.         $interval = new self($this->startDate->sub($duration), $this->endDate->add($duration), $this->boundaryType);
  1144.         if ($this->equals($interval)) {
  1145.             return $this;
  1146.         }
  1147.         return $interval;
  1148.     }
  1149. }